聚合识别设计原则
本文回答「一份需求摆过来,怎么识别出应该有哪些聚合」。它是战略建模步骤,既用于全新需求从 0 识别,也用于迭代需求中「要不要新增一个聚合」的判断。识别结果直接喂给聚合设计(
aggregate-design),并最终落为一份符合《聚合设计文档标准》的设计文档。
1. 定位与适用场景
| 场景 | 是否适用 | 说明 |
|---|---|---|
| 全新需求 | 是 | 从需求文本 / 口述中识别一组聚合候选及其边界 |
| 迭代需求(加聚合) | 是 | 判断「新能力应扩到既有聚合」还是「独立成新聚合」 |
| 迭代需求(改聚合) | 否 | 聚合边界已定,走「聚合演进设计」;本文只负责识别,不负责改 |
识别阶段不写代码、不出设计文档正文,只产出「聚合候选清单 + 边界 + 不变量 + 粒度结论」,作为后续设计与文档的输入。
2. 识别四步(事件风暴)
2.1 列事件
引导需求方列出业务已发生或会发生的事件(过去式、事实语义):「订单已支付」「库存已扣减」「物流已揽收」。
- 每个事件归属一个潜在聚合;同一业务动作下事件密集、且需强一致的,归同一聚合候选。
- 参考:事件建模指南 的事件命名与事实语义。
2.2 提炼不变性
对每个候选聚合追问:「哪些业务规则必须在一次事务内保持一致?」
- 强一致不变性(如「订单总额 = Σ 订单项金额」)→ 同一聚合内。
- 最终一致(如「订单支付成功 → 扣库存」)→ 跨聚合,走领域事件。
2.3 定边界(强一致 vs 最终一致)
| 关系 | 判定 | 落点 |
|---|---|---|
| A 的不变性依赖 B 的当前态、需同事务 | 强一致 | A、B 同一聚合 |
| A 只关心 B「发生了什么」,不关心其当前态 | 最终一致 | A、B 跨聚合,B 发事件、A 订阅 |
2.4 定粒度(小聚合原则)
用「小聚合」原则校验候选是否过大:
- 是否引入了不需要一起加锁的实体?(是 → 拆出)
- 跨聚合是否只持 ID?
- 子实体是否随父聚合存在、无独立访问?(是 → 同聚合子表;否 → 独立聚合)
3. 边界判据清单
识别完成时,对每个候选应明确回答如下问题(缺一则回到 §2):
- 聚合根是谁?为什么是它?
- 包含哪些实体 / 值对象?
- 不归本聚合管什么(同样重要,防止边界蔓延)?
- 维护哪些核心不变量?
- 与其他聚合是强一致还是最终一致?
4. 迭代中的「加聚合」判定
迭代需求常出现「这个新能力放哪」。判定顺序:
- 新能力的不变量是否与某既有聚合的不变性同事务强一致?是 → 扩到该聚合(走演进设计)。
- 否则,新能力是否有独立生命周期 / 独立访问?是 → 新增独立聚合。
- 仅是既有聚合的一个新只读视图 → 走投影读模型,不增聚合。
经验法则:当「加进既有聚合会让聚合膨胀、并发冲突上升」时,倾向独立成聚合,跨聚合以事件 / ID 协作。
5. 常见误判与反模式
| 误判 | 纠偏 |
|---|---|
| 一个聚合包下整个领域 | 按不变性切分为小聚合 |
| 跨聚合持对象引用 | 只持 ID(约束 13) |
| 把最终一致当强一致(同聚合硬塞) | 跨聚合走领域事件 |
| 无身份值对象建独立子表 | 并入父表 JSON |
| 子实体被他聚合独立访问却塞同聚合 | 提为独立聚合 |
6. 与其他最佳实践的关系
- 识别出的聚合,进入聚合设计原则做详细设计,并按聚合设计文档标准产出统一结构的设计文档。
- 涉及持久化 / 表边界,参考聚合数据库设计原则。
- 涉及跨聚合协作,参考事件订阅领域服务落地模式与对外广播落地模式。