最近在团队里推DDD,最常听到的一句话是:“模型图好看,落地就抓瞎。”这事我太有同感了。DDD难落地不是理念有问题,而是从业务语言到代码结构之间隔着一整条流水线,过程中全是需要耗费大量心力的转译和判断。也正是这份“重”,让“DDD落地难”成了微服务架构团队里最常见的叹息。
前阵子我接触到 cleanddd-skills 这套东西,思路很直接:DDD 那些标准动作,比如事件风暴、聚合拆分、防腐层设计、代码骨架生成,交给 AI 去干,人只负责拍板业务规则。听起来像偷懒,实际上是把 DDD 从“个人专家手艺”变成“团队可复制流程”。这篇文章我会用实际项目场景过一遍它的设计逻辑、完整使用路径、生成效果以及我踩过的坑,想用 AI 把 DDD 落地往前推一把的工程师,应该能从中拿到可以直接用的东西。
1. DDD落地的真实痛点:为啥大部分项目最后都变成了“伪DDD”
先说结论:大多数团队不是不知道 DDD 的好处,而是死在了从“建模”到“代码”之间的漫长转译上。DDD 本身提供的是一套思考框架,但它没有把手把手教你把每个概念变成代码的路径,于是项目刚开始时雄心勃勃,半年后全部退回 CRUD 加 Service 大杂烩。
1.1 模型与代码脱节:评审会说“好”,上线就变“烂”
我见过太多项目开过漂亮的建模工作坊,白板上画得清清楚楚,领域专家也在场,大家一致同意订单聚合根、商品值对象、库存领域服务的设计。可等真正进入开发阶段,问题全冒出来。今天一个紧急业务需要改订单状态,后天一个报表需求要跨多个聚合查询,大家没有耐心去维护那个精心设计的模型,直接在应用服务里把仓储干掉,写了个原生 SQL join。代码和模型很快就对不上了。
到上线一年后再回头做技术复盘,想照着当初的模型文档去维护代码,发现已经没有对应关系了。所谓“DDD架构”只剩下一堆空壳文件夹:domain 目录下有 Entity,但里面躺着几十个 getter/setter;application 目录下有 Service,但业务规则全写在里面,一个事务里处理五六个实体。这种“伪 DDD”比不用 DDD 还要糟糕,因为它给了团队一种虚假的架构安全感,后续维护时很难判断这块逻辑到底应该放哪一层。
深挖根因,关键瓶颈不是开发者不懂聚合,而是模型和代码之间缺少一个“持续校验”的反馈环。白板上的模型是一次性产物,代码才是持续演化的活物。没有工具持续督促模型对齐、没有自动化能力把模型结构映射成代码骨架,模型就注定会在开发压力下腐化。这正是 AI 可以切入的地方——它不是帮你做业务决策,而是帮你在每次改动时都保持模型与代码的对应。
1.2 事件风暴门槛高,不是每个团队都有领域专家坐镇
事件风暴是 DDD 里最经典的建模活动:一堆人贴便签,把业务事件、命令、聚合、限界上下文一步步理出来。但这个活动对参与者要求很高,尤其是那个能拍板业务规则的领域专家。现实中大多数团队根本没有专职领域专家,所谓业务人员可能是产品经理,也可能是懂一点业务的开发组长。让他们对“订单已提交”“库存已预占”这类领域事件做分类,他们自己都很难拿出准确说法。
更要命的是,事件风暴通常只有一两天,产出却是后续几个月开发的依据。会议一散,便签拍个照塞进 Wiki,没人再打开。后续开发时遇到边界模糊,谁敢去找业务对线?只好自己拍脑袋。于是模块边界、聚合划分、上下文映射全靠个人理解,每个人理解还不一致。
我也试过把事件风暴压缩成半天工作坊,请业务方聊核心流转,结果聊出来的都是接口字段和页面流程,不是领域规则。后来才意识到,让业务方直接讲“领域事件”是反直觉的,他们习惯讲操作步骤,不会讲状态变化。这里就需要有人能做一层转译,把操作步骤拆成领域事件,再组织成模型。这个转译工作AI特别擅长:它不需要“现场理解”业务,只要你把原始业务描述喂进去,它就能按照事件风暴的格式梳理出一版候选事件流,然后再由人筛选确认,效率完全不同。
1.3 聚合边界总是拍脑袋,防腐层最后都成了摆设
聚合边界设计堪称 DDD 落地里最考验功力的一环。一个订单要不要包含订单项?库存是一个独立聚合还是值对象?支付记录要不要直接挂在订单上?这些设计决策如果没有清晰原则,最终往往取决于“哪个方案代码写着方便”。
常见情况是,开发为了事务一致性把所有东西塞进一个大聚合,最后一张表几千个字段,任何修改都锁同一行,并发能力差到离谱。反过来,有人为了性能把聚合拆得太碎,一个业务操作跨四五个聚合,分布式事务满天飞,一致性无法保证。这两种极端都是一个原因:聚合的边界不是从业务不变量推导的,而是看心情定的。
防腐层就更惨了。设计的时候都同意“不能让外部系统模型污染领域模型”,但真做的时候,防腐层要么被忽略,要么变成了一个只知道转字段的工具类,调用链还是把外部 DTO 直接传进 domain 层。因为防腐层本身是“没有业务价值”的代码,它不像下单、退款那样能讲出故事,KPI 考核也没人看它。但它恰恰是 DDD 架构长期健康的护城河,丢了它,领域模型就会被各种外部协议的字段侵蚀。所以我说,DDD 落地难,难的不是概念,是没人把这些苦活累活持续做到位。而 AI,或者说 cleanddd-skills 这类技能包,能帮我们把这一整套流程变成标准化流水线。
2. cleanddd-skills的设计思路:把DDD重活拆给AI干
当我把“让AI干”挂在嘴边时,很多人第一反应是嗤之以鼻:“不就是一个提示词吗?让 AI 写 DDD 代码?”事实没那么简单。cleanddd-skills 确实依赖 AI,但它核心的设计思路,是把一个资深 DDD 架构师解决问题的完整路径固化成了 AI 可执行的工作流。它不只是一个会写代码的助手,更像一个 24 小时在线的建模搭档。
2.1 它的本质:一组可复用的DDD专家技能包
cleanddd-skills 本质上是“技能包集合”。你可以把它理解成给 AI 预装的一系列“专业能力模块”:包括事件风暴、聚合设计、限界上下文划分、领域模型生成、防腐层生成、代码骨架生成等。
每个 skill 内部包含几个部分:触发场景、执行步骤、输入输出格式、质量检查清单。比如事件风暴这个 skill,它的执行步骤大致是:先读取你描述的业务流程,识别关键状态变化,输出领域事件表;再根据事件归属划分聚合;然后识别命令和角色;最后生成一张带优先级的建模问题清单,让 ChatGPT 或 Claude 去提问,而不是一股脑给你结果。
这里有一个很关键的机制:skill 不是靠“大模型开悟”一次性输出,而是靠结构化的步骤约束模型逐步推理。和普通“帮我设计一个订单聚合”的 prompt 相比,step-by-step 的工作流能把“拍脑袋”变成“推导”,每一步都有依据。
2.2 和普通“AI写代码”提示词有什么不同
很多人用过“帮我写一个电商系统的 DDD 架构”,然后 AI 吐出来一堆带有 domain/application/infrastructure 三层的目录结构。看起来很标准,但深究一下,那个模型只是把“电商系统”四个字映射到泛型模板上,完全不含业务逻辑。这种生成的架构属于“绣花枕头”,看着像 DDD,实际上价值很有限。
cleanddd-skills 的区别体现在两点。
第一,它有建模前置环节。你在代码生成之前,AI 会先引导你描述业务流程、领域事件、规则约束,把领域知识榨干,然后基于领域事实做设计。普通 prompt 是“从关键词直接到代码”,skill 是“从业务描述到事件流到模型到代码”,然后到代码只是流水线最后一站。
第二,它维护了一整套“约束规则”。比如:实体和值对象的区分依据、聚合内引用规则、领域服务无法归入现有实体时才创建、应用层不直接依赖基础设施层。这套约束在生成代码的每一步都会被检查,减少了 DDD 的“形似神不似”。
从实际体验来说,普通提示词生成代码的正确率也就是“能用”,而用 skill 工程跑出来的结果,至少是“符合 DDD 原则且可评审”。这两者之间差着一个完整的架构评审环节。
2.3 工作流三段式:建模、映射、生成
我把 cleanddd-skills 的工作流简单归纳为三段式:建模 → 映射 → 生成。
建模阶段解决“领域模型是什么”。AI 作为一个结构化助手,会引导你输入业务故事,识别出领域事件、命令、聚合、限界上下文。这个阶段的核心产物是一份领域模型文档,它描述的是业务逻辑本身,不掺杂技术细节。
映射阶段解决“模型怎么映射到代码结构”。同一套领域模型可以映射到 Spring Boot、Java EDA、TypeScript 等不同技术栈。cleanddd-skills 在这个阶段会根据目标技术栈,确定代码目录结构、对象职责、依赖方向、事务边界。比如在 Spring Boot 下,它会把聚合根实现为 JPA Entity,值对象实现成 embeddable,领域服务实现成 Spring Bean,仓库实现为接口加 Implementation。
生成阶段才真正开始产出代码。AI 基于前两个阶段的模型和技术映射,生成领域模型、仓库接口、应用服务、防腐层、基础实现等完整代码。对工程师来说,前两个阶段才是决定架构质量的部分,最后一个阶段只是体力活。
这套设计的好处是,它把“决策”和“转译”分开了。人负责拍板业务决策,AI 负责把决策转译成模型、再把模型转译成代码。这也解决了开头说的“模型腐化”问题——因为模型一旦变更,重新跑一遍流水线,代码骨架就能同步更新,模型和代码始终保持对应关系。
3. 安装与上手:从一个真实业务需求开始
理论部分说了那么多,接下来进入实战。我用一个真实的业务需求来演示一下 cleanddd-skills 的使用流程。拿“订单履约”场景举例,这个场景足够典型,大家都能看懂,涉及的 DDD 元素也丰富。
3.1 环境准备和安装
cleanddd-skills 的使用前提是你有一个能跑 AI 对话的环境,以及一个支持加载外部技能包的客户端,一般是 Claude Desktop、知识库型 IDE 插件这类工具。安装步骤很简单:把 cleanddd-skills 项目克隆到本地,把 skills 目录下的技能包路径配置到 AI 客户端的技能加载目录,然后在对话中开启对应技能即可。
给第一次接触的人一个建议:别一上来就装全套技能包,先用核心的三个——event-storming、aggregate-design、code-gen。这三个技能覆盖了“建模到生成”的最短闭环。其他的比如 context-mapping、anti-corruption-layer,等玩熟了再逐步加。
我实际的安装配置大概长这样(伪配置):
# 克隆项目 git clone https://github.com/xxx/cleanddd-skills.git # 将 skills 目录加入 AI 客户端的技能路径 # 例如在你使用的客户端配置文件中加入: skill-path: ./cleanddd-skills/skills enabled-skills: event-storming, aggregate-design, code-gen这里有个容易忽略的坑:技能包加载之后最好新建会话验证一下。有些客户端会缓存技能列表,旧会话里可能无法触发新技能,导致你写了半天它也没走上 DDD 流程。实测下来,新建会话是最稳的。
3.2 用自然语言描述业务,让skill跑完第一轮建模
安装完成后,我不建议直接说“帮我设计订单履约 DDD”,那样表达信息量太少。正确做法是,像给一位刚入职的领域专家讲业务一样,把业务流程讲清楚。一个可参考的写法是:
我们要设计一个订单履约系统。核心流程如下:用户创建订单后,系统先做库存预占,然后调用支付网关收款,支付成功后订单流转到仓库,仓库进行拣货、打包、出库,出库后物流系统揽收并更新物流轨迹。用户可以在任意环节取消订单,但已出库订单取消需要走拦截流程。仓库库存不足时,订单进入缺货等待状态,到货后自动恢复履约。请基于以上业务启动事件风暴建模。
这里的关键是信息密度。你把状态变化、分支条件、超时规则、决策规则都写上,AI 才有足够素材提取领域事件。如果描述里只有“订单支付”四个字,模型设计必然单薄。
启动 event-storming 技能后,AI 首先会输出一份领域事件草稿。这段输出我建议不要直接当结论,而是当作“值得评审的候选清单”。
领域事件候选: 1. 订单已创建 2. 库存预占完成 3. 库存预占失败 4. 支付完成 5. 支付超时 6. 订单已取消 7. 订单已进入缺货等待 8. 订单已恢复履约 9. 订单已出库 10. 物流已揽收这段输出里,你会发现一个值得讨论的点:支付超时算不算领域事件?严格来说,“超时”是一种时间触发的状态变化,应该算,但它的归属聚合需要讨论。这类问题正好是人工介入评审的价值点。AI 不是替代你决策,而是把所有候选摆上台面,让你做更高密度、更高层次的决策。
3.3 关键产物一:事件风暴与限界上下文草稿
第一轮事件流出来后,skill 会继续引导你划界限上下文(Bounded Context)。这个环节它通常会用几个维度去判断:业务语义是否一致、变更频率是否相近、团队组织边界、性能吞吐要求等。
以订单履约为例,AI 会在事件流基础上把“订单创建与查询”“库存仓储控制”“支付结算”“物流履约”几个候选上下文梳理出来。
值得提醒的是,限界上下文划分没有一个“标准答案”。同一个电商场景,淘宝、京东、拼多多这类不同平台的边界设计都可能不一样。AI 给出的推荐是合理基线,你应根据团队组织架构进行调整。比如团队里只有一支后端队伍,那拆成 5 个上下文意义就不大,可以合并成 3 个模块,重点保住聚合边界和依赖方向。
这时候需要打开 context-mapping 技能,让 AI 输出上下文之间的关系图。它一般会用文本形式描述:订单上下文依赖库存上下文,支付上下文通过事件异步通知订单上下文,访问外部 WMS 时通过防腐层隔离。结合领域事件清单,你会得到一张“上下文关系表”,这比白板上画的更精细,因为每个依赖都标注了来源和理由。
3.4 关键产物二:聚合根候选清单与拆解理由
聚合设计是 DDD 里最富争议的部分。cleanddd-skills 在这步的输出逻辑是:先列出所有候选实体,再根据业务不变量和一致性边界分组,最后对每个组选出聚合根。
以订单履约场景为例,AI 会先列出订单、订单项、商品、库存、支付单、出库单、物流运单等候选。接着它会对“订单与订单项为何在一个聚合”给出理由:订单行的增删改必须保持总数一致,跨订单项的不变量要求在单个事务内完成,所以必须同聚合。而库存为什么不应该做成订单的子实体:因为库存没有强一致要求,允许超卖预占失败,并且库存事务边界通常独立于订单。
这里 AI 的设计决策完全是从业务规则推导的,推导链路上每一步都有依据。输出的聚合根候选长这样:
| 聚合根 | 包含实体/值对象 | 业务不变量 | 事务边界 |
|---|---|---|---|
| 订单 | 订单项、收件地址、金额明细 | 订单总金额 = 所有订单项金额之和 | 订单状态变更与订单项变更同事务 |
| 库存单 | 库存明细、预占单 | 库存预占数量不可超过实际库存 | 预占成功/失败影响订单状态但不跨聚合强一致 |
| 支付单 | 支付记录、退款单 | 支付总金额与订单应付金额一致 | 支付回调处理独立幂等 |
如果你发现 AI 给出的聚合边界和你的业务理解不一致,务必要在生成代码之前让它调整,因为聚合边界一旦固化到代码里,后续改动成本极高。这是整个技能流里人工介入权重最高的环节,宁可在这一步多花一小时,也别到代码阶段返工。
4. 实战演示:用订单履约场景跑通全流程
现在用订单履约场景完整走一遍从建模到代码生成的流水线。这部分你会看到 AI 产出的模型长什么样、生成代码的目录结构是什么样、以及人工需要在哪些地方做修改。
4.1 输入的业务描述
为了更接近真实开发,我加了一些边界条件进去,包括:用户下单时可以选择预占锁定库存还是下单后异步确认;库存不足时一旦补货自动恢复;已出库订单取消要走 WMS 回传拦截结果。规则越细,生成的模型越可信。
订单履约规则如下:
- 用户提交订单后可选择“库存锁定”或“支付后再确认库存”;
- 若选择锁定库存,则下单即预占库存,预占失败则订单进入冻结状态并提示库存不足;
- 若未锁定库存,则支付成功后才尝试占库,失败则自动退款并取消订单;
- 已支付订单可申请取消,若订单未出库则直接取消,若已出库则进入拦截流程,等待仓库回传拦截结果;
- 缺货等待的订单在补货入库后自动恢复履约,不需要人工介入。
这段描述的每一句话都对应了后续代码里的一个业务规则分支。输入质量决定了模型质量,这句话在 DDD 设计里永远成立。你可以看到,这些规则里其实已经隐含了多个聚合之间的交互方式,比如订单状态受库存结果和支付结果的双重影响。如果不对这些分支做显式建模,代码里百分之百会出现长长的 if-else 判断,写着写着就把领域规则丢进应用层了。
4.2 AI生成的领域模型(实体、值对象、聚合)
第一轮建模后,AI 输出了一组领域模型。我贴在下面的是精简版:
聚合根订单(Order)内部包含:订单项(OrderLine,值对象)、收件地址(RecipientAddress,值对象)、金额明细(MoneyAmount,值对象)、库存预占状态(StockReservationStatus,值对象)。
@Entity @Table(name = "orders") public class Order { @EmbeddedId private OrderId orderId; @Embedded private RecipientAddress address; @OneToMany(cascade = ALL, fetch = LAZY) @JoinColumn(name = "order_id") private List<OrderLine> lines; @Enumerated(EnumType.STRING) private OrderStatus status; @Embedded private StockReservationStatus reservationStatus; // 领域行为:只有当订单处于 PENDING_PAYMENT 且未锁定库存时,可以切换为支付后占库 public void confirmStockAfterPayment() { if (this.reservationStatus != StockReservationStatus.NOT_LOCKED) { throw new IllegalStateException("订单已锁定库存,不允许切换扣库方式"); } this.reservationStatus = StockReservationStatus.PAYMENT_CONFIRM; } }这段代码里有几个值得注意的设计点。Order 作为聚合根,它暴露的是领域行为方法 confirmStockAfterPayment,而不是直接暴露 setStatus、setReservationStatus 之类的操作。外部唯一能改变状态的方式,就是调用语义清晰的业务方法。这一层保护是 DDD 的核心价值所在。
仓库接口也生成了。它的接口只定义领域需要的方法,具体的数据库实现由 infrastructure 层提供。
public interface OrderRepository { Optional<Order> findById(OrderId orderId); void save(Order order); Optional<Order> findPendingRestoreOrders(); }这里你想让 AI 不要把所有查询都塞进 Repository,可以在技能里配置规则。默认情况下,订单查询、报表类方法会被拆到单独的查询服务,避免污染聚合仓库。这一点相当重要,因为报表需求会拖着一个巨型查询接口,最终导致聚合根变成一个数据访问门面。
4.3 生成的Spring Boot骨架代码与实际调整点
建模确认后,code-gen 技能会把模型映射成完整的 Spring Boot 工程骨架,目录结构大概是这样的:
order-service ├── application │ ├── service │ │ ├── OrderApplicationService.java │ │ └── OrderQueryService.java │ └── dto ├── domain │ ├── model │ │ ├── Order.java │ │ ├── OrderId.java │ │ ├── OrderLine.java │ │ └── OrderStatus.java │ ├── repository │ │ └── OrderRepository.java │ └── service │ └── OrderDomainService.java ├── infrastructure │ ├── repository │ │ └── OrderRepositoryImpl.java │ └── anticorruption │ └── WmsAdapter.java └── interfaces ├── controller └── event └── OrderEventConsumer.java对于这个结构,我要强调两个在实际项目中反复踩坑的点。
第一个是依赖方向。domain 层不应该直接引入 Spring 的注解或依赖。但 AI 默认生成的代码很容易把 spring-data-jpa 注解写在 entity 上,比如前面的 Order 类里就有 @Entity、@Table 这类标准 JPA 注解。如果你的团队非常洁癖,希望领域层完全与技术框架解耦,需要在技能里追加一条显式规则:领域层禁止使用任何框架注解,JPA 映射放到 infrastructure 层或者单独 mapping 类。
第二个是事务边界。AI 生成的应用服务里,事务粒度默认都是方法级别,也就是一个用例一个事务。这在大多数场景下是对的,但万一某个用例需要跨两个聚合,比如“支付成功且有锁定库存”的场景里既要改支付单状态又要改订单状态,这时事务边界要画在哪层,skill 本身无法替你决策,它只能按单聚合事务生成。真实生产里这类跨聚合一致性,比较常见的解法是引入本地消息表加事件驱动,而不是硬写在同一个事务里。这是你在评审代码时要重点看的地方。
5. 效果实测与踩坑记录:AI写的DDD代码有哪些坑
没有任何工具是银弹。我用 cleanddd-skills 跑通整个流程后,确实感受到了效率的提升,但也发现 AI 在 DDD 落地上有一些倾向性问题。如果不注意,这些坑照样会把项目带回“伪 DDD”。
5.1 最大收益:模型评审效率明显提升
先说收益,这也是我推荐这套玩法的根本原因。过去我们建模评审会,最痛苦的是大家对着白板“从一个对象聊到另一个对象”,没有结构。用 skill 跑出来的候选模型,把实体、事件、上下文、聚合边界、业务不变量都列成清单,评审会直接变成了清单评审会,效率和深度完全不同。
过去一次评审可能要 4 小时,大家最后在“报销单算不算一个聚合”上吵一小时。现在 AI 把候选设计摆出来,每个人直接对着候选提意见,十分钟之内就能收敛分歧。不是 AI 比人更懂业务,而是它把讨论对象从“空气”变成了“可反驳的草案”。在很多团队里,缺的就是这么一份“看起来已经很完善”的初稿,有了初稿,讨论才有的放矢。
另外一个隐藏收益是:新人也能参与架构讨论。之前如果对 DDD 理解不深,进评审会基本是听天书。现在 AI 给出的每个决策都带理由和业务不变量,新人读着这些理由就能快速理解模型设计思路,团队的整体 DDD 能力都在被托起来。
5.2 坑一:AI太爱设计“万能聚合”,需要人工砍
AI 生成聚合边界时有一种倾向:下意识把所有关联的对象都塞进同一个聚合,因为它本能地觉得“都要在一个事务里改”。比如营业额统计、促销活动规则、会员积分策略,这些都被 AI 试图挂在订单聚合上。结果就是聚合内对象膨胀,事务范围越拉越大,最终必然遇到并发锁冲突和性能瓶颈。
我的处理方式是,在大多数业务里保持一个原则:聚合尽量小而专一,优先保证不变量,而不是图操作方便。如果跨聚合的一致性无法避免,就显式用领域事件去处理,而不是扩大聚合边界。所以每次 AI 给出聚合设计后,我都会强制问它一句:如果这个聚合去掉某个子对象,哪些业务规则会破坏?如果不会破坏,就说明它可能不应该在这里。
实际项目中,订单聚合最后只保留订单项、金额、地址这类必须同生共死的对象,库存、支付、物流全部独立成聚合。这样做后,代码里少了跨聚合的大事务,事件驱动流程也变得更加清晰。
5.3 坑二:生成代码的包结构容易“串层”
AI 生成代码时,最容易出的低级错误是包结构串层。domain 层里出现 Spring 注解算一个,更常见的是 infrastructure 层里出现了业务规则。比如“库存不足时订单进入冻结状态”,这段规则逻辑被 AI 放进仓储实现里,因为实现里恰好有判断库存的代码。但严格说,这是领域规则,应该由 domain 服务或应用服务协同多个聚合完成,不应该沉到 infrastructure 层。
对付这个坑没有捷径,只能靠代码评审和静态检查。cleanddd-skills 自带的 quality-check skill 可以帮你做一轮自动检查,它会扫描生成的分层结构里是否存在依赖倒置违规、领域层是否出现框架依赖、聚合根是否暴露 setter 等。实测下来,它抓得还是比较准的,能过滤掉七八成明显问题。
但自动化检查永远替代不了人工判断。我推荐在每个模块生成完成后,指定一个“DDD守门人”角色做最终评审。他会提问:这条规则属于哪个对象的核心职责?如果这个对象被删掉,规则会怎么变化?这类问题能逼着开发者把散落各处的规则重新安放到正确位置。
5.4 坑三:依赖倒置执行过头,接口满天飞
另一个让我哭笑不得的坑是,AI 对“依赖倒置”执行得太彻底,生成了大量不必要的接口。每个领域服务、每个应用服务、每个仓储、每个集成点,都配了一个接口一个实现类。接口数量翻倍,没人知道哪些接口是真正为了抽象多态而存在,哪些只是为抽象而抽象。
代码里充满了这种结构:
public interface OrderQueryService { ... } public class JpaOrderQueryService implements OrderQueryService { ... } public interface InventoryCheckService { ... } public class CompositeInventoryCheckService implements InventoryCheckService { ... }这些接口并没有多个实现,也没有在未来需要多个实现的迹象,纯属增加理解成本和跳转路径。DDD 里的依赖倒置不是要求每个类都接口化,而是要求依赖方向从政策层指向机制层。对没有多实现需求的类,直接用具体类完全没问题。
我自己调整时,会把 AI 生成的接口实现二合一,只保留那些确实需要隔离技术细节的查询服务。比如访问 WMS 系统的防腐层肯定要独立接口,因为后面很可能会接第二家物流供应商;但订单查询服务这类纯内部逻辑,直接一个类结束战斗。
5.5 人工必须守住的几个检查点
从这套流程跑下来,总结出几个 AI 干不了、必须人工把关的检查点。首先是业务不变量。AI 可以从描述里提取出“金额必须相等”“状态不允许跳变”这类规则,但判断这个不变量是否真的需要强一致、是否可以容忍最终一致,只有业务方说了算。其次是聚合边界的最终裁决。AI 给出的是基于当前描述的推荐,但当业务规则存在竞争关系时,比如“库存要实时准确”和“库存要支持高并发”,这个取舍是 AI 无法替你决定的。第三是事件命名与语言统一。团队内部统一用“预占成功”还是“占用完成”,看起来是小问题,但术语不一致会造成技术人员和业务人员沟通时的语义错位,这个习惯得靠人维护。
把话说得更直白一点:AI 在这条流水线上其实是“执行者”和“初稿者”,它负责把你拍到桌面上的需求高效地转成结构化模型和代码。但业务决策者始终是人。用的时候别把 AI 的输出当成最终答案,而要把它的输出当成一个“值得认真讨论的候选项”。这个心态转变很关键,否则 A 用想法工程化,B 也会变成另一种形式的“伪 DDD”。
6. 团队落地建议:从个人玩具到团队协作
如果你自己把 cleanddd-skills 玩顺了,下一步自然是把它引入团队。这个东西从“个人效率工具”到“团队协作规范”之间,还有一段路要走,下面是我觉得比较实用的落地方案。
6.1 怎么把skill包接入团队规范
最推荐的做法是,把 cleanddd-skills 定义的核心产物纳入团队的架构设计文档模板。现在团队里很多需求是从 PRD 直接跳代码的,中间缺了领域建模环节。可以约定:涉及核心业务规则的模块,必须先跑一轮事件风暴建模,把领域事件表、聚合清单、上下文关系作为设计文档附件,随代码一起评审。
这个规范对 AI 的约束同样有效。团队代码仓库里可以维护一个 ddd-model 目录,存放每个模块的模型描述源文件,实现了什么?它实际上就是一份人机共同维护的活文档。模型变更时,更新源文件,然后重新跑一遍 code-gen 技能生成骨架代码,人工再把新逻辑合到业务实现里。这样模型和代码之间的关系从“各自演化”变成“同源同步”,实现了我开头说的反馈环。
6.2 与AI协作的正确姿势
和 AI 协作做 DDD 架构,姿势很重要。我见过翻车最厉害的同事,是因为他把 AI 当成可以全权委托的同学,做完建模直接照单全收。问题在于他缺少独立的架构判断力,AI 怎么建议他就怎么实现,遇到模型与业务摩擦时他不会调整,反而试图强行改业务去适配模型。这是本末倒置。
正确姿势是把 AI 当成一个“极其勤奋但需要明确指令的初级架构师”。给它输入业务时,尽量喂原汁原味的业务素材,而不是你已经加工过的技术方案。你越是只用技术语言告诉它“我要一个订单聚合”,它的设计空间就越窄,产出就越模板化。你越是把真实业务中的例外规则、分支条件、模糊冲突全面交代给它,它生成的模型就越贴合实际。
AI 模糊不清的地方,不要让它猜,马上追问它。比如“支付完成后库存预占失败,操作应发生在哪个用例中?”,让它在下一步生成代码前把条件补齐,避免模型带病进入编码阶段。
6.3 后续还能怎么扩展
沿着这条路再往前看,这套思路的扩展空间还很大。最直接的是把 cleanddd-skills 接到 CI 流程里,每次合并请求自动跑一个 DDD 结构检查,检查领域层是否出现外部依赖、聚合根是否有实体注入、防腐层是否被绕过。不用特别复杂,标准也可以从严格到宽松逐步配置,核心是让“模型腐化”在提交阶段就被发现,而不是上线半年后追悔莫及。
另一个方向是把建模产物和接口文档、测试用例联动起来。既然事件、命令、聚合边界都已经结构化了,理论上可以从模型直接生成接口定义、状态机测试用例,再和代码实现对照。这已经接近模型驱动开发的思路了。我目前也在试,等有足够样本了再写成更细的内容分享出来。
最后简单说说使用心得吧。踩过几次坑之后,我最大的体会是:DDD 的瓶颈向来不是“知识”而是“耐力”。大多数人不是不知道聚合原则,而是缺乏一次又一次把模型搬进代码、在细节中维持一致性的毅力。cleanddd-skills 的价值恰恰在于把这份“耐力活”自动化了,把人的精力从机械转译中释放出来,重新聚焦到业务判断上。只要你能守住业务决策这道闸门,让 AI 帮忙跑完从建模到生成的流水线,DDD 落地这件事,是真的可以往前跨一大步的。