使用 DynamoDB 作为在线商店的数据存储
此使用案例讨论了使用 DynamoDB 作为在线商店(或电子商店)的数据存储。
应用场景
在线商店可让用户浏览不同的商品并最终购买它们。根据生成的发票,客户可以使用折扣码或礼品卡付款,然后使用信用卡支付剩余金额。采购的商品将从几个仓库中的一个仓库中挑选,并发运到提供的地址。在线商店的典型访问模式包括:
-
获取给定 customerId 的客户
-
获取给定 productId 的商品
-
获取给定 warehouseId 的仓库
-
通过 productId 获取所有仓库的商品库存
-
获取给定 orderId 的订单
-
获取给定 orderId 的所有商品
-
获取给定 orderId 的发票
-
获取给定 orderId 的所有货件
-
获取给定日期范围内给定 productId 的所有订单
-
获取给定 invoiceId 的发票
-
获取给定 invoiceId 的所有付款
-
获取给定 shipmentId 的货件详细信息
-
获取给定 warehouseId 的货件
-
获取给定 warehouseId 的所有商品的库存
-
获取给定日期范围内给定 customerId 的所有发票
-
获取给定日期范围内给定 customerId 订购的所有商品
实体关系图
这是实体关系图(ERD),我们将使用它来将 DynamoDB 建模为在线商店的数据存储。
访问模式
当使用 DynamoDB 作为在线商店的数据存储时,我们将考虑这些访问模式。
-
getCustomerByCustomerId -
getProductByProductId -
getWarehouseByWarehouseId -
getProductInventoryByProductId -
getOrderDetailsByOrderId -
getProductByOrderId -
getInvoiceByOrderId -
getShipmentByOrderId -
getOrderByProductIdForDateRange -
getInvoiceByInvoiceId -
getPaymentByInvoiceId -
getShipmentDetailsByShipmentId -
getShipmentByWarehouseId -
getProductInventoryByWarehouseId -
getInvoiceByCustomerIdForDateRange -
getProductsByCustomerIdForDateRange
架构设计的演变
使用NoSQL Workbench for DynamoDB,导入 AnOnlineShop_1.jsonAnOnlineShop 的新数据模型和名为 OnlineShop 的新表。请注意,我们使用通用名称 PK 和 SK 作为分区键和排序键。这是一种用于将不同类型的实体存储在同一个表中的做法。
步骤 1:解决访问模式 1 (getCustomerByCustomerId)
导入 AnOnlineShop_2.jsongetCustomerByCustomerId)。有些实体与其他实体没有关系,因此我们将对它们使用相同的 PK 和 SK 值。在示例数据中,请注意,键使用前缀 c#,以便将 customerId 与稍后添加的其他实体区分开来。对于其他实体也重复这种做法。
为了解决这种访问模式,GetItem 操作可以与 PK=customerId 和 SK=customerId 结合使用。
步骤 2:解决访问模式 2 (getProductByProductId)
导入 AnOnlineShop_3.jsonproduct 实体的访问模式 2(getProductByProductId)。产品实体的前缀为 p#,并且使用了相同的排序键属性来存储 customerID 以及 productID。通用命名和垂直分区允许我们创建这样的项目集合,以实现有效的单表设计。
为了解决这种访问模式,GetItem 操作可以与 PK=productId 和 SK=productId 结合使用。
步骤 3:解决访问模式 3 (getWarehouseByWarehouseId)
导入 AnOnlineShop_4.jsonwarehouse 实体的访问模式 3(getWarehouseByWarehouseId)。我们目前已将 customer、product 和 warehouse 实体添加到同一个表中。它们使用前缀和 EntityType 属性进行区分。类型属性(或前缀命名)可提高模型的可读性。如果我们只是将不同实体的字母数字 ID 存储在同一属性中,可读性就会受到影响。在没有这些标识符的情况下,很难将一个实体与另一个实体区分开来。
为了解决这种访问模式,GetItem 操作可以与 PK=warehouseId 和 SK=warehouseId 结合使用。
基表:
步骤 4:解决访问模式 4 (getProductInventoryByProductId)
导入 AnOnlineShop_5.jsongetProductInventoryByProductId)。warehouseItem 实体用于跟踪每个仓库中的商品数量。在仓库中添加或移除商品时,通常会更新此项目。从 ERD 中可以看出,product 和 warehouse 之间存在多对多关系。在此处,从 product 到 warehouse 的一对多关系建模为 warehouseItem。稍后,也将对从 warehouse 到 product 的一对多关系进行建模。
访问模式 4 可以通过查询 PK=ProductId 和 SK begins_with “w#“ 来解决。
有关 begins_with() 以及其他可应用于排序键的表达式的更多信息,请参阅键条件表达式。
基表:
步骤 5:解决访问模式 5(getOrderDetailsByOrderId)和 6(getProductByOrderId)
通过导入 AnOnlineShop_6.jsoncustomer、product 和 warehouse 项目。然后,导入 AnOnlineShop_7.jsonorder 构建一个项目集合,该集合可以处理访问模式 5(getOrderDetailsByOrderId)和 6(getProductByOrderId)。您可以看到建模为 orderItem 实体的 order 和 product 之间的一对多关系。
要解决访问模式 5(getOrderDetailsByOrderId),请使用 PK=orderId 查询表。这将提供有关订单的所有信息,包括 customerId 和订购的商品。
基表:
要解决访问模式 6(getProductByOrderId),我们只需要读取 order 中的商品。使用 PK=orderId 和 SK begins_with “p#” 查询表来实现这一目标。
基表:
步骤 6:解决访问模式 7(getInvoiceByOrderId)
导入 AnOnlineShop_8.jsoninvoice 实体添加到订单 项目集合中,以处理访问模式 7(getInvoiceByOrderId)。为了解决这种访问模式,您可以将查询操作与 PK=orderId 和 SK begins_with
“i#” 结合使用。
基表:
步骤 7:解决访问模式 8(getShipmentByOrderId)
导入 AnOnlineShop_9.jsonshipment 实体添加到订单 项目集合中,以解决访问模式 8(getShipmentByOrderId)。我们通过在单表设计中添加更多类型的实体来扩展相同的垂直分区模型。请注意订单 项目集合如何包含 order 实体与 shipment、orderItem 和 invoice 实体之间的不同关系。
要按 orderId 获取货件,可以使用 PK=orderId 和 SK begins_with “sh#” 执行查询操作。
基表:
步骤 8:解决访问模式 9(getOrderByProductIdForDateRange)
我们在上一步中创建了一个订单 项目集合。此访问模式具有新的查找维度(ProductID 和 Date),这要求您扫描整个表并筛选掉相关记录以获取目标项目。为了解决这种访问模式,我们需要创建全局二级索引(GSI)。导入 AnOnlineShop_10.jsonorderItem 数据。数据现在具有 GSI1-PK 和 GSI1-SK,它们将分别是 GSI1 的分区键和排序键。
DynamoDB 会自动将包含 GSI 的键属性的项目从表填充到 GSI。无需手动在 GSI 中进行任何其他插入。
要解决访问模式 9,请使用 GSI1-PK=productId 和 GSI1SK between (date1,
date2) 对 GSI1 执行查询。
基表:
GSI1:
步骤 9:解决访问模式 10(getInvoiceByInvoiceId)和 11(getPaymentByInvoiceId)
导入 AnOnlineShop_11.jsongetInvoiceByInvoiceId)和 11(getPaymentByInvoiceId),这两者都与 invoice 相关。尽管这些是两种不同的访问模式,但它们是使用相同的键条件实现的。Payments 定义为 invoice 实体上具有映射数据类型的属性。
注意
GSI1-PK 和 GSI1-SK 已重载以存储有关不同实体的信息,因此,可以从同一 GSI 提供多种访问模式。有关 GSI 重载的更多信息,请参阅在 DynamoDB 中重载全局二级索引。
要解决访问模式 10 和 11,请使用 GSI1-PK=invoiceId 和 GSI1-SK=invoiceId 查询 GSI1。
GSI1:
步骤 10:解决访问模式 12(getShipmentDetailsByShipmentId)和 13(getShipmentByWarehouseId)
导入 AnOnlineShop_12.jsongetShipmentDetailsByShipmentId)和 13(getShipmentByWarehouseId)。
请注意,shipmentItem 实体添加到基表上的订单 项目集合中,以便能够在单个查询操作中检索有关订单的所有详细信息。
基表:
GSI1 分区和排序键已用于对 shipment 和 shipmentItem 之间的一对多关系进行建模。要解决访问模式 12(getShipmentDetailsByShipmentId),请使用 GSI1-PK=shipmentId 和 GSI1-SK=shipmentId 查询 GSI1。
GSI1:
我们需要创建另一个 GSI(GSI2),来为访问模式 13(getShipmentByWarehouseId)的 warehouse 和 shipment 之间新的一对多关系建模。要解决此访问模式,请使用 GSI2-PK=warehouseId 和 GSI2-SK
begins_with “sh#” 查询 GSI2。
GSI2:
步骤 11:解决访问模式 14(getProductInventoryByWarehouseId)、15(getInvoiceByCustomerIdForDateRange)和 16(getProductsByCustomerIdForDateRange)
导入 AnOnlineShop_13.jsongetProductInventoryByWarehouseId),请使用 GSI2-PK=warehouseId 和 GSI2-SK
begins_with “p#” 查询 GSI2。
GSI2:
要解决访问模式 15(getInvoiceByCustomerIdForDateRange),请使用 GSI2-PK=customerId 和 GSI2-SK between
(i#date1, i#date2) 查询 GSI2。
GSI2:
要解决访问模式 16(getProductsByCustomerIdForDateRange),请使用 GSI2-PK=customerId 和 GSI2-SK between
(p#date1, p#date2) 查询 GSI2。
GSI2:
注意
在 NoSQL Workbench 中,分面 表示应用程序对 DynamoDB 的不同数据访问模式。分面为您提供了一种查看表中数据子集的方法,而不必查看不符合分面约束的记录。分面是一种可视化数据建模工具,在 DynamoDB 中不作为可用的构造存在,因为它们纯粹是对访问模式建模的帮助。
导入 AnOnlineShop_facets.json
下表总结了所有访问模式以及架构设计如何解决访问模式:
| 访问模式 | 基表/GSI/LSI | 操作 | 分区键值 | 排序键值 |
|---|---|---|---|---|
| getCustomerByCustomerId | 基表 | GetItem | PK=customerId | SK=customerId |
| getProductByProductId | 基表 | GetItem | PK=productId | SK=productId |
| getWarehouseByWarehouseId | 基表 | GetItem | PK=warehouseId | SK=warehouseId |
| getProductInventoryByProductId | 基表 | Query | PK=productId | SK begins_with "w#" |
| getOrderDetailsByOrderId | 基表 | Query | PK=orderId | |
| getProductByOrderId | 基表 | Query | PK=orderId | SK begins_with "p#" |
| getInvoiceByOrderId | 基表 | Query | PK=orderId | SK begins_with "i#" |
| getShipmentByOrderId | 基表 | Query | PK=orderId | SK begins_with "sh#" |
| getOrderByProductIdForDateRange | GSI1 | 查询 | PK=productId | date1 和 date2 之间的 SK |
| getInvoiceByInvoiceId | GSI1 | 查询 | PK=invoiceId | SK=invoiceId |
| getPaymentByInvoiceId | GSI1 | 查询 | PK=invoiceId | SK=invoiceId |
| getShipmentDetailsByShipmentId | GSI1 | 查询 | PK=shipmentId | SK=shipmentId |
| getShipmentByWarehouseId | GSI2 | 查询 | PK=warehouseId | SK begins_with "sh#" |
| getProductInventoryByWarehouseId | GSI2 | 查询 | PK=warehouseId | SK begins_with "p#" |
| getInvoiceByCustomerIdForDateRange | GSI2 | 查询 | PK=customerId | i#date1 和 i#date2 之间的 SK |
| getProductsByCustomerIdForDateRange | GSI2 | 查询 | PK=customerId | p#date1 和 p#date2 之间的 SK |
在线商店最终架构
这是最终的架构设计。要以 JSON 文件格式下载此架构设计,请参阅 GitHub 上的 DynamoDB 设计模式
基表
GSI1
GSI2
在此架构设计中使用 NoSQL Workbench
若要进一步探索和编辑新项目,您可以将此最终架构导入到 NoSQL Workbench,这是一款为 DynamoDB 提供数据建模、数据可视化和查询开发功能的可视化工具。请按照以下步骤开始使用:
-
下载 NoSQL Workbench。有关更多信息,请参阅 下载 NoSQL Workbench for DynamoDB。
-
下载上面列出的 JSON 架构文件,该文件已经采用 NoSQL Workbench 模型格式。
-
将 JSON 架构文件导入到 NoSQL Workbench。有关更多信息,请参阅 导入现有数据模型。
-
导入到 NOSQL Workbench 后,您便可编辑数据模型。有关更多信息,请参阅 编辑现有数据模型。
-
要将数据模型可视化、添加样本数据或从 CSV 文件导入样本数据,请使用 NoSQL Workbench 的数据可视化工具功能。