mcpField
A
aggregate-design

aggregate-design

github.com/j5ik2o开发8039 次安装27 周前更新
代码仓库

关于 aggregate-design

面向实践 DDD 的开发者,介绍如何划分集约并维持其不变条件。内容涵盖集约根访问、按 ID 引用其他集约,以及以结果一致性处理边界外的关系。

主要功能

  1. 01以不变条件划分集约边界
  2. 02通过集约根访问内部对象
  3. 03按 ID 引用其他集约
  4. 04保持集约规模精简
  5. 05以领域事件协作并实现结果一致性

适用场景

  1. 01评估集约边界是否过大
  2. 02检查外部访问是否经过集约根
  3. 03审查跨集约事务设计

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/j5ik2o/okite-ai --skill aggregate-design

安装后在 Claude Code 中输入/aggregate-design确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
trigger-eval.json
references/
python.md
rust.md
scala.md
typescript.md

详细介绍

聚合设计指南

DDD 中聚合(Aggregate)的设计原则。

什么是聚合

聚合 = 一致性边界

  • 聚合是一个对象图,并将整个对象图作为一个单元处理
  • 聚合内部维护真正的不变量(必须始终满足的约束)
  • 聚合根(根实体)是整个聚合的唯一入口点
边界示例:Car 聚合
┌─────────────────────────────────────┐
│           Car集約                    │
│  ┌──────────────────────────────┐  │
│  │   Car (集約ルート)            │  │
│  │   - carId: CarId             │  │
│  │   - tires: List[Tire]        │  │
│  │   - engine: Engine           │  │
│  └──────────────────────────────┘  │
│         ↓ 所有               ↓ 所有  │
│  ┌──────────┐         ┌─────────┐ │
│  │  Tire    │ × 4     │ Engine  │ │
│  │ (VoかEnt)│         │ (VoかEnt)│ │
│  └──────────┘         └─────────┘ │
└─────────────────────────────────────┘
  • Tire 和 Engine 位于 Car 聚合边界内
  • 外部无法直接访问 Tire 或 Engine
  • 只能通过 Car 进行操作

Design by Contract(DbC)

聚合应基于契约进行设计。

契约说明责任
前置条件(Precondition)调用方法前必须满足的条件调用方
后置条件(Postcondition)方法执行后必须满足的条件实现方
不变量(Invariant)必须始终满足的条件实现方

各语言的详细实现模式请参阅 references/typescript.md、references/scala.md、references/rust.md、references/python.md。

基本原则

1. 推荐不可变性(Immutability)

在现代开发中,推荐使用不可变(Immutable)对象。除非有特别理由,否则应保持不可变。 更新状态时,沿用现有值,仅覆盖需要更改的字段。 这样可以避免添加字段时遗漏修改,并明确表达更新意图。

语言不可变更新模式
TypeScriptProps 类型 + ...this.props 展开语法
Scalacase class + copy()
Ruststruct + ..self 结构体更新语法
Pythondataclass(frozen=True) + replace()
2. 强一致性边界

聚合是一个强一致性边界。聚合内部的所有状态都由该聚合管理。

3. 对其他聚合进行间接引用

聚合内部不保存对其他聚合的引用。需要与其他聚合建立关联时,通过 ID 进行间接引用。

4. 完整构造函数

使用基本构造函数初始化所有状态。即使使用重载,也必须将其设计为使用基本构造函数的辅助构造函数。

5. 防御性复制

如果持有可变对象,向外部返回时必须返回副本,或将其转换为不可变对象。

6. 维护不变量

无论执行何种操作,都不得进入无效状态。对于不可变聚合,应通过基本构造函数进行保护。

结构原则

7. 通过聚合根访问

访问聚合内部的实体或值对象时,必须通过聚合根。

8. 1 个事务 = 1 个聚合

不要在单个事务中更新多个聚合。通过最终一致性确保聚合间的一致性。

9. 保持聚合精简

过大的聚合会导致并发问题(锁竞争)。只包含真正需要保持一致的范围。

协作原则

10. 通过领域事件进行协作

聚合状态发生变化时发布领域事件,其他聚合或外部系统订阅事件并作出响应。

11. 乐观锁(仅在有需求时)

仅在需要检测并发更新冲突时才使用版本号。没有相关需求时则无需使用。

12. 持久化无关性

聚合应专注于领域逻辑,不关心如何保存(这是仓储的职责)。


Evans 规则(Eric Evans)

源自 DDD 原著的聚合规则。

Evans 规则 1:根实体的职责

只有聚合根可以直接从仓储获取。

  • 外部对象不能持有对聚合内部的引用
  • 聚合根可以传递内部实体的标识符,但仅限临时使用
  • 聚合根负责维护整个聚合的不变量
Evans 规则 2:限制对内部对象的访问

边界内的实体拥有局部标识符,只需在聚合内唯一即可。

  • 外部无法直接访问
  • 内部实体的标识符无需全局唯一

Vernon 的 4 条规则(Vaughn Vernon)

源自《实践领域驱动设计》的设计规则。

规则 1:将真正的不变量纳入一致性边界

Design aggregates based on true invariants(在一致性边界内确保真正的不变量)

良い例:Order集約
- 注文合計 = 各明細の小計の合計(真の不変条件)
- この計算は即座に正しくなければならない

悪い例:結果整合性で十分な場合
- 在庫数の更新は別の集約で結果整合性
规则 2:保持聚合精简

Keep aggregates small(聚合应保持精简)

大型聚合的问题:

  • 锁竞争增加
  • 并发能力下降
  • 事务失败率上升
❌ Bad: 大きな集約
Product集約
├── productId
├── name
├── backlogItems: List[BacklogItem]  ← 数百件になる可能性
└── ...

✅ Good: 分割した集約
Product集約              BacklogItem集約
├── productId           ├── backlogItemId
├── name                ├── productId(IDで参照)
└── ...                 └── ...
规则 3:通过 ID 引用其他聚合

Reference other aggregates by ID only(其他聚合仅通过 ID 引用)

与前面提到的“对其他聚合进行间接引用”原则相同。

规则 4:最终一致性

Use eventual consistency outside the boundary(边界外采用最终一致性)

  • 通过领域事件 + 最终一致性确保聚合间的一致性
  • 不要在单个事务中更新多个聚合

审查清单

审查现有聚合时使用。

Design by Contract
  • 是否明确指定了前置条件(require)?
  • 是否满足后置条件(ensuring)?
  • 是否在构造函数及每次操作后维护了不变量?
基本原则
  • 聚合是否不可变(Immutable),或者是否有明确理由无法做到不可变?
  • 是否持有对其他聚合的直接引用(是否仅使用 ID)?
  • 是否提供了完整构造函数?
  • 辅助构造函数是否使用了基本构造函数?
  • 返回可变对象时,是否进行了防御性复制?
  • 是否在构造函数中验证了不变量?
Evans 规则
  • 是否只有聚合根可以直接从仓储获取?
  • 是否不存在从外部直接引用聚合内部的情况?
  • 内部实体的标识符是否仅限临时使用?
Vernon 规则
  • 聚合边界内是否仅包含真正的不变量(是否存在过度包含)?
  • 聚合是否足够精简(是否包含数百条记录的集合)?
  • 是否仅通过 ID 引用其他聚合?
  • 是否通过最终一致性确保聚合间的一致性?
结构原则
  • 外部访问是否仅通过聚合根进行?
  • 是否存在可以直接操作内部实体的漏洞?
  • 聚合大小是否合适(是否过大)?
协作原则
  • 状态变化时是否发布领域事件?
  • 有乐观锁需求时,是否实现了版本号?
  • 没有乐观锁需求时,是否避免使用不必要的版本号?
  • 持久化逻辑是否泄漏到了聚合内部?

相关技能(建议一并阅读)

使用此技能时,请同时参阅以下技能:

  • domain-building-blocks:构成聚合的要素(值对象、实体、领域服务)的设计
  • aggregate-transaction-boundary:聚合与事务边界的关系(1 个事务 = 1 个聚合规则)
  • cross-aggregate-constraints:聚合间约束检查与最终一致性的设计
  • repository-placement:聚合仓储接口的放置位置

评论

支持 Markdown