在领域驱动设计(DDD)中,**聚合(Aggregate)和聚合根(Aggregate Root)**是战术设计的核心模式,用于管理领域对象之间的复杂关系和数据一致性。它们是实现高内聚、低耦合领域模型的关键工具。
1. 聚合(Aggregate)的本质
定义
- 聚合是一组强关联的领域对象(实体、值对象)的集合,它们形成一个一致性边界,所有操作必须通过聚合根进行。
- 核心目标:确保聚合内部的数据完整性和业务规则(不变条件),避免分散的业务逻辑和无效状态。
关键特性
- 一致性边界:一个事务只能修改一个聚合(强一致性),跨聚合操作通过最终一致性(如领域事件)实现。
- 最小化聚合大小:聚合应尽可能小,避免性能问题(如加载大量数据)。
- 独立生命周期:聚合内的对象共存亡(例如删除订单聚合根会连带删除所有订单项)。
2. 聚合根(Aggregate Root)的作用
定义
- 聚合根是聚合的入口点,外部只能通过聚合根与聚合内的对象交互。
- 职责:
- 维护聚合内部的一致性(例如校验规则)。
- 封装聚合内的复杂操作。
- 作为聚合的唯一标识(聚合根的ID即聚合的全局唯一ID)。
示例:订单聚合
// 聚合根:Order
public class Order {
private String orderId; // 聚合的唯一标识
private List<OrderItem> items; // 聚合内的值对象
private OrderStatus status;
// 添加订单项(业务逻辑内聚到聚合根)
public void addItem(Product product, int quantity) {
if (status != OrderStatus.DRAFT) {
throw new IllegalStateException("Cannot modify a confirmed order.");
}
items.add(new OrderItem(product.getId(), quantity));
}
// 提交订单
public void confirm() {
if (items.isEmpty()) {
throw new IllegalStateException("Order must have items.");
}
this.status = OrderStatus.CONFIRMED;
}
}
// 值对象:OrderItem
public class OrderItem {
private String productId;
private int quantity;
}
3. 聚合设计原则
原则1:通过聚合根访问内部对象
- 错误做法:直接操作聚合内部对象。
// ❌ 直接修改OrderItem OrderItem item = order.getItems().get(0); item.setQuantity(10);
- 正确做法:通过聚合根方法操作。
// ✅ 聚合根控制逻辑 order.updateItemQuantity(productId, 10);
原则2:聚合内部强一致性
- 聚合根确保所有操作满足业务规则。
public class BankAccount { private String accountId; private BigDecimal balance; // 提款操作 public void withdraw(BigDecimal amount) { if (balance.compareTo(amount) < 0) { throw new InsufficientBalanceException(); } this.balance = balance.subtract(amount); } }
原则3:通过ID引用其他聚合
- 避免直接持有其他聚合的引用,通过ID关联。
public class Order { private String customerId; // 引用客户聚合的ID // 而不是 private Customer customer; }
原则4:小聚合优先
- 大聚合会导致性能问题(如加载大量子对象)。
- 反例:将整个电商系统的用户、订单、商品放在一个聚合中。
- 正例:拆分为
User
、Order
、Product
三个聚合。
4. 聚合根的持久化(Java示例)
使用JPA/Hibernate实现聚合根的持久化:
@Entity
@Table(name = "orders")
public class Order {
@Id
private String orderId;
@Embedded // 值对象映射
private Address shippingAddress;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "order_id")
private List<OrderItem> items = new ArrayList<>();
// 业务方法省略...
}
@Embeddable // 值对象
public class Address {
private String street;
private String city;
}
5. 常见问题与解决方案
问题1:如何确定聚合边界?
- 答案:通过事件风暴找出业务不变条件,将需要强一致性的对象放在同一聚合。
问题2:聚合根是否允许跨聚合修改数据?
- 答案:不允许直接修改,应通过领域事件或最终一致性(如订单创建后发布
OrderCreatedEvent
,触发库存扣减)。
问题3:聚合根是否可以是值对象?
- 答案:不可以,聚合根必须是实体(具有唯一标识)。
问题4:如何避免聚合的“贫血模型”?
- 答案:将业务逻辑内聚到聚合根中,避免将逻辑分散到Service类。
总结
- 聚合是领域模型的一致性单元,聚合根是其访问入口。
- 设计聚合的关键是业务不变条件和最小化边界。
- 在Java中,通过聚合根封装业务逻辑,结合JPA等技术实现持久化。