聚合根设计 (Aggregate Root Design)
概述
聚合根是聚合的唯一入口,外部对象只能通过聚合根与聚合交互;它持有聚合的全局标识,负责维护聚合的所有业务不变量。
核心规则:
- 聚合根本身是 Entity,拥有全局唯一标识
- 外部对象只引用聚合根,不直接引用内部实体或值对象
- 聚合根负责维护所有跨子对象的业务不变量
- 仓储以聚合根为单位加载与保存(一次性加载完整聚合)
- 聚合根负责**产生(raise / record)**领域事件——把"已发生的事实"记入自身;事件的对外发布则由应用服务在事务边界统一完成(或经由 Outbox 投递)
- 一个事务只修改一个聚合实例
- 聚合间通过 ID 引用,不通过对象引用
代码示例
// ✅ Order 是聚合根,OrderItem 是内部实体
public class Order { // 聚合根
private OrderId id;
private CustomerId customerId; // 引用其他聚合用 ID
private List<OrderItem> items = new ArrayList<>();
private OrderStatus status;
private Money totalAmount;
// 所有操作通过聚合根进行
public void addItem(ProductId productId, Money price, int quantity) {
if (status != OrderStatus.DRAFT) {
throw new DomainException("只有草稿订单可以添加商品");
}
if (quantity <= 0) {
throw new DomainException("数量必须为正");
}
// 检查不变量:同一商品不能重复添加
if (items.stream().anyMatch(i -> i.getProductId().equals(productId))) {
throw new DomainException("商品已存在,请修改数量");
}
items.add(new OrderItem(productId, price, quantity));
recalculateTotal();
}
public void removeItem(ProductId productId) {
items.removeIf(i -> i.getProductId().equals(productId));
recalculateTotal();
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::subtotal)
.reduce(Money.ZERO, Money::add);
}
}
// OrderItem 是内部实体,外部不能直接访问
public class OrderItem { // 内部实体
private ProductId productId;
private Money unitPrice;
private int quantity;
Money subtotal() { return unitPrice.multiply(quantity); }
}
# ✅ 聚合根设计
class ShoppingCart: # 聚合根
def __init__(self, cart_id: str, customer_id: str):
self.id = cart_id
self.customer_id = customer_id # 引用其他聚合用 ID
self._items: list[CartItem] = []
def add_item(self, product_id: str, price: Decimal, quantity: int):
existing = self._find_item(product_id)
if existing:
existing.increase_quantity(quantity)
else:
self._items.append(CartItem(product_id, price, quantity))
def checkout(self) -> "Order":
if not self._items:
raise DomainError("购物车为空")
# 聚合根负责创建新聚合
return Order.create_from_cart(self.customer_id, list(self._items))
def _find_item(self, product_id: str):
return next((i for i in self._items if i.product_id == product_id), None)
聚合设计原则
1. 尽量小的聚合
❌ 一个聚合包含 Order + Customer + Product + Payment
✅ Order 聚合只包含 Order + OrderItems
2. 聚合间通过 ID 引用
❌ private Customer customer; // 对象引用
✅ private CustomerId customerId; // ID 引用
3. 一个事务一个聚合
❌ 一个事务修改 Order + Inventory + Payment
✅ 一个事务只修改 Order,通过事件通知其他聚合
4. 最终一致性
聚合内:强一致性(事务保证)
聚合间:最终一致性(通过事件)
常见误区
❌ 为内部实体单独建仓储——OrderItemRepository、OrderLineRepository
→ 一聚合一仓储;内部实体通过聚合根加载和保存
❌ 聚合根成为"上帝类"——所有方法都堆在 Order 上,单类逾千行 → 跨聚合的协同抽到领域服务;聚合内的复杂规则抽成规约 / 策略对象
❌ 对外暴露内部可写集合——getItems() 返回可写 List,外部直接增删
→ 返回不可变视图(Collections.unmodifiableList),或只暴露查询方法(findItemBySku())
与其他DDD概念的关系
| 概念 | 关系 |
|---|---|
| 聚合 | 聚合根是聚合的入口 |
| 实体 | 聚合根本身是实体 |
| 领域事件 | 聚合根发布领域事件 |
| 仓储 | 仓储按聚合根为单位操作 |
总结
核心:聚合根是唯一入口,维护一致性,控制所有写操作。
实践:聚合尽量小、通过 ID 引用其他聚合、一个事务一个聚合。