聚合演进设计原则
本文回答「既有聚合要改 / 要加能力时,设计文档怎么写」。它对应《聚合设计文档标准》中的「修改场景」,是
ddd-design-doc在迭代需求下产出修改类设计文档的依据。全新需求请走「聚合识别设计原则」。
1. 定位与适用场景
| 场景 | 是否适用 | 说明 |
|---|---|---|
| 给既有聚合加操作 / 命令 | 是 | 操作码声明 + 旧快照机制 |
| 给既有聚合加子实体 / 值对象 | 是 | 子表 vs 独立聚合判定 |
| 给既有聚合加领域事件 / 订阅 | 是 | 事件载荷 + 投影影响 |
| 既有聚合演进式拆分 | 是 | 衔接「聚合识别设计原则」 |
| 全新聚合 | 否 | 走「聚合识别设计原则」 |
2. 根因分析三层
修改类设计必须包含根因分析,避免只修表象导致同类缺陷复发:
| 层级 | 问什么 | 落点 |
|---|---|---|
| 直接原因 | 哪段实现没拦住 / 没覆盖 | 具体代码(用 行号:相对路径 标注) |
| 深层原因 | 为什么没拦住(执行顺序 / 模型漏洞) | 机制层面 |
| 本质原因 | 为什么会有这样的设计 | 职责 / 边界 / 模型层面,而非「忘了写判断」 |
3. 修改场景设计要点
3.1 加操作 / 命令
- 操作码在操作注册表设计声明。
- 前置类规则(如「仅待发货可发货」)必须读旧快照
old:框架execute先跑领域逻辑后校验,当前态已被改动(详见聚合业务规则(OrderRule 范式))。
3.2 加子实体 / 值对象
- 随父聚合存在、无独立访问 → 同聚合子表(
TrackedList+ 独立子表)。 - 有独立访问 / 独立生命周期 → 提为独立聚合(衔接聚合数据库设计原则 §2 一表 / 多表判定)。
- 无身份值对象 → 并入父表 JSON,不建子表。
3.3 加领域事件 / 订阅
- 事件载荷含改前 / 改后;订阅者与投影影响见事件订阅领域服务落地模式 与投影读模型代码落地指南。
- 即便零改动投影,也要在文档中写「零改动,理由:……」,避免被误判为遗漏。
4. 改动清单与落地顺序
按聚合设计文档标准 §11,用表格给出确切文件路径 + 动作(新增 / 改 / 删)+ 说明,三列缺一不可:
| # | 文件 | 动作 | 说明 |
|---|---|---|---|
| 1 | domain/.../operation/OrderOperationRegistry.java | 改 | 新增 CORRECT_LOGISTICS |
| 2 | domain/.../event/OrderLogisticsCorrectedEvent.java | 新增 | 携带改前 / 改后 + 原因 |
并单独给出落地顺序,保证每步可编译、可单测(如先立规则与操作码,再改聚合,最后接应用层与接口层)。
5. 版本化
迭代产出的设计文档按需求分目录,同一需求内三份契约共用一份目录,目录名本身即版本标识。
- 目录约定:活动需求
docs/design/changes/active/{需求}/{agg}/,归档后移到changes/archive/{需求}/{agg}/。 - 文档顶部
结论先行块标注:需求名,以及「替代上一版契约的哪些章节」(上一版指changes/archive/中该聚合最近一次)。 - 同一需求重做时,在该聚合子目录下加
v2/。 - 这样多轮迭代的文档可按需求回溯,评审者能直接比对变更。
6. 范围控制
- 顺带发现的同类缺口,一律记入风险表并标注「不在本次范围」,用于另开文档,避免范围蔓延(反模式:顺手一起改导致评审与回滚困难)。
- 设计文档只引用框架机制链接,不重复讲解(见设计文档编写规范)。
7. 常见反模式
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| 只有方案没有根因 | 同类缺陷反复出现 | 写三层根因,本质原因落职责 / 边界 |
| 状态前置规则读当前态 | 规则永远通过 | 前置判断必须读 old |
| 只在正文描述改动无清单表 | 易漏改 / 改错 | 独立改动清单表 + 落地顺序 |
| 期望写成「报错 / 失败」 | 无法断言 | 写确切消息码常量名与状态枚举值 |
| 顺手一起改顺带缺陷 | 范围蔓延 | 记入风险表,标「不在本次范围」 |