2. 领域驱动设计基础:聚合、实体、值对象在支付订单中的概念映射
在支付订单领域,DDD 的三个核心构造块——实体(Entity)、值对象(Value Object)和聚合(Aggregate)——为复杂业务逻辑提供了清晰的建模框架。理解它们在支付场景中的具体映射,是后续实现幂等处理和防重策略的前提。
2.1 实体:具有唯一标识且可变的对象
实体通过 ID 而非属性值来区分,其状态会随时间或业务操作发生变化。在支付订单领域,以下对象应建模为实体:
| 实体名称 | 唯一标识 | 可变状态示例 | DDD 角色 |
|---|---|---|---|
| 支付订单(PaymentOrder) | paymentOrderId(全局唯一) | 订单状态、支付金额、支付时间 | 聚合根 |
| 商户(Merchant) | merchantId | 结算费率、API 密钥 | 聚合根(独立聚合) |
| 支付渠道(PaymentChannel) | channelId | 渠道状态、可用额度 | 实体(内部或外部) |
| 退款记录(RefundRecord) | refundId | 退款状态、退款金额 | 实体(支付订单聚合内) |
关键原则:支付订单的
paymentOrderId 必须在整个生命周期内保持不变。即使订单状态从“待支付”变为“已支付”,其 ID 依然唯一标识该笔交易。这是幂等处理的基础——通过 ID 识别同一笔请求。
2.2 值对象:描述属性,无唯一标识
值对象通过属性值来定义,两个值对象如果所有属性相同,则视为相等。它们通常是不可变的,用于描述实体的特征或业务概念。
- 金额(Money):包含金额数值和币种(如
amount: 100.00, currency: "CNY")。两个金额对象如果数值和币种都相同,则相等。 - 支付方式(PaymentMethod):描述支付工具类型(如
type: "WECHAT_PAY", account: "wx_12345")。 - 地址(Address):包含省、市、区、详细地址等属性,用于订单配送或结算。
- 时间范围(TimeRange):如
startTime: "2025-01-01 00:00:00", endTime: "2025-01-31 23:59:59",用于优惠活动有效期。 - 幂等键(IdempotentKey):由调用方生成的唯一请求标识,作为值对象附加在支付请求中。
建模建议:将
Money 设计为值对象,可以避免金额计算中的精度丢失和币种混淆。在支付订单实体中,totalAmount 字段的类型应为 Money 而非原始数字。
2.3 聚合:事务一致性的边界
聚合是一组相关对象的集合,外部只能通过聚合根(Aggregate Root)访问内部对象。聚合边界内的所有修改必须在一次事务中完成,保证强一致性。
在支付订单领域,典型的聚合设计如下:
2.4 聚合设计原则在支付场景中的应用
支付订单聚合的设计需遵循以下原则,以确保数据一致性和系统可维护性:
- 聚合根唯一入口:所有对支付订单的修改(如支付、退款、关闭)必须通过
PaymentOrder聚合根的方法执行,不允许直接操作内部实体或值对象。 - 事务边界最小化:一次支付操作只修改一个聚合实例。例如,支付成功时只更新
PaymentOrder的状态,不直接更新商户的余额(通过领域事件异步处理)。 - 引用外部 ID:聚合内部只存储外部聚合的 ID(如
merchantId),而非整个对象。这避免了跨聚合的事务耦合。 - 幂等性内置:聚合根应提供幂等操作方法,接收
IdempotentKey值对象作为参数,在聚合内部检查是否已处理。
常见误区:将支付订单、商户、渠道放在同一个大聚合中。这会导致每次支付操作都需要加载商户和渠道的全部数据,降低并发性能,且容易引发跨聚合的事务冲突。正确的做法是保持聚合独立,通过领域事件或最终一致性协调。
2.5 概念映射总结
下表总结了 DDD 核心概念在支付订单领域的具体映射:
| DDD 概念 | 支付领域映射 | 关键特征 | 示例 |
|---|---|---|---|
| 聚合根 | 支付订单 | 全局唯一 ID,事务边界入口 | PaymentOrder |
| 实体 | 退款记录、支付通知 | 有 ID,状态可变 | RefundRecord |
| 值对象 | 金额、支付方式、幂等键 | 无 ID,不可变,按值相等 | Money, IdempotentKey |
| 领域服务 | 支付路由、风控校验 | 无状态,处理跨聚合逻辑 | PaymentRoutingService |
| 领域事件 | 支付成功事件、退款完成事件 | 记录已发生的事实,异步处理 | PaymentSucceededEvent |