跳转到内容

依赖方向与架构测试

用途:让“依赖只能指向内圈”从口头约定变成 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)+ 阈值检查
  1. 先基线,再收紧:存量违规多时,把当前违规列表作为基线冻结,只禁止新增(棘轮策略),逐步减少。
  2. 与变更同批引入:新模块从第一天就有规则,成本最低。
  3. 失败信息可执行:断言消息写清“应该怎么改”(例:请通过 domain 的网关接口访问,不要在应用层引用 Mapper)。
  4. 规则数量克制:10 条核心规则胜过 60 条细节规则,后者会被绕过并失去信任。
  5. 可视化配套:生成依赖图放进文档,评审时对照。
  • 六边形/整洁架构:规则是这些风格的机器化表达。
  • 限界上下文:跨上下文访问禁令是最有价值的一类规则。
  • 数据所有权:配合数据库账号隔离,形成代码与数据两侧的双约束。
误区 修正
规则写在文档里 必须进 CI
一次上太多规则导致全红 基线冻结 + 棘轮收紧
只检查包名不检查类职责 结合命名与注解断言
为了通过测试滥用 //noinspection 例外必须登记负责人与到期时间
  • ArchUnit 官方文档(Java 架构测试)
  • Robert C. Martin《Clean Architecture》第 22 章(依赖规则)
  • Michael Feathers《修改代码的艺术》(架构约束与遗留系统治理)