1. 领域驱动设计基础:聚合根、实体、值对象的概念与区别

领域驱动设计(Domain-Driven Design,DDD)为复杂业务系统的建模提供了严谨的战术模式。在支付订单领域,正确区分聚合根(Aggregate Root)、实体(Entity)和值对象(Value Object),是构建高内聚、低耦合领域模型的前提。本节将逐一解析这三个核心概念,并结合支付订单场景说明其应用。

1.1 核心概念定义与对比

概念 定义 标识特征 可变性 生命周期
实体(Entity) 具有唯一标识、且在其生命周期内状态可连续变化的对象。 拥有 ID(如订单号、用户ID) 可变(状态可更新) 独立、持续
值对象(Value Object) 通过属性值描述某个概念,无唯一标识,不可变。 无 ID,靠属性值相等判断 不可变(替换而非修改) 依附于实体
聚合根(Aggregate Root) 聚合的入口实体,负责维护聚合内部所有对象的一致性边界。 是实体的一种,拥有全局唯一 ID 可变(通过聚合根方法) 管理整个聚合生命周期
关键区别速记:实体关心“我是谁”(ID),值对象关心“我是什么”(属性值)。聚合根是“边界守卫”,外部只能通过聚合根访问聚合内部。

1.2 在支付订单模型中的应用

以一笔典型的电商支付订单为例,我们可以将领域模型拆解如下:

  • 聚合根:PaymentOrder —— 支付订单聚合的入口。外部系统(如支付网关回调、用户取消请求)只能通过 PaymentOrder 的方法来修改聚合内部状态。它持有全局唯一的 paymentOrderId。
  • 实体:PaymentTransaction —— 支付交易记录。每次支付尝试(如首次支付、重试、退款)都会生成一个交易记录,拥有自己的 transactionId,但其生命周期完全受 PaymentOrder 管理。
  • 值对象:Money —— 金额对象。包含 amount 和 currency 两个属性,无 ID。当需要修改金额时,直接替换整个 Money 实例,而非修改其内部属性。
  • 值对象:PaymentMethod —— 支付方式。描述支付使用的渠道(如支付宝、)、卡号后四位等,无独立标识,不可变。
  • 值对象:Address —— 账单地址。由省、市、街道等属性构成,无 ID,不可变。
建模原则:在支付订单聚合中,PaymentOrder 是唯一允许被外部引用的对象。所有对 PaymentTransaction 或 Money 的修改,都必须通过 PaymentOrder 提供的领域方法完成,从而保证聚合内部的数据一致性。

1.3 聚合边界与一致性

聚合根的核心职责是维护事务一致性边界。在支付场景中,以下规则必须在同一个事务中保证:

  1. 订单总金额必须等于所有已确认交易金额之和。
  2. 订单状态(待支付、支付中、已支付、已退款)与交易状态(成功、失败、处理中)必须逻辑一致。
  3. 支付方式变更时,必须重置所有未完成交易的支付渠道信息。

这些规则由 PaymentOrder 聚合根的方法(如 confirmPayment()、refund())统一实施,外部调用者无需关心内部细节。

支付订单领域模型:聚合根、实体与值对象 聚合边界(Aggregate Boundary) 聚合根:PaymentOrder paymentOrderId: String (全局唯一ID) 状态: 待支付 | 支付中 | 已支付 | 已退款 实体:PaymentTransaction transactionId: String 状态: 创建 | 处理中 | 成功 | 失败 值对象:Money amount: BigDecimal currency: Currency (不可变) 值对象:PaymentMethod channel: String, cardLast4: String 值对象:Address province, city, street (不可变) 管理 包含 引用 引用 说明:外部系统只能通过 PaymentOrder 聚合根访问内部实体和值对象,确保数据一致性。

1.4 实体 vs 值对象:支付场景中的选择依据

在建模过程中,判断一个概念应该是实体还是值对象,可以依据以下问题:

  • 它需要被独立追踪吗? —— 需要则选实体。例如,每笔交易记录需要被审计追踪,因此 PaymentTransaction 是实体。
  • 它的属性值改变后,还是同一个对象吗? —— 如果属性改变意味着对象被替换,则是值对象。例如,金额从 100 元变为 200 元,我们不会说“同一个金额对象被修改了”,而是说“新的金额对象替换了旧的”。
  • 它会被多个实体共享吗? —— 值对象通常被共享且不可变。例如,多个订单可以引用同一个 Address 实例(如果地址相同)。
常见误区:不要将数据库主键 ID 作为判断实体的唯一标准。在 DDD 中,实体 ID 是领域标识,而非数据库代理键。例如,Money 在数据库中可能有行 ID,但在领域模型中它仍然是值对象,因为其领域标识是金额和货币的组合。

1.5 聚合根的设计原则

设计 PaymentOrder 聚合根时,需遵循以下原则:

  1. 原子性:聚合内的所有对象必须在同一个事务中保持一致。
  2. 最小化:聚合应尽量小,只包含必须保持一致的属性。例如,PaymentOrder 不应包含用户个人信息(如用户名、手机号),这些属于另一个聚合。
  3. 通过 ID 引用外部聚合:聚合之间通过 ID 引用,而非直接持有对象引用。例如,PaymentOrder 持有 userId,而非 User 对象。
  4. 最终一致性:跨聚合的更新可以使用领域事件实现最终一致性。例如,支付成功事件触发订单状态更新。
聚合根设计原则:通过 ID 引用外部聚合 PaymentOrder 聚合 paymentOrderId: String userId: String (外部引用) transactions: List<Transaction> 通过 userId 引用 User 聚合 userId: String name: String email: String 原则:聚合之间通过 ID 引用,不直接持有对象,降低耦合度。

1.6 小结

在支付订单领域模型中:

  • 聚合根(PaymentOrder)是事务一致性的守卫者,外部只能通过它操作聚合内部。
  • 实体(PaymentTransaction)拥有独立标识,状态可变,生命周期由聚合根管理。
  • 值对象(Money、PaymentMethod、Address)无标识、不可变,用于描述实体的属性。

正确区分这三者,是后续进行 OpenAPI 接口参数配置与测试的基础。聚合根决定了 API 的入口和资源路径,实体和值对象则决定了请求/响应体的字段结构。