4. 订单上下文(Order Context)分析

订单上下文是支付订单领域聚合建模的核心子域,承载着订单从创建到终态的全生命周期管理。本节将深入分析订单的状态流转、核心业务规则以及聚合边界划分,为后续的服务边界与通信机制设计奠定基础。

4.1 订单生命周期状态机

支付订单的状态变迁遵循严格的有限状态机(FSM)模型,每个状态转换都对应明确的业务操作与领域事件。以下是订单的核心状态定义与转换规则:

状态 状态码 描述 允许的下游状态
创建 CREATED 订单已生成,但尚未发起支付 待支付、已取消
待支付 PENDING 已发起支付请求,等待支付网关回调 已支付、已取消
已支付 PAID 支付成功,资金已冻结或到账 已退款、部分退款
已取消 CANCELLED 订单被用户或系统取消,未产生资金变动 终态
已退款 REFUNDED 全额退款完成,资金已退回用户 终态
部分退款 PARTIAL_REFUND 部分金额已退款,剩余金额仍有效 已退款
关键约束: 订单状态转换必须满足幂等性与顺序一致性。例如,已取消的订单不可再转为待支付;已退款的订单不可重复退款。状态机应通过领域层守卫(Guard)强制执行。
订单状态机流转图 创建 待支付 已支付 已取消 已退款 部分退款 说明: • 已取消为终态,不可再流转到其他状态。 • 部分退款可继续退款至全额退款,但不可逆向回到已支付。

4.2 核心业务规则

订单上下文必须强制执行以下业务规则,以保证资金安全与数据一致性:

  • 金额一致性规则: 订单总金额必须等于所有订单项金额之和,且不得为负数或零(除零元体验单外)。
  • 支付超时规则: 待支付订单超过指定时间(如30分钟)未完成支付,系统应自动取消订单并释放库存。
  • 取消前置规则: 只有处于创建或待支付状态的订单允许取消;已支付订单必须走退款流程。
  • 退款金额约束: 累计退款金额不得超过订单实际支付金额,部分退款时剩余金额必须大于零。
  • 幂等性规则: 同一支付请求或退款请求重复提交时,系统应返回相同结果,不产生重复扣款或重复退款。
实践建议: 在订单聚合根中维护一个 version 字段(乐观锁),每次状态变更时校验版本号,可有效防止并发场景下的状态覆盖问题。

4.3 订单聚合边界与实体划分

在订单上下文中,聚合(Aggregate)的边界划分直接影响事务一致性与性能。推荐将以下实体组织在同一个订单聚合内:

实体/值对象 类型 职责
Order(订单) 聚合根 持有订单全局标识、状态、金额、用户ID,负责状态变更的守卫逻辑
OrderItem(订单项) 实体 描述单个商品/服务的购买信息,包含商品ID、单价、数量、小计
PaymentRecord(支付记录) 实体 记录每次支付请求的流水,包括支付渠道、交易号、金额、时间
RefundRecord(退款记录) 实体 记录每次退款操作的流水,包括退款原因、金额、渠道返回信息
OrderAddress(订单地址) 值对象 收货地址的快照,不可变,随订单创建时生成
边界警告: 不要将支付网关的会话信息(如 token、sessionId)放入订单聚合内。这些属于支付上下文(Payment Context)的职责,跨聚合引用会导致强耦合与性能瓶颈。
订单聚合边界与实体关系图 订单聚合边界 Order(聚合根) orderId, status, totalAmount OrderItem 商品ID, 单价, 数量 PaymentRecord 渠道, 交易号, 金额 RefundRecord 退款原因, 金额 地址 值对象 说明:聚合根 Order 持有对所有子实体的引用,所有外部访问必须通过聚合根入口,保证数据一致性。 子实体不能单独被外部仓储直接访问,只能通过 Order 聚合根加载。

4.4 领域事件与跨上下文通信

订单上下文通过领域事件(Domain Event)与其他上下文解耦通信。关键事件包括:

  • OrderCreated — 订单创建成功后发布,触发库存预留、优惠券锁定等后续流程。
  • OrderPaid — 支付成功后发布,触发发货、积分赠送等流程。
  • OrderCancelled — 订单取消后发布,触发库存释放、优惠券回滚。
  • OrderRefunded — 退款完成后发布,触发财务对账、用户通知。
事件驱动设计要点: 领域事件应包含事件ID、发生时间、订单ID、事件类型以及必要的业务载荷(如退款金额)。事件发布应采用事务性发件箱模式(Transactional Outbox),确保事件不会因本地事务回滚而丢失。

4.5 关键代码示例:订单状态变更守卫

以下代码展示了订单聚合根中状态变更的守卫逻辑,确保状态转换的合法性:

public class Order : AggregateRoot
{
    public OrderId Id { get; private set; }
    public OrderStatus Status { get; private set; }
    public Money TotalAmount { get; private set; }
    private List<OrderItem> _items;
    private List<PaymentRecord> _payments;
    private List<RefundRecord> _refunds;

    // 状态转换映射表
    private static readonly Dictionary<OrderStatus, HashSet<OrderStatus>> _allowedTransitions = 
        new()
    {
        { OrderStatus.Created, new() { OrderStatus.Pending, OrderStatus.Cancelled } },
        { OrderStatus.Pending, new() { OrderStatus.Paid, OrderStatus.Cancelled } },
        { OrderStatus.Paid, new() { OrderStatus.Refunded, OrderStatus.PartialRefund } },
        { OrderStatus.PartialRefund, new() { OrderStatus.Refunded } },
        { OrderStatus.Cancelled, new() },
        { OrderStatus.Refunded, new() }
    };

    public void TransitionTo(OrderStatus newStatus)
    {
        if (!_allowedTransitions[Status].Contains(newStatus))
            throw new DomainException($"不允许从 {Status} 转换到 {newStatus}");

        var previousStatus = Status;
        Status = newStatus;

        // 发布领域事件
        AddDomainEvent(new OrderStatusChangedEvent(Id, previousStatus, newStatus));
    }

    public void Cancel()
    {
        TransitionTo(OrderStatus.Cancelled);
        AddDomainEvent(new OrderCancelledEvent(Id));
    }

    public void MarkPaid()
    {
        TransitionTo(OrderStatus.Paid);
        AddDomainEvent(new OrderPaidEvent(Id));
    }
}
设计要点: 状态转换守卫逻辑放在聚合根内部,确保任何外部调用都无法绕过业务规则直接修改状态。领域事件在状态变更后立即发布,由事件总线异步分发到其他上下文。