3. 支付订单聚合根设计:订单状态机与核心行为建模
支付订单聚合根是支付域的核心实体,它封装了订单的生命周期、状态流转与核心业务逻辑。在领域驱动设计中,聚合根是保证数据一致性和业务规则正确执行的边界。本章将深入设计支付订单的状态机,并建模其核心行为,为后续的幂等处理与防重策略奠定基础。
3.1 支付订单状态机设计
支付订单的状态机定义了订单从创建到最终完成的全部合法状态及转换路径。一个健壮的状态机能够防止非法状态跃迁,确保业务逻辑的严谨性。
设计原则: 状态转换必须由聚合根内的行为方法驱动,不允许外部直接修改状态字段。所有状态变更需伴随领域事件的发布。
3.1.1 核心状态定义
| 状态名称 | 状态码 | 描述 | 是否终态 |
|---|---|---|---|
| 待支付 | PENDING | 订单已创建,等待用户发起支付 | 否 |
| 支付处理中 | PROCESSING | 已收到支付请求,正在与第三方渠道交互 | 否 |
| 支付成功 | SUCCESS | 支付渠道确认资金已成功扣款 | 是 |
| 支付失败 | FAILED | 支付渠道返回明确失败原因 | 是 |
| 已退款 | REFUNDED | 订单已完成全额退款 | 是 |
| 已关闭 | CLOSED | 订单超时未支付或用户主动取消 | 是 |
3.1.2 状态转换图
下图展示了支付订单的核心状态机,箭头表示合法的状态迁移路径。每个迁移都对应一个聚合根行为方法。
3.2 聚合根核心行为建模
聚合根的行为方法封装了业务逻辑,并确保每次状态变更都经过校验。以下是支付订单聚合根的核心行为定义。
3.2.1 核心行为清单
| 行为方法 | 触发条件 | 前置状态 | 后置状态 | 领域事件 |
|---|---|---|---|---|
createOrder() |
用户提交支付请求 | 无 | PENDING | OrderCreatedEvent |
startPayment() |
用户确认支付 | PENDING | PROCESSING | PaymentStartedEvent |
markSuccess() |
收到渠道成功回调 | PROCESSING | SUCCESS | PaymentSucceededEvent |
markFailed() |
收到渠道失败回调 | PROCESSING | FAILED | PaymentFailedEvent |
closeOrder() |
超时或用户取消 | PENDING | CLOSED | OrderClosedEvent |
refund() |
发起退款请求 | SUCCESS | REFUNDED | OrderRefundedEvent |
3.2.2 行为实现示例(伪代码)
以下代码展示了聚合根中 markSuccess() 方法的典型实现,包含状态校验、幂等处理和事件发布。
public class PaymentOrder extends AggregateRoot {
private OrderId orderId;
private OrderStatus status;
private Money amount;
private PaymentId paymentId;
private List<DomainEvent> events = new ArrayList<>();
// 标记支付成功
public void markSuccess(PaymentResult result) {
// 1. 状态校验:仅允许从 PROCESSING 转换
if (this.status != OrderStatus.PROCESSING) {
throw new IllegalStateException(
"当前状态不允许标记成功: " + this.status);
}
// 2. 幂等校验:如果已成功,直接返回(防重)
if (this.status == OrderStatus.SUCCESS) {
return;
}
// 3. 更新状态
this.status = OrderStatus.SUCCESS;
this.paymentId = result.getPaymentId();
// 4. 发布领域事件
PaymentSucceededEvent event = new PaymentSucceededEvent(
this.orderId, this.paymentId, result.getPaidAt());
this.events.add(event);
}
// 其他行为方法类似...
}
关键设计点:
- 状态校验前置: 每个行为方法首先检查当前状态是否允许转换,防止非法操作。
- 幂等返回: 对于已处理过的请求(如重复回调),直接返回而不报错,保证接口幂等性。
- 事件驱动: 状态变更后立即发布领域事件,供其他限界上下文(如账务、通知)订阅处理。
3.3 状态机与幂等处理的集成
在支付场景中,渠道回调可能因网络重试等原因重复到达。聚合根的状态机天然支持幂等:如果当前状态已经是目标状态(如 SUCCESS),则直接忽略重复请求。这种设计避免了在应用层做额外的去重判断,将幂等逻辑内聚在领域层。
3.3.1 幂等处理流程
- 接收回调: 渠道回调到达应用服务。
- 加载聚合根: 根据订单 ID 从仓储加载
PaymentOrder聚合根。 - 调用行为: 调用
markSuccess()或markFailed()。 - 状态判断: 聚合根内部判断当前状态是否已为目标状态。
- 幂等返回: 如果已处理,直接返回成功,不产生副作用。
- 保存聚合根: 如果状态变更,仓储保存新状态并发布事件。
注意: 聚合根内部的幂等判断依赖于正确的状态初始化。如果仓储返回的聚合根状态不是最新的(例如缓存未更新),可能导致重复处理。因此,在加载聚合根时,应确保从数据库或缓存中获取最新状态。
3.4 聚合根与 Redis 防重策略的协作
虽然聚合根内部实现了幂等,但在高并发场景下,仍可能出现多个线程同时加载同一聚合根并尝试修改的情况。此时需要借助外部锁机制(如 Redis 分布式锁)来保证并发安全。
下图展示了聚合根与 Redis 防重策略的协作流程。
最佳实践: Redis 锁的 key 建议使用
payment:order:lock:{orderId} 格式,锁的超时时间应大于业务执行时间(通常 3-5 秒),并配合 Redisson 等客户端实现自动续期,防止死锁。
3.5 本章小结
- 支付订单聚合根通过状态机严格管理订单生命周期,每个状态转换由聚合根行为方法驱动。
- 聚合根内部实现幂等逻辑,对重复请求直接返回,保证业务正确性。
- 在并发场景下,结合 Redis 分布式锁 防止多个线程同时修改同一聚合根。
- 状态变更后发布领域事件,实现与其他限界上下文的解耦。