4. 识别订单上下文中的聚合根与聚合边界
从订单业务规则反推聚合边界
在订单上下文中,聚合根是外部访问聚合内部对象的唯一入口,聚合边界则决定哪些对象必须在同一次事务中保持一致。判断聚合根时,先问三个问题:谁负责维护整体不变量?谁被外部直接引用?谁的生命周期由谁控制?
以订单为例,订单行(OrderLine)不能脱离订单独立存在,收货地址(ShippingAddress)在订单提交后通常作为快照值对象保存。因此,Order 是聚合根,OrderLine 是聚合内部实体,ShippingAddress 是聚合内值对象。外部只能通过 Order 修改订单行或地址,不能绕过 Order 直接操作 OrderLine。
一致性边界与事务边界的取舍
聚合内的一致性由单次事务保证,聚合间的一致性通过领域事件或最终一致性实现。订单聚合内需要强一致的内容包括:订单总金额 = 所有订单行小计之和;订单行数量必须大于 0;订单状态与订单行修改的约束关系。而库存扣减、支付记录、积分累计等跨聚合操作,不应放入订单聚合的同一事务中。
| 对象 | 角色 | 是否聚合根 | 一致性要求 |
|---|---|---|---|
| Order | 聚合根 | 是 | 维护订单整体不变量 |
| OrderLine | 内部实体 | 否 | 与 Order 同事务 |
| ShippingAddress | 值对象 | 否 | 随 Order 快照保存 |
| Payment | 独立聚合根 | 是 | 通过订单号关联,最终一致 |
| InventoryReservation | 独立聚合根 | 是 | 通过领域事件异步处理 |
判断口诀:能独立存在并被外部直接查询的,才可能是聚合根;必须随根一起创建、修改、删除的,放在聚合内部。
订单聚合边界示意
落地检查清单
- 外部对象是否只持有 Order 的引用,而不是 OrderLine 的引用?
- 修改订单行数量时,是否通过 Order 的方法触发总金额重算?
- 支付、库存扣减是否通过领域事件异步处理,而非塞进订单事务?
- ShippingAddress 是否作为值对象随订单快照保存,而不是引用客户档案中的地址实体?
- 删除订单时,OrderLine 是否随聚合根级联删除?
常见误区:把 OrderLine 设计成独立聚合根,导致订单总金额与订单行数量出现跨聚合不一致;或者把 Payment 强行放入订单聚合,造成事务过大、锁竞争严重。