3. 聚合建模实战:订单聚合根设计、订单项实体、支付记录值对象、地址值对象

在支付订单系统中,领域驱动设计(DDD)的聚合建模是核心环节。本节将围绕“订单”这一核心聚合,详细拆解其内部组成:订单聚合根、订单项实体、支付记录值对象以及地址值对象。通过明确的边界划分与职责定义,确保数据一致性、业务完整性与高性能缓存策略的落地。

3.1 聚合根设计:订单(Order)

订单聚合根是整个订单生命周期的唯一入口与一致性边界。所有对订单内部状态的修改,必须通过聚合根暴露的方法进行,禁止外部直接操作内部实体或值对象。

设计要素 说明 示例
唯一标识 全局唯一订单号(OrderId),通常由分布式ID生成器产生 202503211234567890
生命周期状态 待支付、已支付、已取消、已退款、已完成 状态机驱动流转
内部集合 持有订单项实体集合(OrderItems) List<OrderItem>
值对象引用 持有支付记录值对象、地址值对象 PaymentRecord, Address
业务方法 创建订单、支付、取消、修改收货地址 pay(), cancel(), changeAddress()
设计原则:聚合根内部的所有变更必须保证最终一致性。例如,当订单支付成功时,聚合根应同时更新订单状态、生成支付记录,并触发库存扣减事件,而非由外部服务分别操作。

3.2 订单项实体(OrderItem)

订单项是订单聚合内的实体,拥有自己的本地标识(OrderItemId),但其生命周期完全依赖于订单聚合根。订单项代表用户购买的具体商品及数量。

  • 核心属性:商品ID(SkuId)、商品名称、单价、购买数量、实付金额、优惠分摊金额。
  • 业务约束:数量必须大于0;实付金额 = 单价 × 数量 - 优惠分摊。
  • 变更场景:仅在订单创建时添加,或在订单拆分/合并时调整,不允许独立于订单根进行修改。
缓存视角:订单项通常作为订单聚合根的嵌套集合整体缓存。在Redis中,使用Hash结构存储订单聚合根,其中订单项序列化为JSON字符串作为一个field。避免将每个订单项独立缓存,以减少缓存碎片与网络开销。

3.3 支付记录值对象(PaymentRecord)

支付记录是不可变的值对象,用于记录一次支付行为的完整快照。一旦生成,其属性不应被修改;若需变更支付方式或金额,应生成新的支付记录。

属性 类型 说明
paymentId String 支付流水号(第三方支付平台返回)
payAmount BigDecimal 实际支付金额(单位:分)
payMethod Enum 支付、支付宝、银行卡等
payTime Long 支付完成时间戳
tradeStatus Enum 成功、失败、退款中、已退款
不可变性陷阱:值对象在Java中应声明为final类,所有字段使用final修饰,且不提供setter方法。在Redis缓存中,支付记录作为订单Hash的一个field存储,更新时直接覆盖整个值对象,而非修改内部字段。

3.4 地址值对象(Address)

地址同样作为值对象存在,用于描述订单的收货信息。由于地址变更频繁且不涉及业务一致性(不影响订单金额),将其设计为值对象可简化聚合边界。

  • 属性组成:收件人姓名、联系电话、省份、城市、区县、详细地址、邮政编码。
  • 业务规则:地址变更时,直接替换整个值对象,无需保留历史版本(除非业务要求物流轨迹追溯)。
  • 缓存策略:地址与订单聚合根一同缓存,避免单独查询地址表。若地址数据量极大,可考虑在Redis中单独设置过期时间较短的缓存,但需与订单缓存保持一致性。

3.5 聚合建模整体结构图

以下SVG图展示了订单聚合根、订单项实体、支付记录值对象、地址值对象之间的包含关系与边界。

订单聚合建模结构图 聚合根边界(Order Aggregate) 订单聚合根(Order) OrderId, Status, TotalAmount 订单项实体(OrderItem) OrderItemId, SkuId, Quantity Price, ActualAmount 支付记录值对象(PaymentRecord) PaymentId, PayAmount, PayMethod PayTime, TradeStatus 地址值对象(Address) Receiver, Phone, Province, City, Detail 聚合根 实体 值对象 值对象

3.6 聚合内数据一致性策略

在支付订单系统中,聚合内的一致性通过以下机制保障:

  1. 事务边界:聚合根内部的所有操作(如创建订单+添加订单项+生成支付记录)应在同一个数据库事务中完成。
  2. 缓存更新:聚合根数据变更后,先更新数据库,再删除或更新Redis缓存(Cache-Aside模式)。
  3. 事件驱动:聚合根完成状态变更后,发布领域事件(如OrderPaidEvent),由订阅者处理库存扣减、积分赠送等跨聚合操作。
性能调优建议:对于高频读取的订单聚合,在Redis中使用Hash结构存储所有字段,避免序列化整个对象。例如:
HSET order:202503211234567890 status PAID totalAmount 9900 items "[{\"skuId\":\"1001\",\"qty\":2}]" payment "{\"payMethod\":\"WECHAT\",\"payAmount\":9900}" address "{\"city\":\"Beijing\"}"
这样可在不反序列化整个对象的情况下,单独获取订单状态或地址信息,显著降低缓存读取延迟。

3.7 聚合建模与缓存选型对照

聚合元素 推荐缓存结构 缓存Key设计 过期策略
订单聚合根 Redis Hash order:{orderId} TTL 30分钟 + 主动更新
订单项集合 Hash内嵌JSON order:{orderId} → field: items 随聚合根过期
支付记录 Hash内嵌JSON order:{orderId} → field: payment 随聚合根过期
地址 Hash内嵌JSON order:{orderId} → field: address 随聚合根过期