AWS 上有一个关于类似问题的动手实验室 - “包含社交网络的移动应用程序”:https://aws.amazon.com/getting-started/hands-on/design-a-database-for-a-mobile-app-with-dynamodb/4/
简要说明:
- 用户将通过您的应用上传照片
- 用户希望找到并关注朋友
- 关注好友后,用户将收到好友新照片的通知
- 用户将能够向他们的朋友发送消息
- 朋友可以查看他们的照片
- 用户可以使用四种表情符号之一对照片做出反应——心形、笑脸、竖起大拇指或一副太阳镜。
- 查看照片时,用户应该能够看到照片收到的每种类型反应的数量
该模型具有以下实体:User、Photo、Reaction、Friendship。
一个User 可以有多个Photos,一个Photo 可以有多个Reactions。最后,Friendship 实体代表了用户之间的多对多关系,因为一个用户可以关注多个用户,也可以被多个其他用户关注。
访问模式
根据业务需求,这些是确定的访问模式:
用户
- 创建用户配置文件(写入)
- 更新用户资料(写入)
- 获取用户配置文件(读取)
照片
- 为用户上传照片(写)
- 查看用户最近的照片(阅读)
- 对照片做出反应(写)
- 查看照片和反应(阅读)
友谊
用户可以关注朋友,查看他们朋友的活动更新,并接收他们可能想要关注的其他朋友的推荐。
友谊是一种单向的关系,就像 Twitter。一个用户可以选择关注另一个用户,并且该用户可以选择关注该用户。对于我们的应用程序,我们将关注用户的用户称为“关注者”,将用户关注的用户称为“关注者”。
根据这些信息,我们有以下访问模式:
- 关注用户(写)
- 查看用户的关注者(阅读)
- 用户关注的视图(读取)
在 Friendship 实体上,我们有一个访问模式需要查找关注特定用户的所有用户,以及一个访问模式来查找给定用户关注的所有用户。
餐桌设计
因此,我们将使用具有 PK 和 SK 值的复合主键。复合主键将为我们提供 PK 上的查询能力,以满足我们需要的查询模式之一:
Entity PK SK
User USER#<USERNAME> #METADATA#<USERNAME>
Photo USER#<USERNAME>. PHOTO#<USERNAME>#<TIMESTAMP>
Reaction REACTION#<USERNAME>#<TYPE> PHOTO#<USERNAME>#<TIMESTAMP>
Friendship USER#<USERNAME> #FRIEND#<FRIEND_USERNAME>
Friendship 实体使用与 User 实体相同的 PK。这将允许您在单个查询中获取用户的元数据以及用户的所有关注者:
KeyConditionExpression="PK = :pk AND SK BETWEEN :metadata AND :photos",
ExpressionAttributeValues={
":pk": { "S": "USER#{}".format(username) },
":metadata": { "S": "#METADATA#{}".format(username) },
":photos": { "S": "PHOTO$" },
},
二级(反向)索引对于查询多对多关系的“另一端”很有用。您的友谊实体就是这种情况。使用您的主键结构,您可以通过查询表的主键来查询特定用户的所有关注者。添加倒排索引后,您将能够通过查询倒排索引找到用户正在关注的用户(“已关注”):
KeyConditionExpression="SK = :sk",
ExpressionAttributeValues={
":sk": { "S": "#FRIEND#{}".format(username) }
},
扩展
有趣的是调整设计以支持超受欢迎的用户(拥有数百万追随者)。
这里没有提到的另一个有趣的访问模式是用户提要 - 查看他们的朋友最近发布的所有照片。这可以通过另一个表来完成,以包含此数据流,每当朋友发布某些内容时(查找他的关注者,更新他们的提要...),该数据流就会更新。