4. 订单聚合设计(上):订单聚合根(Order)与订单项(OrderItem)的实体建模

在支付订单系统中,订单聚合是整个领域模型的核心。本章将聚焦于订单聚合根 Order 与订单项 OrderItem 的实体建模,明确两者的职责边界、生命周期与持久化映射关系。

4.1 聚合设计原则回顾

根据 Evans 的 DDD 定义,聚合是一组相关对象的集合,外部只能通过聚合根访问内部实体。订单聚合的设计需遵循以下原则:

原则 说明 订单聚合中的应用
一致性边界 聚合内所有对象在事务中保持一致 订单状态变更时,所有订单项必须同步锁定
聚合根唯一引用 外部只能通过聚合根 ID 引用聚合 外部服务只持有 orderId,不直接引用 OrderItem
内部实体本地标识 内部实体在聚合内唯一即可 OrderItem 使用 orderId + itemSeq 作为本地标识
最小化聚合规模 避免加载过多无关对象 订单项数量通常 ≤ 50,超过则拆分为子订单
实践提示:订单聚合的边界决定了事务粒度。将订单项纳入订单聚合,意味着一次订单提交操作必须保证所有订单项的状态一致,不可出现部分成功部分失败的情况。

4.2 订单聚合根(Order)实体建模

Order 作为聚合根,承担着对外交互、生命周期管理和一致性保障的核心职责。其属性设计应聚焦于订单全局信息,而非商品明细。

4.2.1 核心属性

属性 类型 说明
orderId OrderId(值对象) 全局唯一标识,由订单号生成服务分配
buyerId BuyerId(值对象) 买家标识,关联用户上下文
sellerId SellerId(值对象) 卖家标识,关联商户上下文
orderStatus OrderStatus(枚举) 待支付、已支付、已取消、已退款等
totalAmount Money(值对象) 订单总金额,由订单项金额汇总计算
paymentAmount Money(值对象) 实际支付金额,可能包含优惠分摊
createdAt DateTime 订单创建时间
version int 乐观锁版本号,用于并发控制

4.2.2 聚合根行为

  • 创建订单:接收 CreateOrderCommand,校验买家/卖家合法性,生成订单项列表,计算总金额。
  • 提交支付:校验订单状态为待支付,锁定订单项库存,更新状态为已支付。
  • 取消订单:校验订单状态为待支付,释放库存,更新状态为已取消。
  • 申请退款:校验订单状态为已支付,生成退款请求,更新状态为退款中。
关键约束:聚合根的所有状态变更必须通过显式方法完成,禁止直接修改属性。例如:order.submitPayment() 而非 order.setOrderStatus()。

4.3 订单项(OrderItem)实体建模

OrderItem 是订单聚合的内部实体,代表一笔订单中的单个商品购买记录。它不对外暴露,仅通过 Order 聚合根访问。

4.3.1 核心属性

属性 类型 说明
itemSeq int 订单内序号,从 1 开始递增
productId ProductId(值对象) 商品标识,关联商品上下文
skuId SkuId(值对象) SKU 标识,关联库存上下文
quantity int 购买数量,必须大于 0
unitPrice Money(值对象) 商品单价
subtotal Money(值对象) 小计金额 = 单价 × 数量
itemStatus ItemStatus(枚举) 正常、已退款、部分退款

4.3.2 实体行为

  • 计算小计:根据单价和数量自动计算,不允许外部直接设置。
  • 锁定库存:在订单提交支付时,由聚合根统一调用每个订单项的库存锁定方法。
  • 标记退款:当发生退款时,更新 itemStatus 为已退款或部分退款。
注意:订单项不应包含商品描述、图片 URL 等展示信息。这些信息属于商品上下文,订单项仅持有 productId 和 skuId,通过领域服务或读模型获取展示数据。

4.4 订单聚合结构图

下图展示了订单聚合的内部结构,以及聚合根与内部实体、值对象之间的关系:

订单聚合结构图 聚合根:Order orderId, buyerId, sellerId orderStatus, totalAmount, version 内部实体:OrderItem itemSeq, productId, skuId quantity, unitPrice, subtotal 内部实体:OrderItem itemSeq, productId, skuId quantity, unitPrice, subtotal 内部实体:OrderItem itemSeq, productId, skuId quantity, unitPrice, subtotal 值对象:OrderId 全局唯一标识 值对象:Money 金额 + 币种 枚举:OrderStatus 待支付/已支付/已取消 说明:OrderItem 仅在 Order 聚合内存在,外部通过 orderId 引用整个聚合 值对象和枚举不可变,实体通过聚合根方法统一变更状态

4.5 实体与值对象的代码示例

以下代码展示了 Order 聚合根和 OrderItem 实体的核心实现,采用 Java 语言描述:

// 订单聚合根
public class Order {
    private OrderId orderId;
    private BuyerId buyerId;
    private SellerId sellerId;
    private OrderStatus orderStatus;
    private Money totalAmount;
    private Money paymentAmount;
    private LocalDateTime createdAt;
    private int version;
    private List<OrderItem> items;

    // 聚合根行为:提交支付
    public void submitPayment(Money amount) {
        if (this.orderStatus != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalStateException("订单状态不允许支付");
        }
        // 校验支付金额
        if (!this.totalAmount.equals(amount)) {
            throw new IllegalArgumentException("支付金额与订单总金额不一致");
        }
        // 锁定所有订单项库存
        for (OrderItem item : this.items) {
            item.lockInventory();
        }
        this.orderStatus = OrderStatus.PAID;
        this.paymentAmount = amount;
    }

    // 聚合根行为:取消订单
    public void cancel() {
        if (this.orderStatus != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalStateException("订单状态不允许取消");
        }
        for (OrderItem item : this.items) {
            item.releaseInventory();
        }
        this.orderStatus = OrderStatus.CANCELLED;
    }
}

// 订单项实体(内部实体)
public class OrderItem {
    private int itemSeq;
    private ProductId productId;
    private SkuId skuId;
    private int quantity;
    private Money unitPrice;
    private Money subtotal;
    private ItemStatus itemStatus;

    // 计算小计
    public void calculateSubtotal() {
        this.subtotal = this.unitPrice.multiply(this.quantity);
    }

    // 锁定库存
    public void lockInventory() {
        // 调用库存上下文服务
        InventoryService.lock(this.skuId, this.quantity);
    }

    // 释放库存
    public void releaseInventory() {
        InventoryService.release(this.skuId, this.quantity);
    }
}

4.6 持久化映射策略

订单聚合在持久化时,通常采用以下映射方式:

数据库表 映射对象 主键 说明
orders Order 聚合根 order_id 存储订单全局信息
order_items OrderItem 实体 order_id + item_seq 存储订单项明细,外键关联 orders 表
实践建议:使用 ORM 框架(如 JPA/Hibernate)时,将 OrderItem 的 @ManyToOne 关联设置为 cascade = ALL 和 orphanRemoval = true,确保聚合内实体生命周期与聚合根一致。

4.7 本章小结

  • 订单聚合根负责全局状态管理和一致性边界,外部通过 orderId 引用。
  • 订单项实体是聚合内部对象,持有商品标识和数量信息,不对外暴露。
  • 值对象如 Money、OrderId 提供不可变的值语义,增强模型表达力。
  • 持久化映射采用主表 + 明细表结构,通过外键维护聚合完整性。