依赖方向与架构测试
用途:让“依赖只能指向内圈”从口头约定变成 CI 可拦截的事实
为什么需要机器约束
「为什么需要机器约束」小节链接分层退化几乎总是从“临时 import 一下”开始:应用层直接引用 Mapper、领域对象加持久化注解、Controller 里写业务判断。每次都有理由,半年后边界只剩目录名。
架构测试把依赖规则写成断言,纳入构建流程,违反即失败。它是唯一能长期维持边界的机制。
常见断言清单
「常见断言清单」小节链接| 规则 | 断言示例 |
|---|---|
| 领域层不依赖基础设施 | domain 包不得依赖 infrastructure、adapter |
| 领域层不依赖技术框架 | domain 不得 import ORM、HTTP、消息客户端包 |
| 持久化对象不外泄 | *DO/*PO 只在 infrastructure 内被引用 |
| 上层不依赖下层实现 | app 只依赖 domain 的接口,不依赖 *Impl |
| 模块间不循环依赖 | 禁止包循环(cycle 数为 0) |
| 上下文隔离 | contextA 不引用 contextB 的内部包 |
| 命名与位置一致 | 以 Controller 结尾的类必须在 adapter 包 |
工具选择
「工具选择」小节链接| 语言/生态 | 工具 |
|---|---|
| Java/JVM | ArchUnit(JUnit 集成,最常用)、jdeps + 脚本、SonarQube 依赖规则 |
| TypeScript/前端 | dependency-cruiser、eslint-plugin-boundaries |
| Python | import-linter |
| Go | 包可见性 + 自定义 lint(depguard) |
| 通用 | 依赖图生成(Dependency Cruiser、jDepend、Structure101)+ 阈值检查 |
落地策略
「落地策略」小节链接- 先基线,再收紧:存量违规多时,把当前违规列表作为基线冻结,只禁止新增(棘轮策略),逐步减少。
- 与变更同批引入:新模块从第一天就有规则,成本最低。
- 失败信息可执行:断言消息写清“应该怎么改”(例:请通过 domain 的网关接口访问,不要在应用层引用 Mapper)。
- 规则数量克制:10 条核心规则胜过 60 条细节规则,后者会被绕过并失去信任。
- 可视化配套:生成依赖图放进文档,评审时对照。
与其他概念的关系
「与其他概念的关系」小节链接- 六边形/整洁架构:规则是这些风格的机器化表达。
- 限界上下文:跨上下文访问禁令是最有价值的一类规则。
- 数据所有权:配合数据库账号隔离,形成代码与数据两侧的双约束。
常见误区
「常见误区」小节链接| 误区 | 修正 |
|---|---|
| 规则写在文档里 | 必须进 CI |
| 一次上太多规则导致全红 | 基线冻结 + 棘轮收紧 |
| 只检查包名不检查类职责 | 结合命名与注解断言 |
为了通过测试滥用 //noinspection |
例外必须登记负责人与到期时间 |
延伸阅读
「延伸阅读」小节链接- ArchUnit 官方文档(Java 架构测试)
- Robert C. Martin《Clean Architecture》第 22 章(依赖规则)
- Michael Feathers《修改代码的艺术》(架构约束与遗留系统治理)