ddd-aggregates · diff
v1.0.1 to v1.1.0
56 added, 1 removed. Audit A to A.
---
name: ddd-aggregates
description: >-
Design DDD aggregates and aggregate boundaries from invariants: aggregate roots,
entities, value objects, transaction boundaries, and cross-aggregate consistency
strategies. Use when designing aggregate boundaries, invariants, transaction boundaries,
or cross-aggregate consistency.
risk: safe
source: self
tags: "[ddd, tactical, aggregates, invariants]"
date_added: "2026-05-08"
slug: ddd-aggregates
- version: 1.0.1
+ version: 1.1.0
displayName: ddd-aggregates
---
# DDD Aggregates
> English version: [English](SKILL.en.md)
## 使用时机
- 限界上下文与集成策略已就绪,需要设计上下文内部的构造块。
- 需要回答"哪些对象必须一起变更以保持一致性"。
- `ddd-model-review` 报告"不变量表达率 < 60%",或 `ddd-domain-interactions` 报告"事件需携带另一聚合私有数据"时,作为回溯目标重新执行。
## 输入要求
- **必需**:事件流与命令候选(来自 `ddd-discover`)、上下文目录与词汇表(来自 `ddd-contexts`)。
- **可选**:上下文映射与失败模式(来自 `ddd-context-map`)。
## 流程
1. **提取不变量**:从命令与事件中提取"必须始终为真"的业务规则(不变量)。
2. **聚类对象**:以不变量为纽带,将对象聚类为聚合候选;明确聚合根(唯一外部入口)。
3. **识别构造块**:区分聚合内的实体(有标识、有生命周期)与值对象(无标识、不可变、按值比较)。
4. **定义边界规则**:外部只持有聚合根引用;跨聚合引用使用 ID;每个聚合是一个事务边界。
- **外部引用再审视**:对模型中每一个"外部引用对象"(foreign reference),再问一遍——我们自己是否需要管理它的生命周期(创建、修改、终结)?若答案为是,它应被提升为 **内部聚合**,而不是仅保留 ID 引用。典型触发器:参考数据类对象(港口目录、日历、线路目录)若由我方维护,必须列为聚合。
- **Specification 模式识别**:当某条业务规则以"给定 X,X 是否满足 Y"的谓词形式出现(如 `isSatisfiedBy(Itinerary)`),显式抽取为 Specification,而不是塞进工厂或服务内部的 if 分支。
5. **命令边界**:为每个聚合定义它处理的命令、前置验证、副作用。
6. **跨聚合一致性**:聚合内强一致;聚合间最终一致——定义事件驱动 / 补偿 / 重试策略。
7. **仓储接口草案**:为每个聚合产出仓储接口的语义定义(方法名 + 语义,不含代码实现)。
## 输出
| 工件 | 结构要求 |
| :--------------- | :--------------------------------------------------------------- |
| 聚合目录 | 表格:聚合名、聚合根、包含实体、包含值对象、关键不变量、关键命令 |
| 不变量表 | 表格:不变量、触发命令、校验位置、违反时行为 |
| 实体与值对象清单 | 表格:名称、类型(Entity/VO)、所属聚合、标识策略/相等性定义 |
| 事务边界说明 | 列表:默认规则、例外条件、并发/锁策略 |
| 跨聚合一致性策略 | 表格:场景、触发事件、补偿方式、幂等保障、重试策略 |
| 仓储接口草案 | 表格:聚合、方法、语义说明、查询边界 |
## 校验清单
- [ ] 每个聚合至少对应 1 条显式不变量
- [ ] 聚合边界说明了事务边界;默认"1 个事务修改 1 个聚合"
- [ ] 跨聚合一致性有事件与补偿策略
- [ ] 无"以外键划聚合"的反模式(聚合不是 ORM 关系映射)
- [ ] 实体与值对象的区分有明确理由(标识 vs 值语义)
- [ ] 聚合大小合理:单个聚合不应包含 > 5 个实体(若超过需论证)
- [ ] 每个外部引用都已回答过"其生命周期是否由我方管理";答案为是者已提升为内部聚合
- [ ] 已扫描"谓词型业务规则"并评估 Specification 模式;入选者已列为一等构造块
## 回溯触发
- 不变量跨越多个上下文 → 回溯至 `ddd-contexts`(一致性需求被边界割裂)。
- 被 `ddd-domain-interactions` 触发:事件需携带另一聚合私有数据,说明聚合边界需调整。
- 被 `ddd-model-review` 触发:不变量表达率 < 60%(聚合可能是数据容器而非行为边界)。
## 示例
+ ### 示例 1 · 最短真实样例(前置 → 调用 → 产出摘录)
+
+ 前置:`ddd-contexts` 已产出 Booking 上下文定义,`ddd-discover` 已产出事件流。
+
+ 调用:
+
```text
@ddd-aggregates
基于以下上下文定义和事件流,帮我设计 Booking 上下文内的聚合:
- 上下文:Booking(预订全生命周期)
- 核心术语:Booking, TimeSlot, BookingPolicy, CheckIn
- 关键事件:BookingRequested, BookingConfirmed, BookingCancelled, CheckInRecorded
请输出聚合目录、不变量表、实体/值对象清单与事务边界说明。
```
+
+ 产出摘录(聚合目录节选,演示"长什么样"):
+
+ ```text
+ | 聚合 | 聚合根 | 包含实体 | 包含值对象 | 关键不变量 | 关键命令 |
+ |---------|---------|-----------------|--------------------|--------------------------------|---------------------|
+ | Booking | Booking | Booking | DateRange, Purpose | 同一时段不可重复确认 | ConfirmBooking |
+ | CheckIn | CheckIn | CheckIn | ActualTimes | 入住时间不早于确认的预订开始日 | RecordCheckIn |
+ ```
+
+ 不变量表节选:
+
+ ```text
+ | 不变量 | 触发命令 | 校验位置 | 违反时行为 |
+ |------------------------|----------------|------------------|--------------------|
+ | 同一时段不可重复确认 | ConfirmBooking | Booking 聚合内 | 命令拒绝,发冲突事件 |
+ ```
+
+ ### 示例 2 · 前置缺失时的出口
+
+ ```text
+ @ddd-aggregates # 未附上下文定义与事件流
+ ```
+
+ → 出口:回复「缺少必需输入:事件流与命令候选(ddd-discover)、上下文目录与词汇表(ddd-contexts)」,停止;不凭空聚类聚合。
+
+ ## 失败出口与边界处置(可观察)
+
+ | 情形 | 可观察出口 |
+ |------|-----------|
+ | 必需输入缺失(无事件流/无上下文目录) | 停止并指出缺失项及其来源技能;**不产出**聚合目录 |
+ | 输入来自其他项目/上下文(错仓型:术语与本仓对不上) | 停止,指出「上下文定义与当前仓库不匹配」并给证据 |
+ | 不变量跨多个上下文 | 不硬划:按「回溯触发」退回 `ddd-contexts`,说明割裂点 |
+ | 用户只给了 ORM 模型/表结构(无业务规则) | 指出「外键关系不等于聚合」,请提供命令与业务规则;表结构只能作参考 |
+ | 单聚合候选 > 5 实体 | 不静默接受:按校验清单要求给出拆分论证或拆分方案 |
+ | 无网/无仓 | 仍可基于粘贴内容离线设计;产出标注「未经仓库交叉验证」 |
+
+ ## 错法 → 改法(FAQ)
+
+ | 错法 | 为什么错 / 后果 | 改法 |
+ |------|-----------------|------|
+ | 以外键/ORM 关系划聚合 | 聚合是一致性边界不是存储关系;照表划会把事务边界做大,并发全部互锁 | 从不变量出发聚类(流程第 1–2 步),表结构仅作参考 |
+ | 聚合根外的实体直接对外暴露 | 外部可绕过根的不变量校验,聚合保护失效 | 外部只持聚合根引用;跨聚合引用用 ID |
+ | 一个事务改多个聚合 | 跨聚合强一致 → 锁竞争与失败放大 | 聚合内强一致;聚合间用事件 + 补偿/重试(最终一致) |
+ | 参考数据(港口目录、日历)只留 ID 引用,但生命周期其实由我方管理 | 该对象无法建模维护流程,规则散落各处 | 走「外部引用再审视」:我方管理生命周期 → 提升为内部聚合 |
+ | 谓词型规则塞进服务里的 if 分支 | 规则不可复用、不可测试、不可组合 | 抽为 Specification(`isSatisfiedBy`),列为一等构造块 |
+ | 值对象加了 ID | 有 ID 即有生命周期,实为实体伪装 | 按「标识 vs 值语义」重新判定(流程第 3 步),在实体/值对象清单写明理由 |
+ | 巨型聚合(>5 实体)硬撑 | 事务冲突频发、加载成本高 | 按不变量拆分,跨子一致性改事件驱动 |
+ | 自主触发(无用户请求自行设计聚合) | 与本仓技能调用纪律冲突 | 仅在用户显式调用或 `ddd-model-review`/`ddd-domain-interactions` 回溯触发时执行 |