一、聚合(Aggregate)
1. 定义
聚合是领域驱动设计(DDD)中的一个重要概念。它指的是一组相关对象的集合,这些对象作为数据更改的一个单元进行处理。聚合内部有明确的边界,聚合内的对象之间有强关联,聚合之间只有通过聚合根才能联系。
2. 作用
- 划分边界:明确哪些对象属于同一个业务操作单元。
- 保证一致性:聚合内部的数据一致性由聚合根负责维护。
- 简化外部访问:外部只能通过聚合根访问聚合内部的对象。
二、聚合根(Aggregate Root)
1. 定义
聚合根是聚合内部的唯一入口,是聚合的代表对象。外部只能通过聚合根来访问和操作聚合内的其他对象,不能直接引用聚合内的其他实体或值对象。
2. 作用
- 负责聚合内的数据完整性和一致性。
- 对外提供操作接口,屏蔽聚合内部的实现细节。
- 唯一标识聚合。
三、举例说明
例子:电商系统中的订单
1. 领域建模
假设有如下业务对象:
Order(订单)OrderItem(订单项)Payment(支付信息)
2. 聚合与聚合根的划分
- 聚合根:Order
- 聚合成员:OrderItem、Payment
Order(订单)【聚合根】
├── OrderItem(订单项)
└── Payment(支付信息)
3. 访问和操作规则
- 只能通过Order对象来访问和修改OrderItem和Payment。
- 外部不能直接操作OrderItem或Payment,比如不能直接删除一个OrderItem,必须通过Order的方法来删除。
4. 代码示例(伪代码)
public class Order {
private List<OrderItem> items;
private Payment payment;
public void addItem(OrderItem item) {
items.add(item);
}
public void removeItem(OrderItem item) {
items.remove(item);
}
public void pay(Payment payment) {
this.payment = payment;
}
}
public class OrderItem {
private String productId;
private int quantity;
// 只能被Order操作
}
public class Payment {
private String paymentId;
private double amount;
// 只能被Order操作
}
- 这里,Order是聚合根,只有通过Order的方法才能操作OrderItem和Payment。
四、设计原则
1. 聚合的大小
- 聚合不宜过大,否则会导致性能问题和复杂性提升。
- 只把强一致性要求的对象纳入同一个聚合。
- 弱一致性对象可放在不同聚合,通过领域事件等方式同步。
2. 聚合根的职责
- 聚合根负责所有业务规则和数据一致性。
- 聚合根的方法要保证聚合内部状态的正确性。
- 聚合根应对外暴露简洁的操作接口,隐藏聚合内部细节。
五、事务边界
- 事务只跨越一个聚合。即一次业务操作(如下单、支付)只修改一个聚合的数据,避免分布式事务。
- 如果需要同时修改多个聚合,建议使用最终一致性(如事件驱动、消息队列)而不是强一致性。
例子:
用户下单时,订单(Order)聚合和库存(Inventory)聚合分别处理自己的事务,库存的减少可以通过事件异步通知。
六、持久化策略
- 只保存聚合根对象,聚合内部的实体和值对象通过聚合根持久化。
- 外部系统只通过聚合根的ID来引用聚合,避免直接引用聚合内部成员的ID。
例子:
订单(Order)聚合持久化时,OrderItem和Payment作为Order的子表或嵌套对象一起保存。
七、聚合与其他领域模型的关系
1. 聚合之间的关系
- 聚合之间通过聚合根的ID进行关联,而不是通过对象引用。
- 避免聚合之间直接持有其他聚合的对象引用,这样可以减少耦合。
2. 聚合与实体、值对象的关系
- 聚合根是实体,有全局唯一标识(ID)。
- 聚合内部可包含其他实体和/或值对象。
- 值对象没有唯一标识,只通过属性区分。
八、实际应用注意事项
- 建模时要识别聚合边界,找出哪些对象需要强一致性,哪些可以弱一致性。
- 聚合根的设计要考虑性能,避免聚合根过大导致数据库锁和并发问题。
- 聚合内的对象变化都要通过聚合根完成,保证业务规则和数据完整性。
- 聚合之间的交互建议用领域事件或服务协调,避免直接操作其他聚合的数据。
九、进一步举例:用户与地址
假设有如下业务场景:
- 用户(User)可以有多个收货地址(Address)。
- 只有用户(User)能添加、删除自己的地址(Address)。
建模:
- User为聚合根,Address为User聚合内部的实体。
public class User {
private String userId;
private List<Address> addresses;
public void addAddress(Address address) {
addresses.add(address);
}
public void removeAddress(Address address) {
addresses.remove(address);
}
}
public class Address {
private String addressId;
private String detail;
// 只能被User操作
}
- 外部只能通过User的addAddress/removeAddress方法管理地址,不能直接操作Address对象。
十、复杂业务场景下的聚合建模
1. 聚合分拆与协作
在实际业务中,聚合往往并非孤立存在,可能需要与其他聚合协作。例如:
- 订单与商品库存
订单(Order)聚合和库存(Inventory)聚合分别有自己的聚合根。下单时,订单聚合根负责订单创建,库存聚合根负责库存扣减。两者之间通过事件或应用服务协调,而不是直接操作对方的数据。
2. 聚合的边界调整
随着业务发展,聚合边界可能需要调整。例如:
- 用户与地址
一开始,地址(Address)作为用户(User)聚合的内部实体。但如果地址需要独立被管理(比如地址可以被多用户共享),可以将地址单独建模为聚合,此时用户聚合根只保存地址聚合根的ID。
十一、聚合在微服务与分布式系统中的应用
1. 微服务划分依据
- 微服务的边界往往与聚合边界一致。每个微服务负责一个或多个聚合,保证服务内部的一致性,服务之间通过API或事件通信。
2. 分布式事务的避免
- 由于聚合之间事务不能跨界,分布式系统中推荐采用最终一致性,如事件驱动、补偿机制,而非分布式强一致性事务。
例子:
- 用户下单后,订单服务创建订单,发送“订单已创建”事件,库存服务监听事件后扣减库存。
十二、聚合与CQRS、事件溯源
1. CQRS(命令查询职责分离)
- 在CQRS架构中,命令模型通常以聚合为单位,负责业务操作和一致性。
- 查询模型则可以根据需求优化,不必严格遵循聚合边界。
2. 事件溯源(Event Sourcing)
- 聚合根的每次状态变化都记录为领域事件,聚合根通过事件重放恢复状态。
- 聚合根成为事件溯源的聚合状态载体。
十三、常见建模误区
1. 聚合过大
- 把过多对象放入一个聚合,导致性能瓶颈和并发冲突。
- 解决:只把强一致性需求的对象纳入聚合,弱一致性对象分离。
2. 聚合根“万能”化
- 聚合根设计成“超级对象”或“上帝对象”,承担过多职责。
- 解决:只让聚合根负责业务规则和一致性,辅助职责交给领域服务或工厂。
3. 聚合之间直接引用
- 聚合之间直接持有其他聚合对象,导致耦合和事务扩展。
- 解决:只通过聚合根ID或领域事件进行通信。
十四、实战建议
- 建模时先识别业务场景中的一致性边界,再确定聚合和聚合根。
- 聚合根只暴露必要操作,聚合内部对象不对外暴露。
- 聚合之间的关系用ID或事件,避免对象引用。
- 聚合的持久化以聚合根为单位,存储时聚合根负责聚合内部对象的一致性。
- 微服务设计时聚合与服务边界保持一致,服务内部操作只涉及本服务的聚合。
十五、总结
- 聚合(Aggregate):表示一组相关对象的集合,是业务操作的边界。
- 聚合根(Aggregate Root):是聚合的代表对象,是外部访问聚合的唯一入口,负责聚合的一致性和完整性。
- 外部只能通过聚合根操作聚合内部对象,不能直接引用或修改聚合内的其他对象。
- 聚合是领域对象的集合,有边界。
- 聚合根是唯一入口,负责聚合的一致性和业务规则。
- 外部只能通过聚合根访问和操作聚合内部对象。
- 聚合之间通过ID关联,避免直接引用。
- 事务只覆盖一个聚合,跨聚合用事件或服务协调。
创作不易,点赞关注,互通有无!
与聚合根(Aggregate Root)&spm=1001.2101.3001.5002&articleId=154065635&d=1&t=3&u=92837bdf3f4641aea18a29a25c7097b6)
1103

被折叠的 条评论
为什么被折叠?



