用 AI 编码代理开发 DDK 项目
Claude Code、Codex 这类代理写代码很快,也很容易把分层写乱。DDK 生成的项目把约定写成代理会读的文件,把分层写成会失败的测试,再把失败结果整理成代理能照着改的报告,形成一个闭环。
生成的项目里有什么
用 archetype 或 scripts/ddk.sh new 生成项目后,根目录会多出这些文件:
order-service/
├── AGENTS.md 分层职责、编码约定、禁止事项、完成标准
├── CLAUDE.md @AGENTS.md,并提示优先使用 Skills
├── .claude/skills/
│ ├── ddk-add-aggregate/ 新增聚合
│ ├── ddk-add-use-case/ 新增用例
│ └── ddk-add-domain-event/ 新增领域事件
└── src/test/java/.../ArchitectureTest.java| 文件 | 谁会读 | 内容 |
|---|---|---|
AGENTS.md | Codex 等支持 AGENTS.md 的代理,也适合人读 | 每个包放什么、不放什么;聚合、用例、持久化的写法;提交前必须通过 mvn verify,不许删改 ArchitectureTest |
CLAUDE.md | Claude Code | 通过 @AGENTS.md 导入同一份约定,避免两份文件内容漂移 |
.claude/skills/* | Claude Code(其他代理可以当检查清单读) | 按本项目包结构写好的分步说明,与示例项目 ddk-example-user 的真实写法一致 |
三层骨架生成同样的 AGENTS.md 与 CLAUDE.md,Skill 合并为一个 ddk-add-feature。
反馈回路
代理按 AGENTS.md / Skill 写代码
│
▼
mvn verify ──── 通过 ────► 完成
│
失败
▼
target/archguard/violations.md 哪条规则 · 哪个类 · 怎么改
│
└──► 代理按 hint 修改代码(不删、不放宽规则)──► 再次 mvn verify关键在最后一步。ArchitectureTest 使用 ArchGuard.check:它会执行全部规则,把每条违规连同所在类和修复建议写进报告与断言消息里。代理不需要理解 ArchUnit 的规则描述,照着 hint 改即可。报告格式见 ArchGuard 架构守卫。
一次典型的协作
在生成的四层项目里,对代理说:
新增「订单」聚合:订单有待支付、已支付、已取消三种状态,只有待支付的订单能取消。
再加一个取消订单的接口,取消后发出订单已取消事件。对应 ddk-add-aggregate、ddk-add-use-case、ddk-add-domain-event 三个 Skill,工作会按这样的顺序展开:
- 在
domain.model写OrderId与Order,状态变更守卫写在Order.cancel()里; - 在
domain.acl声明OrderRepository,在infrastructure写 PO、Mapper、两个方向的转换器和仓储实现; - 在
application写命令、响应与应用服务方法,在adapter写请求对象与接口; - 登记
OrderCancelledEvent,在application.handler用AFTER_COMMIT订阅; - 运行
mvn verify。如果某一步把OrderPO引进了领域层,报告会指出Order违反了哪两条规则,代理改为通过仓储端口访问后重新验证。
一次真实的会话记录
下面是一次实际运行的记录,没有修饰。环境:用 DDK v0.3.0 的四层 archetype 生成 order-service,由 Claude Code 的子代理(Opus 5.5)在项目里完成上一节的任务,提示词就是那两句话,另外只要求它保留每一次 mvn verify 的输出。
读取 AGENTS.md、三个 Skill 的 SKILL.md、pom.xml、ArchitectureTest,以及 AGENTS.md 指向的用户示例
领域 domain/model OrderId、OrderStatus、Order(create / restore / pay / cancel,无 setter)
domain/error OrderError
domain/event OrderCreatedEvent、OrderCancelledEvent
domain/acl OrderRepository extends GenericRepository<Order, OrderId>
基础 infrastructure OrderPO、OrderMapper、两个方向的转换器、OrderRepositoryImpl、schema.sql
应用 application CancelOrderCommand、OrderResponse、OrderService.cancel、OrderEventHandler(AFTER_COMMIT)
接口 adapter CancelOrderRequest、OrderController:POST /orders/{id}/cancel
测试 OrderTest(7 个)、OrderApiTest(6 个)
验证 mvn verify 第 1 次即通过[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 -- in com.example.orderservice.adapter.controller.OrderApiTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 -- in com.example.orderservice.ApplicationTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 -- in com.example.orderservice.ArchitectureTest
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 -- in com.example.orderservice.domain.model.OrderTest
[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS几点观察:
- 一次通过,没有触发反馈回路。 新增 21 个文件,没有改动任何已有文件,
target/archguard/violations.md的内容是No architecture violations.。这次起作用的是AGENTS.md和 Skill 里写明的分层约定,架构测试只是确认了结果。 - 代理补了需求里没有的东西,并且都说明了。 订单金额与取消原因字段、
OrderCreatedEvent、让「已支付」状态可达的Order.pay();取消原因按 Skill 里的示例定为必填。这些是需要人确认的判断,不是架构问题,护栏管不到。 - 样本只有一次。 一个小任务一次通过,不能说明代理在更大的改动里也不会越界。
为了看到反馈回路的实际输出,我们在这次会话之后手工往 Order 里加了一个接收 OrderPO 的方法(这一步不是代理做的):
public static Order fromPo(OrderPO po) { ... } // 领域层引用了持久化对象mvn verify 随即失败,violations.md 报出 12 条违规,分属两条规则(节选):
# ArchGuard report
12 violation(s). Fix the code; do not delete or relax the rules.
## DOMAIN_MUST_NOT_DEPEND_ON_OUTER_LAYERS
**How to fix**: 领域层不得引用外层类型:需要的能力在 domain.acl 定义端口并由 infrastructure 实现,需要的数据以参数或值对象传入。
- `com.example.orderservice.domain.model.Order`: Method <...Order.fromPo(...OrderPO)> has parameter of type <...infrastructure.orm.po.OrderPO> in (Order.java:0)
- `com.example.orderservice.domain.model.Order`: Method <...Order.fromPo(...OrderPO)> calls method <...OrderPO.getId()> in (Order.java:85)另一条是 LAYERED_ARCHITECTURE_RULE,指向同一个方法。代理越界时读到的就是这份报告:哪条规则、哪个类、哪一行、怎么改。
为什么这样设计
- 约定写成代理会读的文件:口头约定和团队 Wiki 不会出现在代理的上下文里,
AGENTS.md会。 - 规则写成会失败的测试:代理会绕过建议,但绕不过红色的构建;同时明确写出「不许删改
ArchitectureTest」。 - 失败结果写成可执行的建议:ArchUnit 的原始描述面向规则作者,报告里的 hint 面向要改代码的人或代理。
- 一份约定,多个代理:
AGENTS.md是 Codex 等工具的约定入口,CLAUDE.md只做导入,Skills 是 Claude Code 的增强。
让代理调用你的服务
上面讲的是代理编写代码。反过来,代理也可以调用服务:引入 ddk-mcp-starter 后,适配层里标了 @McpTool 的方法会成为 MCP 工具。参数先经过 Bean Validation,业务异常以错误码返回,工具只能经由应用服务访问领域,由 MCP_TOOLS_MUST_RESIDE_IN_ADAPTER 规则约束。详见 MCP 工具。
DDK 仓库本身
DDK 仓库根目录同样有 AGENTS.md 与 CLAUDE.md,写明构建门禁(Spotless、JaCoCo、NullAway、ArchUnit)、starter 契约、公开与 internal 包的划分和提交规范,方便用 AI 代理参与 DDK 的开发。