模块 6:添加互动,关注用户
使用 DynamoDB 事务通过两种方法处理复杂操作
概述
到目前为止,我们已经实现了在移动应用程序中创建和检索核心实体(如 User 和 Photo)的访问模式。我们还学习了如何使用反向索引对实体实现其他查询模式。
在本模块中,我们将实现两种访问模式:
- 添加照片互动(写)
- 关注好友(写)
请注意,这两种访问模式都是向 DynamoDB 写入数据,与我们目前为止所做的重读取模式不同。
时长
20 分钟
DynamoDB 事务
为了实现以下步骤中的两种访问模式,我们将使用 DynamoDB ACID 事务。DynamoDB 事务于 2018 年 11 月发布。我们来快速了解一下 DynamoDB 中事务的运作方式。
事务在关系型数据库系统中广泛用于同时处理多个数据元素的操作。例如,假设您在经营一家银行。一位名叫 Karen 的顾客向另一位名叫 Sujith 的顾客转账 100 美元。在记录这笔交易时,您需要使用事务确保两位顾客的余额都得到相应的调整,而不只是其中一位。
在 DynamoDB 中添加事务后,可以更轻松地构建应用程序,以便在单个操作中更改多个数据项。通过 DynamoDB 事务,您可以通过一个事务请求完成对最多 10 个数据项的操作。
在调用 TransactWriteItem API 时,您可以执行以下类型的操作。
- Put:用于插入或覆盖数据项;
- Update:用于更新现有数据项;
- Delete:用于删除数据项;
- ConditionCheck:用于对现有数据项进行条件判断,而不对数据项进行任何更改。
在下面的步骤中,我们将使用 DynamoDB 事务,通过两种方法处理复杂操作。
操作步骤
-
添加照片互动
我们将在本模块中实现的第一种访问模式是添加照片互动。
在添加用户对照片的互动时,我们需要完成以下操作:
- 确认用户未在这张照片上使用过此互动类型
- 创建一个新的 Reaction 实体来存储互动数据
- 在 Photo 实体的互动属性中增加适当的互动类型,以便可以在照片上显示互动详情
请注意,这需要在两个不同的数据项(现有 Photo 实体和新的 Reaction 实体)之间执行写入操作,并为其中一个数据项设置条件逻辑。这种操作非常适合用 DynamoDB 事务来完成。
在您下载的代码中,application/ 目录下有一个名为 add_reaction.py 的脚本,其中包含一个添加照片互动的函数。该文件中的这个函数使用 DynamoDB 事务添加互动。
此文件的内容如下所示:
import datetime import boto3 dynamodb = boto3.client('dynamodb') REACTING_USER = 'kennedyheather' REACTION_TYPE = 'sunglasses' PHOTO_USER = 'ppierce' PHOTO_TIMESTAMP = '2019-04-14T08:09:34' def add_reaction_to_photo(reacting_user, reaction_type, photo_user, photo_timestamp): reaction = "REACTION#{}#{}".format(reacting_user, reaction_type) photo = "PHOTO#{}#{}".format(photo_user, photo_timestamp) user = "USER#{}".format(photo_user) try: resp = dynamodb.transact_write_items( TransactItems=[ { "Put": { "TableName": "quick-photos", "Item": { "PK": {"S": reaction}, "SK": {"S": photo}, "reactingUser": {"S": reacting_user}, "reactionType": {"S": reaction_type}, "photo": {"S": photo}, "timestamp": {"S": datetime.datetime.now().isoformat() } }, "ConditionExpression": "attribute_not_exists(SK)", "ReturnValuesOnConditionCheckFailure": "ALL_OLD" }, }, { "Update": { "TableName": "quick-photos", "Key": {"PK": {"S": user}, "SK": {"S": photo}}, "UpdateExpression": "SET reactions.#t = reactions.#t + :i", "ExpressionAttributeNames": { "#t": reaction_type }, "ExpressionAttributeValues": { ":i": { "N": "1" }, }, "ReturnValuesOnConditionCheckFailure": "ALL_OLD" } } ] ) print("Added {} reaction from {}".format(reaction_type, reacting_user)) return True except Exception as e: print("Could not add reaction to photo") add_reaction_to_photo(REACTING_USER, REACTION_TYPE, PHOTO_USER, PHOTO_TIMESTAMP)
在 add_reaction_to_photo 函数中,我们使用 transact_write_items() 方法执行写入事务。我们的事务包含两项操作。
首先,执行 Put 操作,插入新的 Reaction 实体。在进行该操作时,我们设定了一个条件,即该数据项不应存在 SK 属性。这样可以确保不存在同时具有 PK 和 SK 的数据项。如果存在,那就意味着用户已对这张照片添加了此互动。
第二项操作是对 User 实体执行 Update 操作,增加互动属性映射中的互动类型。DynamoDB 强大的更新表达式支持执行原子增量,而无需先检索数据项再更新。
在终端使用以下命令运行该脚本。
python application/add_reaction.py
终端输出应显示已为照片添加互动。
Added sunglasses reaction from kennedyheather
请注意,如果您尝试再次运行该脚本,函数将失败。用户 kennedyheather 已在这张照片上添加了此互动。因此,如果再次执行此操作就违反创建 Reaction 实体操作中的条件表达式。换句话说,该函数是幂等的,使用相同的输入重复调用,它不会产生不同的结果。
DynamoDB 事务的引入极大简化了此类复杂操作的处理流程。以前,需要调用多个带有复杂条件的 API,并在发生冲突时需要手动回滚。现在只需不到 50 行代码就能实现。
在下一步中,我们将了解如何处理“关注用户”访问模式。
-
关注用户
在您的应用程序中,一个用户可以关注另一用户。当应用程序后端收到关注用户的请求时,我们需要执行以下四项操作:
- 检查关注者用户是否尚未关注请求的用户;
- 创建一个 Friendship 实体来记录关注关系;
- 增加被关注用户的关注者数;
- 增加关注者用户的关注数。
在您下载的代码中,application/ 目录下有一个名为 follow_user.py 的文件。此文件的内容如下所示:
import datetime import boto3 dynamodb = boto3.client('dynamodb') FOLLOWED_USER = 'tmartinez' FOLLOWING_USER = 'john42' def follow_user(followed_user, following_user): user = "USER#{}".format(followed_user) friend = "#FRIEND#{}".format(following_user) user_metadata = "#METADATA#{}".format(followed_user) friend_user = "USER#{}".format(following_user) friend_metadata = "#METADATA#{}".format(following_user) try: resp = dynamodb.transact_write_items( TransactItems=[ { "Put": { "TableName": "quick-photos", "Item": { "PK": {"S": user}, "SK": {"S": friend}, "followedUser": {"S": followed_user}, "followingUser": {"S": following_user}, "timestamp": {"S": datetime.datetime.now().isoformat()}, }, "ConditionExpression": "attribute_not_exists(SK)", "ReturnValuesOnConditionCheckFailure": "ALL_OLD", } }, { "Update": { "TableName": "quick-photos", "Key": {"PK": {"S": user}, "SK": {"S": user_metadata}}, "UpdateExpression": "SET followers = followers + :i", "ExpressionAttributeValues": {":i": {"N": "1"}}, "ReturnValuesOnConditionCheckFailure": "ALL_OLD", } }, { "Update": { "TableName": "quick-photos", "Key": {"PK": {"S": friend_user}, "SK": {"S": friend_metadata}}, "UpdateExpression": "SET following = following + :i", "ExpressionAttributeValues": {":i": {"N": "1"}}, "ReturnValuesOnConditionCheckFailure": "ALL_OLD", } }, ] ) print("User {} is now following user {}".format(following_user, followed_user)) return True except Exception as e: print(e) print("Could not add follow relationship") follow_user(FOLLOWED_USER, FOLLOWING_USER)
文件中的 follow_user 函数与应用程序中需要实现的函数类似。该函数使用两个用户名(被关注用户的用户名和关注者用户的用户名),然后运行请求来创建一个 Friendship 实体,并更新这两个 User 实体。
在终端使用以下命令运行该脚本。
python application/follow_user.py
您应当会在终端看到表示操作成功的输出。
User john42 is now following user tmartinez
尝试在终端中再次运行脚本。这时,您应该会收到一条错误消息,表示您无法添加关注关系。这是因为该用户现在关注了请求的用户,因此请求未通过对该数据项的条件检查。
总结
在本模块中,我们了解了如何在应用程序中实现两种高级写操作。首先,我们使用 DynamoDB 事务实现用户对照片添加互动。通过事务,我们在单个请求中完成了对多个数据项的复杂条件写操作。此外,我们还了解了如何使用 DynamoDB 更新表达式,增加映射属性中的嵌套属性。
其次,我们实现了一个用户关注另一个用户的功能。这需要在单个请求中更改三个数据项,同时还要对其中一个数据项执行条件检查。虽然这项操作通常很复杂,但 DynamoDB 通过 DynamoDB 事务让这项操作变得简单。
在下一个模块中,我们将清理创建的资源,并了解 DynamoDB 学习路径中的一些后续步骤。