做后端这些年,DDD 我前前后后推过三四个项目,半数以上中途又默默改回了传统三层架构。原因不是 DDD 理论有问题,而是从“理论”到“代码”这段路太难走,领域建模、聚合边界、防腐层、事务一致性,任何一步没想清楚,后面全是窟窿。后来我开始尝试让 AI 编程助手先读 DDD 规范再动手写代码,把方法论固化成提示词和技能文件,效果比我预期的好不少。这套东西我整理成了一个开源项目,名字叫 cleanddd-skills,本质是一组面向 AI Agent 的 DDD 技能包:不写业务代码,不绑定框架,只负责让 Cursor、Claude Code 这类工具在生成代码前,先按领域驱动设计的规则思考。如果你也被 DDD 落地折磨过,同时又想用 AI 提效,这篇文章值得看完。
1. DDD 落地的尴尬,到底卡在哪
1.1 卡住的地方从来不是语法,而是建模
很多人觉得 DDD 难,是因为它不像三层架构那样有明确的 Controller-Service-Mapper 路径。DDD 要求开发者和领域专家统一语言,先建立领域模型,再设计聚合、值对象、领域事件、应用服务。这套东西做得好,系统确实能抗住复杂业务变化,但问题是团队往往在需求还没理清时就被要求“先跑起来”,结果就是建模阶段被压缩,代码层生搬硬套各种模式。
我见过最典型的场景:项目里到处是 XXXEntity、XXXRepository、XXXServiceImpl,文件结构看着很 DDD,点进去发现 Service 里写了两千行业务逻辑,聚合根连实体都不是,领域事件只是空壳。这种“伪 DDD”比不用 DDD 还难受,因为多了抽象层,但没有任何收益。
AI 在此时反而是个不错的“外挂”。它不会像人一样碍于情面跳过思考步骤,只要你把规则写清楚,它会老老实实先做事件风暴,再画限界上下文,再生成代码。cleanddd-skills 做的就是这件事:把 DDD 建模流程变成 AI 可执行的上下文。
1.2 传统三层架构与领域模型的冲突
传统三层架构的 Controller-Service-DAO 思路,天然把业务逻辑堆在 Service 层。当业务规则一多,Service 层就变成一个“上帝类”。改一个订单状态,可能要同时改订单表、库存表、日志表,还可能在事务里调用远程服务。这种写法不是不能上线,而是后续每一次需求变更都像走钢丝。
DDD 要求把业务逻辑收敛到聚合根和领域服务里,应用层只做协调和事务边界控制。但要真正做到这点,团队必须对“哪些行为属于订单聚合自身,哪些行为属于应用服务编排”有共识。这个共识不是开会能解决的,而是需要一套能落到代码上的检查规则。
cleanddd-skills 的应对方式,是把这些规则写成 AI 能理解和执行的 markdown 文件。模型在生成代码前会先读这些文件,然后按 DDD 战术模式的约束输出。表面上看只是多了一个步骤,实际上相当于给 AI 装上了“领域建模方法论”这个外脑。
1.3 AI 时代,落地 DDD 的成本被重新分配
过去我们推行 DDD,最大的成本是沟通和试错。领域专家画一张事件风暴贴纸,开发照着一顿猛敲,回头发现模型理解错了,改起来几乎等于重写。有了 AI 之后,试错成本被大幅压缩——模型可以很快产出一版聚合设计初稿,开发只需要对照需求做取舍,而不是从零开始建模。
当然,AI 生成的东西不能直接用。我的定位是“AI 负责把 DDD 的重复性推导做掉,人负责决策和兜底”。cleanddd-skills 就是在给 AI 划边界:让它先产出领域模型、限界上下文、聚合边界,再由人来修订。这种协作方式,比人肉建模快,也比无脑让 AI 写 CRUD 靠谱。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. cleanddd-skills 是怎么一个东西
2.1 项目定位与整体结构
cleanddd-skills 不是一个应用,也不是代码生成器,它是一组“技能包”,专门给支持 Skills 机制的 AI 编程工具使用。当前 Cursor 和 Claude Code 都支持通过 markdown 定义 skill,AI 在对话中可以按需读取对应文件,从而获得该领域的专业知识。项目结构大致如下:
text复制cleanddd-skills/
├── SKILL.md # 技能入口,描述用途和触发条件
├── 00-principles.md # DDD 核心原则
├── 01-event-storming.md # 事件风暴指导
├── 02-bounded-context.md # 限界上下文划分
├── 03-aggregate-design.md # 聚合与领域模型设计
├── 04-use-case-orchestration.md# 应用服务协调规则
├── 05-anti-corruption-layer.md # 防腐层设计
├── 06-repository-contract.md # 仓储契约设计
├── templates/ # 各类代码骨架模板
│ ├── aggregate.java
│ ├── domain-service.java
│ ├── application-service.java
│ └── infrastructure-adapter.java
└── examples/
└── order-domain/ # 真实领域示例
每个 markdown 文件都聚焦一个 DDD 主题,AI 在某个场景下只需要加载相关文件,而不是一次性把全部规则塞进上下文,这样既省 token,也不容易把模型绕晕。SKILL.md 是入口,里面写了触发条件:当用户提到“领域建模”“DDD 代码生成”“聚合设计”等关键词时,AI 应该先读取对应文件再回答问题。
2.2 Skill 文件的触发机制
如果你用过 Cursor 的 Rules 或 Claude 的 Skills,应该能理解这种机制:AI 不是每次都会主动读文件,它根据用户输入判定是否加载某个 skill。所以 SKILL.md 里的 description 写得越精准,触发越可靠。
我实际配置时,会在 .cursor/skills/cleanddd-skills/SKILL.md 中放类似这样的元信息:
markdown复制---
name: clean-ddd
description: 当用户需要设计领域模型、划分限界上下文、生成DDD分层代码、重构遗留系统到DDD、创建聚合和值对象、设计防腐层时,使用本技能。
---
在 Cursor 的 Agent 模式下,用户输入“帮我按 DDD 方式设计订单上下文”,模型会解析出“DDD”“订单上下文”这些实体,然后读取技能文件,按照里面的建模步骤执行。如果输入是“写一个订单查询接口”,它可能不会触发 DDD 技能,因为这是个简单 CRUD,不需要走完整建模流程。这套机制非常像“基于上下文的工具调用”,用好了效率极高。
2.3 为什么选“技能包”而不是代码脚手架
不少人问,为什么不直接做一个 Maven/Gradle 脚手架,把 DDD 分层目录生成出来,不是更简单吗?脚手架确实能解决目录结构问题,但解决不了“模型设计”问题。一个订单聚合该包含哪些字段和方法,库存扣减该由谁发起,拆分订单和取消订单是不是同一个事务——这些都不是目录能回答的。
技能包的优势在于,它把 DDD 的“思考方式”交给 AI。模型可以基于当前需求,自主完成事件风暴、聚合划分、接口设计等推导工作。脚手架只能生成模板,而 cleanddd-skills 让 AI 在生成模板之前先做一轮建模推导。所以它更接近“方法论的载体”,而不是“代码的载体”。
3. 核心能力拆解:AI 怎么按 DDD 思路写代码
3.1 从事件风暴到限界上下文
DDD 落地最怕“需求还没建模呢,代码先安排上了”。cleanddd-skills 的第一个核心能力,就是强制 AI 在生成代码前,先输出事件风暴记录。所谓事件风暴,就是罗列业务过程中发生的领域事件,比如“订单已创建”“库存已扣减”“支付已完成”,然后从事件倒推聚合和行为。
我会给 AI 一个需求文本,比如:
text复制用户下单后系统需要锁库存,库存不足则下单失败;支付成功后订单变为待发货;订单取消时要释放库存。
AI 按技能文件会先输出这样一段事件风暴草稿:
text复制领域事件:
1. 订单已提交
2. 库存已锁定
3. 库存不足导致订单失败
4. 支付已成功
5. 订单已取消
6. 库存已释放
然后根据事件之间的关系,划分出两个限界上下文:order-context 和 inventory-context。订单上下文负责订单生命周期,库存上下文负责库存锁定与释放。二者通过领域事件异步通信,而不是像传统写法那样在订单服务里直接调库存 Mapper。这个推导过程,靠提示词和知识文件就能稳定触发。
3.2 从需求文本生成聚合与领域服务
有了限界上下文,下一步就是定义聚合。AI 技能文件里会强调聚合设计的三条原则:一致性边界、最小化对外暴露、事务边界内聚。以订单为例,模型不会把 Order 设计成一个装字段的 POJO,而是会把“提交订单”“确认支付”“取消订单”等行为放到聚合根内部。
我在 examples/order-domain 里保存了一份生成后的订单聚合代码草案,核心部分大致如下:
java复制public class Order {
private OrderId orderId;
private OrderStatus status;
private List<OrderItem> items;
private Money totalAmount;
public void submit(InventoryClient inventoryClient) {
// 这里只负责订单自身状态变更
this.status = OrderStatus.SUBMITTED;
}
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new IllegalStateException("已发货订单不能取消");
}
this.status = OrderStatus.CANCELED;
this.registerEvent(new OrderCanceled(orderId));
}
}
注意这里没有把“扣库存”直接写进 Order 的 submit 方法,而是通过领域事件 OrderSubmitted 让库存上下文监听后处理。AI 生成初稿后,我会人工校正事务边界,比如确认哪些状态必须同步完成,哪些可以通过事件异步解耦。这种边界划分,人写很容易纠结,AI 先用技能规则推一版,反而能提供一个相对标准的起点。
3.3 应用层与基础设施层的依赖倒置
另一个容易翻车的地方是分层依赖。很多团队表面分了 domain、infrastructure、application 三层,但 domain 层直接引用了 MyBatis 的注解,或者 application service 里 new 了一个 repository 实现类。DDD 要求依赖必须朝向领域层,基础设施实现只能通过接口注入。
cleanddd-skills 对此专门写了 05-anti-corruption-layer.md 和 06-repository-contract.md。AI 生成代码时,会先定义 repository 接口,再在 infrastructure 里写实现类,并通过 Spring 依赖注入装配。这样即使你用的是 Spring Boot + MyBatis,领域层也能保持纯粹。
比如生成一个仓储接口时,模型输出的是:
java复制public interface OrderRepository {
Order find(OrderId orderId);
void save(Order order);
}
然后在 infrastructure 层生成 MyBatisOrderRepository,内部处理数据表映射、DTO 转换。领域层完全不知道数据库长什么样。这一点对 AI 生成的代码来说特别重要,因为模型很容易顺手就把 @Mapper 写到领域实体上,技能文件可以在很大程度上避免这种失误。
3.4 代码示例:一个订单域生成过程
我这里放一个最小可用的生成记录,方便你感受 AI 的实际输出节奏。我给的输入只有两句话:“生成一个订单提交的 application service,要求事务只在应用层控制,校验逻辑放到领域层。”AI 在读取技能后,输出如下。
领域层:
java复制public class OrderSubmitValidator {
public void validate(Order order, InventoryAvailability availability) {
if (order.isEmpty()) throw new IllegalArgumentException("订单不能为空");
if (!availability.isEnough()) throw new InsufficientInventoryException();
}
}
应用层:
java复制@Transactional
public class OrderApplicationService {
private final OrderRepository orderRepository;
private final OrderSubmitValidator validator;
private final InventoryClient inventoryClient;
public OrderId submit(SubmitOrderCommand command) {
Order order = Order.create(command.getItems());
InventoryAvailability availability = inventoryClient.checkAvailability(command.getItems());
validator.validate(order, availability);
order.submit();
orderRepository.save(order);
return order.getId();
}
}
这份输出未必完美,但方向上已经符合 DDD 的分层职责:事务边界在应用层,领域校验和状态流转在领域层,基础设施细节完全隔离。剩下的人工工作只是确认 InventoryClient 对应的是库存上下文还是 RPC 接口,以及是否要改成事件驱动。如果没有技能规则,AI 大概率会把这段逻辑写成一个 TransactionTemplate 包住的 300 行 Service。
4. 我实际跑起来的完整流程
4.1 环境准备:Cursor 和 Claude Code 的配置差异
如果你想复现,我建议拿 Cursor 先试。配置很简单:把 cleanddd-skills 目录放到项目的 .cursor/skills/ 下,或者在全局放到 ~/.cursor/skills/ 下。Claude Code 也支持类似机制,把目录放到 ~/.claude/skills/ 即可。
环境方面,我本机用的是 Java 17 + Spring Boot 3.x,AI 用 Cursor 内置的 Claude Sonnet 模型。没有单独接大模型 API,因为这类技能文件本质上是文本上下文,任何支持较长上下文和工具调用的模型都能跑。如果你需要团队共用,把目录放进 git 仓库,大家 pull 下来就是同一套 DDD 约束。
需要注意一点:AI 对 skill 的读取依赖模型对 description 的语义匹配。所以目录名和 SKILL.md 里关键词不要乱改,改成你项目里的业务黑话可能导致触发失败。
4.2 写一份合格的“需求输入”
技能包不是万能的,前提是你得给 AI 足够的需求信息。我建议按下面这个模板写输入:
- 领域:订单履约
- 核心业务:用户下单、锁库存、支付、发货、取消
- 关键规则:已发货订单不能取消;库存不足不能下单
- 约束条件:订单号和支付单号必须幂等
- 目标产出:DDD 分层代码 + 领域事件设计
信息越具体,AI 产出的模型越贴近实际。如果只丢一句“帮我写个订单模块”,模型也会触发技能,但生成的东西会非常泛化,因为需求本身没有领域约束。这个工作不能偷懒。
4.3 迭代生成时的校验清单
AI 生成初稿后,我不会直接合代码,而是按一张固定清单检查:
- 聚合根是否真正维护了一致性边界?还是只是一个数据容器?
- 领域服务是否只处理不属于任何聚合的行为?
- 应用服务是否只做编排放事务,没塞业务规则?
- 仓储接口是否在领域层,实现是否在基础设施层?
- 是否避免了在领域实体上直接使用 DAO 注解?
这套清单也是 cleanddd-skills 里 00-principles.md 的内容。我会把初稿发给同事做 review,重点讨论领域事件是否需要引入消息中间件。AI 能帮我们快速生成,但“是否要可靠投递、是否要最终一致性”这类架构取舍,还是需要人来定。
5. 常见问题与排查记录
5.1 AI 完全不触发 Skill,怎么办
最常见的问题是模型忽略了 SKILL.md。我一开始也遇到过,输入“请用 DDD 帮我设计订单模型”,结果它直接生成了一套普通的 Spring MVC 代码。排查下来发现是 description 里没有把“DDD”和“订单模型”同时放进去,语义匹配太弱。调整后,把 description 写成“当用户涉及领域驱动设计、DDD、聚合、限界上下文、领域事件、分层代码生成时,必须读取本 skill”,触发率明显上升。
5.2 聚合设计偏保守,容易退化成数据类
AI 生成的聚合根有时会退化成一个带着 getter/setter 的实体,行为很少。这种情况我会补一句提示:“请列出该聚合不变量,并把违反不变量会抛出的领域异常写出来。”AI 在输出前会重新思考聚合边界。
如果还是容易退化,我会在代码生成后,用 ArchUnit 加一条测试,规定实体类不能有 public setter。有了自动化约束,AI 生成再乱,CI 也会拦住一部分问题。
5.3 领域事件命名和事件内容不规范
模型生成的领域事件,经常出现 OrderUpdated、OrderChanged 这种无业务语义的事件。技能文件里明确要求:事件必须使用过去时,并且名称要体现业务结果,比如 OrderSubmitted、InventoryLocked、PaymentCompleted。另外事件内容不要直接序列化整个实体,应该放 OrderId、ItemIds、总金额等必要数据。这个规则我会在 review 时重点检查,事件设计差一点,后面接消息队列会非常痛苦。
5.4 与现有架构冲突时不要硬套 DDD
如果你在改造一个遗留系统,不要想着一步到位让 AI 把所有代码都重构成 DDD。cleanddd-skills 支持局部应用,我建议先划定一个核心业务域,比如订单、支付,先把核心域的边界建出来,其它模块暂时走老架构,中间用防腐层隔离。AI 技能包里单独有 anti-corruption-layer.md,专门处理新旧模型之间的转换。
6. 一些想法与小心得
我在实际使用中最深的一个体会是:AI 生成 DDD 代码,最大的价值不是“快”,而是它强迫你先把需求和规则说清楚。很多项目落地 DDD 失败,根本原因不是开发不会写优雅代码,而是业务方和技术方对“订单已取消意味着什么”这种基本概念都没对齐。使用 cleanddd-skills 之后,我必须把需求写成明确的事件和规则,AI 再据此设计模型,这个“先对齐再开写”的流程本身,就是 DDD 最需要的东西。
最后再分享一个小技巧:把技能包里的文件当作团队培训材料。每次让 AI 生成代码前,可以带着新同事看一遍事件风暴和聚合设计规则,比翻《领域驱动设计》那本书快得多。当然,书还是要读,但先让 AI 把骨架搭好,再拿着具体代码去对照书里的概念,学习曲线会平缓非常非常多。
