聚合是什么
聚合是一致性边界:一组对象在被修改时必须保持业务不变量,外部只能通过聚合根访问内部实体与值对象,一次事务只修改一个聚合。聚合内部用数据库事务保证强一致,聚合之间通过领域事件达成最终一致。划分聚合的判据是真需要原子保证的不变量,而不是对象之间的引用关系;把大量实体塞进一个聚合会带来锁竞争、加载开销与并发冲突。
基本信息
| 聚合根 | 聚合的唯一访问入口,负责维护内部不变量 |
|---|---|
| 设计原则 | 聚合尽量小;跨聚合用 ID 引用而非对象引用;一次事务只改一个聚合 |
| 一致性 | 聚合内强一致由事务保证,聚合间最终一致由事件驱动 |
| 标识 | 聚合根有全局唯一标识,内部实体只保证聚合内唯一 |
| 持久化 | 仓储以聚合为粒度加载与保存,不单独保存内部实体 |
不变量示例
不变量示例
class Order { # 聚合根
addLine(sku, qty) {
require(status == 草稿) # 不变量一:仅草稿状态可改明细
require(lines.size < 100) # 不变量二:明细行数上限
require(qty > 0) # 不变量三:数量必须为正
}
submit() { require(lines 非空) } # 提交前必须至少有一行
}
# 跨聚合只保存 ID 引用,不持有其他聚合对象典型使用场景
- 订单与明细:明细行必须与订单一起满足不变量,适合同一聚合
- 库存扣减:库存独立成聚合,通过事件与订单协作而非同事务修改
- 账号与地址:地址是值对象时随账号修改,作为独立实体时另立聚合
- 高并发写入优化:把热点数据移出大聚合,降低锁竞争
- 事件驱动集成:聚合内变更发出领域事件,由其他聚合订阅处理
- 重构旧模型:按事务边界重新审视对象图,识别真正的聚合
聚合边界判断
| 信号 | 建议 | 原因 |
|---|---|---|
| 必须同时成立的不变量 | 放在同一聚合 | 需要事务保证原子性 |
| 仅需最终一致 | 拆成不同聚合 | 用事件协作即可,减少锁范围 |
| 只有一个字段被并发修改 | 考虑独立聚合 | 避免整聚合加锁 |
| 加载一个聚合要读上千行 | 拆小聚合或用延迟加载 | 加载与内存开销过大 |
| 外部经常绕过根直接改内部 | 检查边界是否划错 | 说明真实不变量不在根上 |
聚合根通常不负责查询:复杂列表与统计查询走独立的读模型,避免为了展示需求把聚合加载成巨大的对象图。
跨聚合用一个数据库事务同时修改多个聚合,会重新引入耦合与锁竞争;若确实无法拆分,说明边界划分需要重新评估。