3. 支付订单聚合的识别:从业务需求中识别支付订单聚合的边界

在领域驱动设计中,聚合(Aggregate)是保证数据一致性的最小单元。支付订单作为电商、金融等系统的核心业务对象,其聚合边界的划定直接影响系统的正确性、性能与可扩展性。本节将从真实的业务需求出发,逐步推导支付订单聚合的边界,并给出明确的建模决策。

3.1 业务需求场景分析

我们以一个典型的在线支付流程为例,梳理核心业务需求:

  • 用户提交支付请求:用户选择订单并发起支付,系统创建支付订单记录。
  • 调用第三方支付网关:系统向支付宝/等网关发起支付请求,获取支付链接或二维码。
  • 用户完成支付:用户在支付网关侧完成付款,网关异步通知系统支付结果。
  • 处理支付回调:系统接收回调,更新支付订单状态,并通知订单系统。
  • 退款处理:用户或客服发起退款,系统创建退款记录并调用网关退款接口。
  • 对账与结算:每日与支付网关对账,核对支付与退款记录。

这些需求背后,隐藏着多个业务对象:支付订单、支付流水、退款记录、对账记录、支付通知。它们之间的关系决定了聚合的边界。

3.2 聚合边界识别原则

在 DDD 中,识别聚合边界遵循以下核心原则:

原则 说明 支付订单场景应用
一致性边界 聚合内所有对象必须保持最终一致性或强一致性 支付订单与支付流水必须强一致,不能出现支付成功但流水丢失
事务边界 聚合是事务的最小单位,跨聚合的事务应尽量避免 支付订单与退款记录可接受最终一致性,适合拆分为不同聚合
业务不变量 聚合必须保护其内部业务规则不被破坏 支付金额不能超过订单金额,支付状态转换必须合法
生命周期 聚合根负责管理子对象的生命周期 支付订单聚合根负责创建和管理支付流水
实践提示: 支付订单聚合的边界划定,核心在于判断“支付流水”是否必须与“支付订单”在同一个事务中更新。如果支付流水丢失会导致资金损失,则必须放在同一聚合内。

3.3 支付订单聚合的边界决策

基于上述原则,我们对支付订单相关的业务对象进行聚合边界划分:

支付订单聚合边界识别 支付订单聚合(强一致性) 聚合根:支付订单 (PaymentOrder) orderId, amount, status, payChannel 子实体:支付流水 (PaymentTransaction) transactionId, amount, status, gatewayResp 值对象:支付方式 (PayMethod) 值对象:支付结果 (PayResult) 退款聚合(最终一致性) 聚合根:退款记录 (RefundRecord) refundId, amount, reason, status 值对象:退款结果 (RefundResult) 对账聚合(最终一致性) 聚合根:对账记录 (Reconciliation) reconId, date, diffCount, status 值对象:对账明细 (ReconItem) 说明:支付订单聚合内保持强一致性;退款与对账作为独立聚合,通过领域事件与支付订单聚合进行最终一致性通信

3.4 聚合边界详细说明

根据上图,我们得出以下聚合边界决策:

核心决策: 支付订单与支付流水属于同一聚合,由 PaymentOrder 作为聚合根。退款记录、对账记录作为独立聚合,通过领域事件与支付订单聚合交互。

3.4.1 支付订单聚合(强一致性)

  • 聚合根:PaymentOrder(支付订单)
  • 子实体:PaymentTransaction(支付流水)—— 每次支付尝试对应一条流水
  • 值对象:PayMethod(支付方式)、PayResult(支付结果)
  • 业务不变量:
    • 支付订单总金额 = 所有成功支付流水金额之和(不可超付)
    • 支付订单状态转换:INIT → PAYING → SUCCESS / FAILED / CLOSED
    • 同一支付订单不允许同时存在两条进行中的支付流水

3.4.2 退款聚合(最终一致性)

  • 聚合根:RefundRecord(退款记录)
  • 业务规则:退款总金额 ≤ 支付成功金额 - 已退款金额
  • 通信方式:通过领域事件 PaymentRefundedEvent 通知支付订单聚合更新可退金额

3.4.3 对账聚合(最终一致性)

  • 聚合根:Reconciliation(对账记录)
  • 业务规则:对账差异记录需人工或自动处理
  • 通信方式:通过领域事件 ReconciliationCompletedEvent 触发差异处理流程

3.5 聚合边界对缓存策略的影响

聚合边界的划定直接影响 Redis 缓存策略的设计:

聚合 缓存粒度 缓存策略 失效时机
支付订单聚合 按聚合根 ID 缓存整个聚合 String / Hash 存储聚合快照 支付状态变更、流水新增时失效
退款聚合 按退款 ID 缓存 String 存储,TTL 较短 退款状态变更时失效
对账聚合 按对账日期缓存 Hash 存储对账汇总,TTL 较长 每日对账完成后写入,次日失效
注意: 支付订单聚合内包含支付流水,缓存时必须保证聚合内所有数据的原子性。推荐使用 Redis Hash 结构,将支付订单字段与支付流水列表一起序列化存储,避免部分更新导致的数据不一致。

3.6 聚合边界验证

最后,我们通过几个典型业务场景验证聚合边界的合理性:

聚合边界验证场景 场景1:支付成功回调 1. 接收网关回调 2. 创建支付流水 3. 更新支付订单状态 4. 发布 PaymentCompletedEvent ✅ 同一聚合内,事务安全 流水与订单状态原子更新 场景2:发起退款 1. 创建退款记录 2. 调用网关退款接口 3. 发布 PaymentRefundedEvent 4. 支付订单异步更新可退金额 ⚠️ 跨聚合,最终一致性 通过领域事件解耦 场景3:并发支付请求 1. 用户重复点击支付 2. 同时发起两次支付 3. 聚合内检查进行中流水 4. 拒绝第二次请求 ✅ 聚合内并发控制 业务不变量得到保护

3.7 本章小结

  • 支付订单聚合的边界以强一致性为核心,将支付订单与支付流水放在同一聚合内。
  • 退款和对账作为独立聚合,通过领域事件实现最终一致性,降低事务复杂度。
  • 聚合边界的划定直接影响缓存粒度与失效策略,支付订单聚合适合按聚合根 ID 整体缓存。
  • 通过业务场景验证,确保聚合边界能够保护业务不变量,并支持高并发场景下的正确性。