3. 支付订单聚合设计:聚合根、状态机与生命周期
在支付领域,订单聚合是承载交易核心状态与行为的实体。本章围绕聚合根设计、状态机与生命周期三个维度,构建一个可落地、可验证的支付订单聚合模型。以下内容将直接映射到后续代码实现与领域事件设计。
3.1 聚合根设计原则
支付订单聚合根(Order Aggregate Root)是领域模型的入口,负责维护订单内部一致性。设计时需遵循以下原则:
- 唯一标识:每个聚合根通过
OrderId(值对象)全局唯一标识,不可变。 - 内部一致性:所有订单状态变更必须经过聚合根方法,不允许外部直接修改子实体。
- 边界明确:聚合内只包含订单主表、支付记录、退款记录等强一致性实体;外部关联(如用户、商品)通过 ID 引用。
- 事务范围:一次聚合根操作对应一个工作单元,保证原子性。
| 聚合元素 | 类型 | 说明 |
|---|---|---|
| OrderId | 值对象 | 全局唯一订单号,包含时间戳+业务标识+随机数 |
| OrderRoot | 聚合根 | 持有订单状态、金额、支付方式、生命周期方法 |
| PaymentRecord | 实体 | 每次支付尝试的记录,包含支付渠道、流水号、结果 |
| RefundRecord | 实体 | 退款记录,关联原支付记录,支持部分退款 |
| OrderItem | 值对象 | 订单行项目,包含商品ID、数量、单价 |
设计提示:聚合根不宜过大。如果订单需要频繁查询商品详情,应将商品信息拆出为独立聚合,通过领域服务协调。
3.2 订单状态机
支付订单状态机是领域行为的核心,它定义了订单从创建到完成的合法状态转换路径。状态机必须满足:确定性(每个事件只产生一个目标状态)、可追溯(每次转换记录事件与时间戳)、防重入(已终态不可回退)。
以下为支付订单的核心状态与转换事件:
| 当前状态 | 触发事件 | 目标状态 | 约束条件 |
|---|---|---|---|
| 待支付 | 支付发起 | 支付中 | 订单未超时,金额>0 |
| 支付中 | 支付成功回调 | 已支付 | 渠道流水号唯一,金额匹配 |
| 支付中 | 支付失败回调 | 待支付 | 重试次数未超限 |
| 支付中 | 超时未回调 | 已关闭 | 超过支付有效期 |
| 已支付 | 发起退款 | 退款中 | 退款金额≤可退金额 |
| 退款中 | 退款成功回调 | 已退款 | 退款流水号唯一 |
| 退款中 | 退款失败回调 | 已支付 | 可重新发起退款 |
| 已支付 / 已退款 | 订单完成 | 已完成 | 所有子项履约完毕 |
常见陷阱:不要将“支付中”视为临时状态而忽略持久化。支付中状态可能持续数秒到数分钟,期间需要支持查询、超时处理、重试等操作,必须作为正式状态纳入状态机。
3.3 订单生命周期
订单生命周期描述了订单从创建到最终归档的完整过程。每个阶段都有明确的领域行为、事件与数据约束。生命周期分为以下阶段:
- 创建阶段:聚合根初始化,状态为“待支付”,生成订单ID,记录创建时间与过期时间。
- 支付阶段:接收支付指令,创建支付记录实体,调用支付网关,等待异步回调。支持多次支付尝试(重试)。
- 履约阶段:支付成功后,订单进入“已支付”,触发后续履约流程(如发货、服务开通)。
- 退款阶段:支持全额或部分退款,每次退款生成独立的退款记录,状态迁移至“退款中”直至“已退款”。
- 完成/关闭阶段:所有子项履约完毕进入“已完成”;超时未支付或用户主动取消进入“已关闭”。
- 归档阶段:终态订单超过保留期后,迁移至历史表或冷存储,聚合根生命周期结束。
生命周期事件示例:每个阶段转换都应发布领域事件,例如
OrderCreated、OrderPaid、OrderRefunded、OrderCompleted。这些事件是后续分析与补偿的基础。
3.4 聚合根代码骨架
以下为 Go 语言实现的支付订单聚合根核心结构与方法,展示状态机与生命周期的代码映射:
// OrderRoot 支付订单聚合根
type OrderRoot struct {
ID OrderID
Status OrderStatus
TotalAmount Money
PaidAmount Money
RefundedAmount Money
Items []OrderItem
Payments []*PaymentRecord
Refunds []*RefundRecord
CreatedAt time.Time
UpdatedAt time.Time
ExpireAt time.Time
}
// PlaceOrder 创建订单(聚合根工厂方法)
func PlaceOrder(items []OrderItem, expireDuration time.Duration) (*OrderRoot, error) {
if len(items) == 0 {
return nil, ErrEmptyOrder
}
total := calculateTotal(items)
id := NewOrderID()
now := time.Now()
return &OrderRoot{
ID: id,
Status: OrderStatusPending,
TotalAmount: total,
Items: items,
CreatedAt: now,
UpdatedAt: now,
ExpireAt: now.Add(expireDuration),
}, nil
}
// StartPayment 发起支付(状态机转换:待支付 → 支付中)
func (o *OrderRoot) StartPayment(payMethod PaymentMethod) (*PaymentRecord, error) {
if o.Status != OrderStatusPending {
return nil, ErrInvalidStatus
}
if time.Now().After(o.ExpireAt) {
o.Status = OrderStatusClosed
return nil, ErrOrderExpired
}
record := NewPaymentRecord(o.ID, payMethod, o.TotalAmount)
o.Payments = append(o.Payments, record)
o.Status = OrderStatusPaying
o.UpdatedAt = time.Now()
return record, nil
}
// ConfirmPayment 确认支付回调(状态机转换:支付中 → 已支付)
func (o *OrderRoot) ConfirmPayment(paymentID string, channelTradeNo string) error {
if o.Status != OrderStatusPaying {
return ErrInvalidStatus
}
record := o.findPayment(paymentID)
if record == nil {
return ErrPaymentNotFound
}
record.Confirm(channelTradeNo)
o.Status = OrderStatusPaid
o.PaidAmount = o.TotalAmount
o.UpdatedAt = time.Now()
return nil
}
// RequestRefund 发起退款(状态机转换:已支付 → 退款中)
func (o *OrderRoot) RequestRefund(amount Money, reason string) (*RefundRecord, error) {
if o.Status != OrderStatusPaid {
return nil, ErrInvalidStatus
}
refundable := o.TotalAmount.Sub(o.RefundedAmount)
if amount.GreaterThan(refundable) {
return nil, ErrRefundExceed
}
record := NewRefundRecord(o.ID, amount, reason)
o.Refunds = append(o.Refunds, record)
o.Status = OrderStatusRefunding
o.UpdatedAt = time.Now()
return record, nil
}
实现要点:聚合根方法必须返回明确的错误类型,调用方根据错误决定是否重试或补偿。状态转换前先校验当前状态,避免非法跳转。
3.5 状态机与事件溯源
在支付场景中,状态机与事件溯源(Event Sourcing)结合可以显著提升可追溯性与审计能力。每次状态转换都生成一条领域事件,事件存储作为订单状态的唯一真相源。以下为事件与状态的对应关系:
| 领域事件 | 源状态 | 目标状态 | 事件载荷 |
|---|---|---|---|
| OrderCreated | — | 待支付 | 订单ID、金额、商品列表、过期时间 |
| PaymentStarted | 待支付 | 支付中 | 支付记录ID、支付方式、金额 |
| PaymentSucceeded | 支付中 | 已支付 | 支付记录ID、渠道流水号、支付时间 |
| PaymentFailed | 支付中 | 待支付 | 支付记录ID、失败原因、可重试次数 |
| RefundRequested | 已支付 | 退款中 | 退款记录ID、退款金额、原因 |
| RefundSucceeded | 退款中 | 已退款 | 退款记录ID、渠道退款流水号 |
| OrderCompleted | 已支付/已退款 | 已完成 | 完成时间、履约结果 |
| OrderClosed | 待支付/支付中 | 已关闭 | 关闭原因(超时/用户取消) |
事件溯源注意事项:事件结构一旦发布应避免修改;如需扩展字段,使用新版本事件并保留兼容性。事件存储建议使用独立数据库或消息队列,避免与业务数据库耦合。
3.6 本章小结
- 聚合根设计强调边界明确与内部一致性,支付订单聚合根包含订单主实体、支付记录与退款记录。
- 状态机定义了8个核心状态与8条合法转换路径,确保状态迁移的确定性与可追溯性。
- 生命周期分为创建、支付、履约、退款、完成/关闭、归档六个阶段,每个阶段对应明确的领域行为与事件。
- 代码骨架展示了聚合根方法如何封装状态机逻辑,外部只能通过聚合根方法改变状态。
- 事件溯源可作为状态机的增强方案,提供完整的审计链路与事件重放能力。