2. 领域驱动设计基础:实体、值对象、聚合、聚合根的核心概念回顾

在深入支付订单的聚合建模之前,必须重新校准 DDD 战术模式中的四个核心构造块。这些概念是划分服务边界、设计幂等 Key 以及保证数据一致性的理论基石。以下从定义、识别特征到建模规则进行系统回顾。

2.1 实体(Entity):拥有唯一标识与连续生命周期的对象

实体是 DDD 中最基本的建模单元。其核心特征在于:不是由属性值定义,而是由唯一的身份标识(ID)定义。即使两个实体的所有属性都相同,只要 ID 不同,它们就是不同的对象。

特征 说明 支付领域示例
唯一标识 每个实体拥有不可变的 ID(如 UUID、自增主键) OrderId、PaymentId、AccountId
可变属性 属性可以随时间变化,但身份不变 订单状态从“待支付”变为“已支付”
生命周期 从创建到归档,实体持续存在 支付记录从创建到对账完成
相等性 通过 ID 判断相等,而非属性 orderA.equals(orderB) 比较 orderId
建模提示:在支付订单中,Order 和 Payment 都是典型的实体。不要将“订单金额”或“支付时间”这类值对象误当作实体来建模——它们没有独立的生命周期。

2.2 值对象(Value Object):描述性、不可变、无身份

值对象用于描述实体的某个属性或特征,它没有唯一标识,且通常是不可变(Immutable)的。两个值对象如果所有属性都相同,则视为同一个对象。

  • 不可变性:一旦创建,属性不可修改。需要变更时,直接替换为新的值对象实例。
  • 无身份:不分配 ID,完全由属性值决定相等性。
  • 自包含:通常包含验证逻辑和简单的行为(如金额的加法、地址的格式化)。
支付领域值对象 属性示例 为什么是值对象
Money amount、currency 金额 100 USD 就是 100 USD,没有独立身份
Address country、city、detail 地址相同即相同,不关心是哪个“地址实例”
PaymentMethod type、lastFourDigits 描述支付方式,不跟踪其生命周期
TimestampRange start、end 时间段本身是值,不是实体
常见误区:很多团队将 Money 设计为实体,导致每次金额变更都要生成新的 ID,徒增复杂度。记住:值对象优先于实体,能用值对象就不要用实体。

2.3 聚合(Aggregate):事务一致性的边界

聚合是一组相关对象的集合,它作为一个整体参与数据变更。聚合内部的所有对象必须满足事务一致性:一个事务内对聚合的任何修改,要么全部成功,要么全部回滚。

聚合的核心规则:

  • 原子性:聚合内部的操作是原子的,外部只能通过聚合根访问。
  • 边界性:聚合是持久化的最小单元。一次 save 操作保存整个聚合。
  • 最终一致性:不同聚合之间的数据同步,允许使用事件驱动实现最终一致性。
聚合边界与内部结构示意图 聚合边界(Aggregate Boundary) 聚合根:Order orderId: UUID, status: Enum 实体:Payment paymentId, amount, status 值对象:Money amount: 100, currency: USD 值对象:Address city, detail, zipCode 值对象:TimestampRange createdAt, updatedAt 事务一致性边界:一次 save 操作保存 Order + Payment + Money + Address + TimestampRange

2.4 聚合根(Aggregate Root):外部访问的唯一入口

聚合根是聚合中唯一允许被外部对象引用的实体。外部对象不能直接持有聚合内部其他对象的引用,只能通过聚合根的方法来操作内部状态。

  • 全局标识:聚合根拥有全局唯一的 ID,外部通过该 ID 引用整个聚合。
  • 一致性守卫:所有对聚合内部状态的修改,都必须经过聚合根的方法,确保业务规则不被绕过。
  • 持久化入口:Repository 只负责持久化聚合根,级联保存内部对象。
支付场景示例:在支付订单聚合中,Order 是聚合根。外部服务(如支付网关回调)只能通过 order.pay(paymentId, money) 方法修改状态,而不能直接操作内部的 Payment 实体。这保证了“支付金额必须与订单金额一致”等业务规则不会被绕过。

2.5 实体 vs 值对象:决策矩阵

在实际建模中,区分实体和值对象是设计聚合的第一步。以下决策矩阵可以帮助快速判断:

判断问题 是 → 实体 否 → 值对象
这个对象需要被单独跟踪和修改吗? 需要(如订单状态变更) 不需要(如金额只是数值)
两个对象属性相同,但算不同吗? 算不同(如两个订单 ID 不同) 算相同(如两个 100 USD 等价)
对象有独立生命周期吗? 有(从创建到删除) 没有(创建即使用,用完即弃)
对象需要持久化到独立表吗? 通常需要独立表 通常作为字段嵌入实体表

2.6 聚合设计原则:小聚合 vs 大聚合

聚合的大小直接影响系统性能和一致性模型。以下是两种风格的对比:

维度 小聚合 大聚合
包含对象数 1~3 个对象 5 个以上对象
事务范围 小,并发冲突少 大,并发冲突多
一致性 强一致性(单聚合内) 强一致性(但范围大,性能差)
适用场景 高频写入、高并发 低频写入、复杂业务规则
支付领域示例 订单聚合只包含 Order + Money 订单聚合包含 Order + Payment + Items + Address + Discount
推荐实践:在支付订单建模中,优先采用小聚合策略。将订单、支付、退款拆分为独立的聚合,通过领域事件实现最终一致性。这能显著降低数据库锁冲突,提升系统吞吐量。
小聚合与大聚合对比示意图 小聚合(推荐) 聚合根:Order orderId, status 值对象:Money amount, currency ✅ 高并发,低锁冲突 ✅ 事务范围小 ✅ 易于扩展 大聚合(谨慎使用) 聚合根:Order Payment Money OrderItem Address Discount ⚠️ 低并发,高锁冲突

2.7 本章小结:核心概念速查表

概念 一句话定义 支付领域典型例子
实体 有唯一 ID,属性可变,生命周期持续 Order、Payment、Account
值对象 无 ID,不可变,由属性定义相等性 Money、Address、TimestampRange
聚合 一组相关对象的集合,事务一致性边界 订单聚合(Order + Money + Payment)
聚合根 聚合的唯一入口,外部只能通过它操作内部 Order 作为聚合根