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

在深入支付订单系统的聚合建模之前,必须重新审视领域驱动设计的五个核心构造块。这些概念不仅是战术建模的基石,更是决定微服务边界、数据一致性和缓存策略的关键。以下逐一拆解其定义、特征及在支付场景中的映射。

1. 实体(Entity)

实体是具有唯一标识且会随时间变化的对象。其核心特征是身份连续性——即使所有属性都改变,标识依然不变。

  • 标识方式:自然主键(如订单号)、系统生成ID(如雪花算法ID)、业务组合键。
  • 可变性:属性可修改,但标识不可变。
  • 相等性:通过标识判断相等,而非属性值。
支付场景示例: 订单实体(Order)——订单号“ORD20250321001”始终代表同一笔交易,即使其状态从“待支付”变为“已支付”,地址被修改,它仍然是同一个订单。

2. 值对象(Value Object)

值对象通过属性值来定义自身,没有唯一标识,且不可变。当属性值完全相同时,两个值对象被视为相等。

特征 实体 值对象
标识 有唯一标识 无标识
可变性 可变 不可变(替换而非修改)
相等性 标识相等 属性值全等
生命周期 独立生命周期 依附于实体
支付案例 订单、支付单、账户 金额、地址、货币类型
设计原则: 优先使用值对象而非基本类型。例如,将“BigDecimal amount + String currency”封装为 Money 值对象,可避免精度丢失和货币单位混淆。

3. 聚合(Aggregate)与聚合根(Aggregate Root)

聚合是领域对象的一致性边界,它将一组关联的实体和值对象封装为一个整体。聚合根是聚合中唯一允许外部访问的入口,所有外部操作必须通过聚合根进行。

  • 一致性边界:聚合内部保证事务一致性,跨聚合使用最终一致性。
  • 聚合根规则:聚合根是实体,拥有全局标识;聚合内部对象只能通过聚合根访问。
  • 引用方式:外部只持有聚合根的ID,不持有内部对象的引用。
常见误区: 将数据库表的一对多关系直接映射为聚合关系。例如,订单行(OrderLine)不应独立存在,而应作为订单聚合的一部分,通过订单根来管理。

以下 SVG 展示了支付订单聚合的典型结构,清晰呈现聚合根、实体与值对象的边界关系:

支付订单聚合结构 聚合边界(事务一致性) 订单(聚合根) orderId: String, status: Enum 支付单(实体) paymentId, amount, channel 物流单(实体) shipmentId, address, status 金额(值对象) amount: BigDecimal, currency 地址(值对象) province, city, detail 聚合规则 外部只能通过订单根访问支付单和物流单;金额和地址不可变,替换即创建新实例

4. 领域事件(Domain Event)

领域事件是过去发生的、对业务有意义的事实记录。它用于解耦聚合之间的通信,是实现最终一致性的核心机制。

  • 命名规范:使用过去时态,如 OrderPaid、PaymentFailed。
  • 不可变性:事件一旦发布,内容不可更改。
  • 发布时机:在聚合根的方法中,状态变更后立即发布。
  • 存储与投递:通常先持久化事件,再通过消息中间件投递。
支付场景示例: 当订单支付成功时,订单聚合根发布 OrderPaidEvent,包含 orderId、paidAmount、paidTime。库存服务订阅该事件后扣减库存,积分服务增加积分——整个过程通过事件驱动,无需同步调用。

以下 SVG 展示了领域事件在支付流程中的流转路径:

领域事件驱动流程(支付成功场景) 订单聚合根 发布 OrderPaidEvent 事件存储 持久化 + 投递 消息队列 Kafka / RocketMQ 库存服务 扣减库存 积分服务 增加积分 通知服务 发送短信/邮件 事件驱动实现最终一致性:订单聚合只负责发布事件,不关心谁订阅

5. 核心概念对比总结

概念 标识 可变性 生命周期 支付订单中的角色
实体 有(全局唯一) 可变 独立 订单、支付单、退款单
值对象 无 不可变 依附于实体 金额、货币、地址、银行卡号
聚合根 有(全局唯一) 可变(控制边界) 独立(入口) 订单(管理支付单、物流单)
领域事件 有(事件ID) 不可变 发布即存档 OrderPaid、PaymentRefunded
建模建议: 在支付订单系统中,将“订单”设计为聚合根,内部包含“支付单”实体和“金额”值对象。跨聚合的交互(如订单与库存)通过领域事件解耦,为后续的幂等处理和缓存策略奠定结构基础。