clean-ddd-hexagonal

clean-ddd-hexagonal

主动应用于设计API、微服务或可扩展后端结构时。触发条件:DDD、整洁架构、六边形架构、端口与适配器、实体、值对象、领域事件、CQRS、事件溯源、仓储模式、用例、洋葱架构、发件箱模式、聚合根、防腐层。适用于领域模型、聚合、仓储或限界上下文。整洁架构 + DDD + 六边形模式用于后端服务,语言无关(Go、Rust、Python、TypeScript、Java、C#)。

55Star
3Fork
更新于 2026/7/7
SKILL.md
readonly只读
name
clean-ddd-hexagonal
description

主动应用于设计API、微服务或可扩展后端结构时。触发条件:DDD、整洁架构、六边形架构、端口与适配器、实体、值对象、领域事件、CQRS、事件溯源、仓储模式、用例、洋葱架构、发件箱模式、聚合根、防腐层。适用于领域模型、聚合、仓储或限界上下文。整洁架构 + DDD + 六边形模式用于后端服务,语言无关(Go、Rust、Python、TypeScript、Java、C#)。

整洁架构 + DDD + 六边形架构

结合DDD战术模式、整洁架构依赖规则和六边形端口/适配器的后端架构,用于构建可维护、可测试的系统。

本技能是多种相关架构传统的观点性综合,并非单一规范架构模型。请根据设计问题选择对应的原始来源:领域建模用DDD,端口/适配器用六边形架构,依赖方向用整洁架构,领域中心分层用洋葱架构,CQRS/事件溯源仅用于特定的读/写或时间需求。

何时使用(及何时不使用)

使用场景 跳过场景
复杂业务领域,规则繁多 简单CRUD,业务规则少
长期维护的系统(数年) 原型、MVP、一次性代码
5人以上团队 单人开发或小团队(1-2人)
多个入口点(API、CLI、事件) 单一入口点,简单API
需要替换基础设施(数据库、消息代理) 固定基础设施,不易变更
需要高测试覆盖率 快速脚本、内部工具

从简单开始,仅在需要时增加复杂度。 大多数系统不需要完整的CQRS或事件溯源。

模式边界

模式 主要问题 用途 不要视为
DDD 如何建模复杂业务领域? 通用语言、限界上下文、聚合、值对象 仅文件夹结构
六边形架构 应用程序如何与外部世界交互? 端口、驱动适配器、被驱动适配器、可测试的应用核心 强制六边或精确包布局
整洁架构 依赖应指向哪个方向? 向内依赖规则、用例边界、框架无关性 通用四文件夹模板
洋葱架构 如何保持领域模型为中心? 以领域为中心的分层和依赖反转 当整洁/六边形已解决问题时的额外要求
CQRS 读和写是否需要不同模型? 读写负载差异大的限界上下文 默认应用架构
事件溯源 是否需要完整事件历史的状态? 审计、时间查询、可重放工作流 CRUD系统的持久化默认

关键:依赖规则

依赖仅向内指向。外层依赖内层,反之则不允许。

基础设施 → 应用层 → 领域层
   (适配器)     (用例)    (核心)

需捕获的违规:

  • 领域层导入数据库/HTTP库
  • 在此架构风格中,控制器直接调用仓储而非应用用例
  • 实体依赖应用服务

设计验证: "让你的应用程序在没有UI或数据库的情况下也能工作" — Alistair Cockburn。如果你能在没有基础设施的情况下从测试中运行领域逻辑,那么边界就是正确的。

快速决策树

"这段代码放哪里?"

放哪里?
├─ 纯业务逻辑,无I/O           → domain/
├─ 编排领域 + 有副作用         → application/
├─ 与外部系统通信              → infrastructure/
├─ 定义如何交互(接口)        → port(领域或应用层)
└─ 实现端口                    → adapter(基础设施)

易错点 — LLM最常放错的位置:

代码 原因
业务不变量("订单需要商品才能确认") 领域(实体方法) 这是规则,不是编排
输入格式验证(JSON结构、必填字段) 适配器(控制器/DTO) 协议关注点,非业务规则
事务开始/提交 应用层 用例 = 事务边界
ORM实体/表模型 基础设施 映射到领域对象;绝不让ORM实体成为领域实体
领域↔DB映射 基础设施(映射器) 持久化细节
授权("用户是否允许?") 应用层(策略)或适配器中间件 领域保持与授权无关;仅当角色规则是业务规则时才在领域层编码
时钟、UUID生成 领域/应用层端口;基础设施适配器 保持领域确定性和可测试性
响应领域事件 应用层(事件处理器) 副作用 = 编排
为屏幕查询多表连接 读模型(应用层接口,基础设施实现) 不要强制通过聚合

贫血领域模型的试金石: 如果应用服务从实体读取状态、做出决策、然后写回状态(if (order.status === 'draft') order.status = 'confirmed'),将该逻辑移到实体中作为 order.confirm()。处理器应像脚本一样:加载聚合 → 调用一个行为方法 → 保存 → 发布。

"这是实体还是值对象?"

实体还是值对象?
├─ 具有持久化的唯一标识 → 实体
├─ 仅由其属性定义    → 值对象
├─ "这是同一个东西吗?"         → 实体(身份比较)
└─ "具有相同的值吗?"  → 值对象(结构相等)

"这应该是一个独立的聚合吗?"

聚合边界?
├─ 必须在同一事务中保持一致 → 同一聚合
├─ 可以最终一致                 → 独立聚合
├─ 仅通过ID引用                        → 独立聚合
└─ 聚合中实体超过10个                    → 拆分

规则: 每个事务一个聚合。跨聚合一致性通过领域事件(最终一致性)实现。

目录结构

src/
├── domain/                    # 核心业务逻辑(无外部依赖)
│   ├── {aggregate}/
│   │   ├── entity              # 聚合根 + 子实体
│   │   ├── value_objects       # 不可变值类型
│   │   ├── events              # 领域事件
│   │   ├── repository          # DDD仓储接口(被驱动端口)
│   │   └── services            # 领域服务(无状态逻辑)
│   └── shared/
│       └── errors              # 领域错误
├── application/               # 用例 / 应用服务
│   ├── {use-case}/
│   │   ├── command             # 命令/查询DTO
│   │   ├── handler             # 用例实现
│   │   └── port                # 驱动端口接口
│   └── shared/
│       └── unit_of_work        # 事务抽象
├── infrastructure/            # 适配器(外部关注点)
│   ├── persistence/           # 数据库适配器
│   ├── messaging/             # 消息代理适配器
│   ├── http/                  # REST/GraphQL适配器(驱动)
│   └── config/
│       └── di                  # 依赖注入 / 组合根
└── main                        # 引导 / 入口点

端口放置: 本技能默认采用以DDD为中心的布局,聚合仓储接口位于 domain/ 中的聚合旁边。更严格的六边形布局可能将被驱动端口放在 application/ports/driven/ 下。每个代码库选择一种约定,并保持依赖规则完整。

表示层: 驱动适配器(REST/gRPC/CLI)默认位于 infrastructure/ 下。某些代码库将其提升为第四层 presentation/references/LAYERS.md 展示了该变体)。为控制器选择一个位置,不要同时使用。

事件发布: 保存聚合然后将其事件发布到代理是两次写入;两者之间的崩溃会静默丢弃事件。当事件必须可靠地到达其他服务时,在与聚合相同的事务中将事件写入发件箱表 — 参见 references/CQRS-EVENTS.md 中的发件箱模式。

DDD构建块

模式 目的 关键规则
实体 身份 + 行为 领域 按ID相等
值对象 不可变数据 领域 按值相等,无setter
聚合 一致性边界 领域 仅根被外部引用
领域事件 变更记录 领域 过去时命名(OrderPlaced
仓储 持久化抽象 领域(端口) 每个聚合一个,非每个表一个
领域服务 无状态逻辑 领域 当逻辑不适合实体时
应用服务 编排 应用层 协调领域 + 基础设施

反模式(关键)

反模式 问题 修复
贫血领域模型 实体是数据容器,逻辑在服务中 将行为移入实体
每个实体一个仓储 破坏聚合边界 每个聚合一个仓储
泄露基础设施 领域导入DB/HTTP库 领域零外部依赖
上帝聚合 实体过多,事务慢 拆分为更小的聚合
跳过用例 控制器直接调用仓储 通过应用用例路由
CRUD思维 建模数据而非行为 建模业务操作
过早CQRS 在需要前增加复杂度 从简单读写开始,逐步演进
跨聚合事务 一个事务中多个聚合 使用领域事件实现一致性

实现顺序

  1. 发现领域 — 事件风暴、与领域专家对话
  2. 建模领域 — 实体、值对象、聚合(无基础设施)
  3. 定义端口 — 仓储接口、外部服务接口
  4. 实现用例 — 协调领域的应用服务
  5. 最后添加适配器 — HTTP、数据库、消息实现

DDD是协作的。 与领域专家的建模会话与代码模式同等重要。

参考文档

在执行左侧列出的任务前,阅读对应的文件:

在您... 阅读
在任何层编写代码、注入依赖、或决定3层vs4层 references/LAYERS.md
将系统拆分为服务/上下文、与遗留或第三方系统集成(ACL)、运行事件风暴 references/DDD-STRATEGIC.md
建模实体、值对象、聚合、仓储、领域服务或工厂 references/DDD-TACTICAL.md
定义端口/适配器、命名接口、或布局端口优先结构 references/HEXAGONAL.md
添加命令/查询、领域vs集成事件、发件箱、Saga、或评估CQRS/事件溯源 references/CQRS-EVENTS.md
为任何层编写单元/集成/架构测试 references/TESTING.md
快速回答"哪个模式/哪一层"问题而不深入 references/CHEATSHEET.md

来源

主要来源

主要模式参考

实现指南

补充综合