1. 模块边界设计核心目标
1.1 什么是模块边界
模块边界是软件系统中不同组件之间的明确分隔线,它定义了每个模块对外暴露的接口、隐藏的内部实现以及与其他模块的交互规则。一个清晰的模块边界应满足以下三个特征:
- 可识别:边界在代码结构、文档和架构图中一致可见,不存在隐式依赖。
- 可验证:能够通过静态分析或测试手段明确判断某个跨越边界的行为是否被允许。
- 可变更:模块内部的实现可以在不修改边界契约的前提下自由替换或重构。
边界 ≠ 文件分割 —— 模块边界是逻辑契约的集合,而非物理文件的划分。同一个模块可以分布在多个文件中,只要对外接口一致即可。
1.2 模块边界设计的三大核心目标
| 核心目标 | 说明 | 成功度量指标 |
|---|---|---|
| 明确模块边界定义 | 每个模块拥有显式的入口、出口、数据格式和错误处理约定 | 模块间依赖图无环形;接口文档与实现一一对应 |
| 降低耦合,提升可测试性 | 模块之间的交互仅通过稳定接口进行,避免共享全局状态或内部数据结构 | 模块可独立进行单元测试(Mock 外部依赖≤1 层) |
| 保障模块独立演进 | 任一模块的升级、替换或废弃不会导致其他模块被迫修改 | 每次模块迭代平均影响的模块数≤0.3 |
经验基准: 当单个模块的修改导致两个以上其他模块必须同步修改时,模块边界已经退化,需要重新审查接口抽象与职责分配。
1.3 模块边界的层次结构
一个典型的组件化系统中,模块边界存在于三个层次:基础设施层(技术细节)、领域层(业务逻辑)和应用层(协调编排)。每个层次有独立的边界职责与依赖规则。
- 基础设施层:对外提供数据持久化、网络通信、文件存取等通用能力;边界抽象为接口(如 Repository、Client)。
- 领域层:封装核心业务规则与实体行为;边界通过领域事件、领域服务接口与其他模块协作。
- 应用层:编排领域层和基础设施层完成用户用例;边界以 UseCase、Command/Query 形式定义。
1.4 如何识别边界退化信号
当模块边界设计失效时,团队通常能观察到以下“坏味道”。尽早识别这些信号有助于及时调整边界设计:
- 跨模块类型共用:一个模块直接 import 另一个模块的内部类、内部函数或全局变量。
- 循环依赖:模块 A 依赖模块 B,模块 B 又依赖模块 A(直接或间接)。
- 大规模修改牵连:一次简单业务变更导致 4 个以上的模块同时修改文件。
- 测试困难:为了测试一个模块,需要搭建完整的外部环境(数据库、消息队列、外部服务)。
- 接口膨胀:单模块对外提供的接口超过 15 个,或接口参数超过 5 个。
⚠ 危险信号——共享内部模型
当两个模块通过共享同一个内部数据结构(如数据库实体类、内部 DTO)进行通信时,边界已经名存实亡。任何内部字段的变更都会引发连锁修改,单元测试也将变得脆弱不可控。
当两个模块通过共享同一个内部数据结构(如数据库实体类、内部 DTO)进行通信时,边界已经名存实亡。任何内部字段的变更都会引发连锁修改,单元测试也将变得脆弱不可控。
1.5 起始阶段的边界定义验收清单
在模块边界设计初期,团队可以对照以下清单进行快速自检:
| 编号 | 验收条目 | 状态 |
|---|---|---|
| 1 | 每个模块是否拥有唯一的公开接口集(禁止直接暴露内部实现) | □ 是 □ 否 |
| 2 | 模块间数据交换是否只通过 DTO 或值对象(禁止共享实体) | □ 是 □ 否 |
| 3 | 依赖方向是否严格单向(外层依赖内层,禁止反向) | □ 是 □ 否 |
| 4 | 模块是否可以不依赖运行时容器(如 Spring、IoC)独立进行单元测试 | □ 是 □ 否 |
| 5 | 是否有显式的边界文档(接口契约、数据格式、错误码约定) | □ 是 □ 否 |
以上清单应在每个迭代的架构评审中重新确认。一旦出现 2 个以上的“否”,模块边界应立即启动重构或重新设计。