DDK ArchGuard Starter
把分层约束写成可执行的 ArchUnit 规则。写在文档里的分层只是建议,写成测试才是约束——违反即构建失败。
能做什么
| 规则 | 约束 |
|---|---|
LAYERED_ARCHITECTURE_RULE | 四层:..adapter.. / ..ui.. → ..application.. → ..domain..;..infrastructure.. 只为实现领域端口而依赖领域层 |
THREE_LAYER_ARCHITECTURE_RULE | 三层:..adapter.. → ..business..;..infrastructure.. 实现业务层接口,不被任何层直接引用 |
DOMAIN_MUST_NOT_DEPEND_ON_FRAMEWORKS | ..domain.. 不依赖 Spring、MyBatis(-Plus)、Jackson、JPA |
DOMAIN_MUST_NOT_DEPEND_ON_OUTER_LAYERS | ..domain.. 不依赖应用层、适配层、基础设施层 |
DDK_INTERNALS_MUST_NOT_BE_USED | 应用代码不依赖 com.ddk..internal..。这些包是 starter 的实现细节,任何版本都可能变更 |
MCP_TOOLS_MUST_RESIDE_IN_ADAPTER | @McpTool 方法只能声明在 ..adapter..,工具只能经由应用服务访问领域 |
text
四层 三层
adapter ──► application ──► domain adapter ──► business
│ ▲ ▲
└──► infrastructure infrastructure- 所有规则在层为空时通过:刚生成的骨架、没有适配层的后台服务,不会因为「某层没有类」而失败。
- 三层规则不要求业务层框架无关:业务层合并了应用层,应用服务本来就带
@Service、@Transactional,约束的重点是依赖倒置。 - 两个 archetype 生成的项目都自带使用这些规则的
ArchitectureTest。
引入方式
xml
<dependency>
<groupId>com.ddk</groupId>
<artifactId>ddk-archguard-starter</artifactId>
<version>${ddk.version}</version>
<scope>test</scope>
</dependency>必须用 test 作用域:规则常量的类型是 ArchRule,本模块以 compile 作用域依赖 ArchUnit,不加 test 会把 ArchUnit 带进运行期依赖。
java
class ArchitectureTest {
private final JavaClasses classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.acme.order");
@Test
void architectureIsRespected() {
ArchGuard.check(classes,
CommonArchRules.LAYERED_ARCHITECTURE_RULE,
CommonArchRules.DOMAIN_MUST_NOT_DEPEND_ON_FRAMEWORKS,
CommonArchRules.DDK_INTERNALS_MUST_NOT_BE_USED);
}
}单条规则仍然可以用 rule.check(classes) 单独执行。
两个导入选项都不能省:
DO_NOT_INCLUDE_JARS:否则 classpath 上包名恰好落进层匹配的库类(DDK 自己的com.ddk.core.domain就是)也会被检查;DO_NOT_INCLUDE_TESTS:测试类经常为了构造场景而跨层引用。
违规报告
ArchGuard.check 会执行全部规则,不在第一条失败时中断,然后把结果写到 target/archguard/(可用 -Dddk.archguard.report-dir=... 覆盖):
| 文件 | 给谁看 |
|---|---|
violations.md | 人和 AI 编码代理:按规则分组,每条违规附带具体的修复建议 |
violations.json | 解析结果的工具和代理:每条违规包含 rule、class、detail、hint、ruleDescription |
断言失败的消息内容与报告一致,只看构建日志也能直接动手修。在生成的四层项目里放一个引用 PO 的领域类,实际输出:
text
Architecture violated: 2 violation(s). Fix the code; do not delete or relax the rules.
Report: target/archguard/violations.md
[LAYERED_ARCHITECTURE_RULE] com.acme.order.domain.model.Order
Field <com.acme.order.domain.model.Order.state> has type <com.acme.order.infrastructure.orm.po.OrderPO> in (Order.java:0)
How to fix: 依赖方向应为 adapter → application → domain,infrastructure 只实现 domain.acl 中的接口。……
[DOMAIN_MUST_NOT_DEPEND_ON_OUTER_LAYERS] com.acme.order.domain.model.Order
Field <com.acme.order.domain.model.Order.state> has type <com.acme.order.infrastructure.orm.po.OrderPO> in (Order.java:0)
How to fix: 领域层不得引用外层类型:需要的能力在 domain.acl 定义端口并由 infrastructure 实现,需要的数据以参数或值对象传入。- 违规所在类从 ArchUnit 描述中的代码单元解析:字段、方法会逐级去掉成员部分,直到命中被导入的类。
CommonArchRules之外的自定义规则标为CUSTOM,给出通用建议。- 检查通过时也会写出空报告,工具可以据此判断结果。
配置项
没有 Spring 配置项。规则是测试期使用的静态常量,本 starter 不包含任何自动配置;唯一的开关是报告目录的系统属性 ddk.archguard.report-dir。
规则是怎么被验证的
每条规则都对着一组测试夹具验证「该过的过、该拦的拦」,而不是只断言常量存在:
text
fixture
├── four/valid adapter → application → domain ← infrastructure 全部通过
├── four/violation domain.Order 持有 infrastructure.OrderPO 分层与外层依赖规则拦截
├── four/framework domain.Order 标注 @Component 框架无关规则拦截
├── three/valid adapter → business ← infrastructure 通过
├── three/violation business.AccountService 持有 infrastructure.AccountMapper 三层规则拦截
└── empty 没有任何分层包 四条规则全部通过不适用的场景 / 已知问题
- 层按包名匹配,包名不可配置。包结构不同的项目需要复制
CommonArchRules中的规则并修改definedBy(...)。 consideringAllDependencies()判定较严:字段类型、方法参数、注解、泛型参数、异常声明都计入依赖,存量项目第一次接入时通常会报出大量违规,需要ignoreDependency(...)或分阶段收敛。- 规则仍然偏少:没有包循环依赖检查、命名约定(Repository 接口只在领域层)等规则。
相关阅读
- 分层、应用服务与仓储、DDD 的边界不止一个:规则背后的依赖方向,以及字节码检查看不到的常量依赖