4. 订单聚合设计:订单聚合根(Order)的职责、状态机与生命周期管理
在支付领域驱动设计中,订单聚合(Order Aggregate)是核心战术构件。聚合根 Order 承担着维护订单一致性、执行业务规则、管理状态变迁的职责。本章将深入剖析订单聚合根的边界、状态机设计以及完整的生命周期管理策略。
4.1 聚合根(Order)的职责边界
聚合根是聚合中唯一能被外部访问的入口,Order 聚合根必须确保其内部所有实体和值对象在任何业务操作后都满足不变量。其核心职责包括:
- 一致性守卫:保证订单金额、状态、支付方式等字段在事务边界内同时更新。
- 业务规则执行:如“已支付订单不可修改商品数量”、“退款金额不得超过实付金额”。
- 子实体管理:创建、删除或修改
OrderItem、PaymentRecord等子实体。 - 事件发布:在状态变更时发布领域事件(如
OrderPaid、OrderRefunded)。
设计原则:聚合根应保持精简,只包含与订单核心生命周期强相关的属性。将“物流信息”、“发票信息”等扩展属性拆分为独立聚合,通过订单ID关联,避免聚合膨胀。
4.2 订单状态机设计
订单状态机是聚合根行为建模的核心。一个健壮的状态机应明确每个状态允许的操作、状态间的合法转换路径以及转换时的副作用。以下是支付订单的标准状态定义:
| 状态 | 编码 | 描述 | 允许的操作 |
|---|---|---|---|
| 待支付 | PENDING_PAYMENT | 订单已创建,等待用户支付 | 支付、取消、修改商品 |
| 支付中 | PAYING | 支付请求已发出,等待支付网关回调 | 支付结果通知、超时关闭 |
| 已支付 | PAID | 支付成功,资金已冻结 | 发起退款、确认发货 |
| 已退款 | REFUNDED | 订单已全额退款 | 无(终态) |
| 已取消 | CANCELLED | 用户主动取消或超时取消 | 无(终态) |
| 已完成 | COMPLETED | 交易成功,资金已结算 | 无(终态) |
状态转换必须遵循严格的有限状态机(FSM)规则,不允许跳过中间状态或逆向流转。例如:从“已支付”不能直接回到“待支付”,只能通过“退款”流程进入“已退款”。
实践建议:在 Go 中实现状态机时,优先使用组合模式或状态模式,避免在聚合根内部使用大量的
if-else 判断。将状态转换逻辑封装在独立的 OrderStateMachine 值对象中,提升可测试性。
4.3 生命周期管理
订单聚合根的生命周期从创建开始,经过一系列状态转换,最终到达终态。生命周期管理需要关注以下关键阶段:
- 创建阶段:通过工厂方法
Order.Create()初始化聚合根,校验商品库存、价格、用户身份等前置条件。 - 支付阶段:处理支付请求、接收支付网关回调、更新支付状态并发布
OrderPaidEvent。 - 履约阶段:确认发货、完成交易,触发结算流程。
- 退款/取消阶段:执行退款逻辑,释放库存,更新状态为终态。
避坑指南:不要在聚合根内部直接调用外部基础设施(如数据库、消息队列)。应通过仓储接口持久化聚合根,通过领域事件发布器发送事件。聚合根只关注业务逻辑,不关心技术实现。
4.4 聚合根代码骨架(Go 实现)
以下是一个简化但完整的 Order 聚合根 Go 代码骨架,展示了状态机与生命周期管理的关键模式:
package order
import (
"errors"
"time"
)
// OrderStatus 订单状态枚举
type OrderStatus string
const (
StatusPendingPayment OrderStatus = "PENDING_PAYMENT"
StatusPaying OrderStatus = "PAYING"
StatusPaid OrderStatus = "PAID"
StatusRefunded OrderStatus = "REFUNDED"
StatusCancelled OrderStatus = "CANCELLED"
StatusCompleted OrderStatus = "COMPLETED"
)
// Order 聚合根
type Order struct {
ID string
UserID string
TotalAmount int64
Status OrderStatus
Items []OrderItem
CreatedAt time.Time
UpdatedAt time.Time
events []DomainEvent // 领域事件列表
}
// OrderItem 订单项值对象
type OrderItem struct {
ProductID string
Quantity int
Price int64
}
// DomainEvent 领域事件接口
type DomainEvent interface{}
// NewOrder 工厂方法:创建订单聚合根
func NewOrder(id, userID string, items []OrderItem) (*Order, error) {
if len(items) == 0 {
return nil, errors.New("order must have at least one item")
}
total := int64(0)
for _, item := range items {
total += item.Price * int64(item.Quantity)
}
o := &Order{
ID: id,
UserID: userID,
TotalAmount: total,
Status: StatusPendingPayment,
Items: items,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
o.addEvent(OrderCreatedEvent{OrderID: id, Amount: total})
return o, nil
}
// MarkPaid 标记订单为已支付
func (o *Order) MarkPaid(payTime time.Time) error {
if o.Status != StatusPaying {
return errors.New("order is not in paying status")
}
o.Status = StatusPaid
o.UpdatedAt = payTime
o.addEvent(OrderPaidEvent{OrderID: o.ID, PaidAt: payTime})
return nil
}
// RequestRefund 发起退款
func (o *Order) RequestRefund() error {
if o.Status != StatusPaid {
return errors.New("only paid orders can be refunded")
}
o.Status = StatusRefunded
o.UpdatedAt = time.Now()
o.addEvent(OrderRefundedEvent{OrderID: o.ID})
return nil
}
// Cancel 取消订单
func (o *Order) Cancel() error {
if o.Status != StatusPendingPayment {
return errors.New("only pending payment orders can be cancelled")
}
o.Status = StatusCancelled
o.UpdatedAt = time.Now()
o.addEvent(OrderCancelledEvent{OrderID: o.ID})
return nil
}
// addEvent 内部方法:添加领域事件
func (o *Order) addEvent(event DomainEvent) {
o.events = append(o.events, event)
}
// Events 获取并清空领域事件(供仓储使用)
func (o *Order) Events() []DomainEvent {
events := o.events
o.events = nil
return events
}
代码要点:
- 所有状态变更方法(
MarkPaid、RequestRefund、Cancel)都先校验当前状态,确保状态机规则不被破坏。 - 领域事件通过
addEvent收集,由仓储在保存聚合根后统一发布,保证事件与数据持久化的事务一致性。 - 工厂方法
NewOrder确保创建时所有不变量(如商品数量、金额计算)得到满足。
4.5 生命周期中的事件驱动
订单聚合根的生命周期管理离不开领域事件的驱动。每个状态转换都应发布对应的事件,供其他限界上下文(如库存、通知、结算)订阅和处理。典型的事件流如下:
4.6 生命周期管理最佳实践
- 显式状态机:将状态转换规则集中定义,避免散落在各个方法中。可以使用状态转换表或状态模式。
- 事件溯源可选:对于高合规性支付系统,可考虑事件溯源(Event Sourcing)存储订单事件流,而非仅存储当前状态。
- 超时处理:为“待支付”和“支付中”状态设置超时定时器,超时后自动触发取消或关闭操作。
- 幂等性:支付回调、退款请求等外部操作必须支持幂等,防止重复处理导致状态错乱。