模块依赖与版本约定
模块拆分的必然产物是依赖关系。没有规范的版本体系,微架构或模块化系统会迅速退化为“依赖地狱”。版本号是模块间通信的契约核心——它不仅标识了模块的快照,更定义了模块边界对外部消费者的承诺与约束。
4.1 语义化版本:模块边界的数字契约
语义化版本(SemVer, Semantic Versioning)将版本号划分为 MAJOR.MINOR.PATCH 三段格式,每段数字对应不同类型的边界变更。它将模块边界的稳定性预期显式化,使得消费者无需阅读源码即可判断升级风险。
| 版本类型 | 边界变更性质 | 消费者策略 |
|---|---|---|
| MAJOR(主版本) | 不兼容的公共 API 变更(边界断裂) | 必须重新评估适配,不能自动升级 |
| MINOR(次版本) | 向后兼容的功能新增(边界扩展) | 可无损升级,需验证新特性 |
| PATCH(修订号) | 内部缺陷修复(边界不变) | 可热修复替换,风险极低 |
零版本的陷阱: 主版本号
0.y.z 属于初始开发阶段,公共 API 不保证稳定。消费者依赖 0.x 模块时,应锁定精确版本(=0.2.0),否则可能因上游随意变更导致构建断裂。
4.2 版本约束与依赖解析
模块描述文件中的依赖条目通常使用版本约束范围,为自动更新提供弹性空间。常见的约束符号包括:
^1.2.0(兼容版本):允许 MINOR 和 PATCH 级别变化,即1.x.x且不低于1.2.0。~1.2.0(大致版本):只允许 PATCH 级别变化,即1.2.x。1.2.0(精确版本):锁定精确版本,不允许任何变化。>=1.2.0 <2.0.0(范围版本):复合条件区间。
依赖解析器会根据这些约束,在满足所有模块声明的条件下推导出依赖图,并将其结果固化到锁文件中。锁文件是消除环境差异的关键保障。
4.3 模块描述文件设计
模块描述文件是模块边界的“护照”。它必须向外界明确声明:模块的身份(name)、版本(version)、对外的公共接口(exports)以及自身的依赖(dependencies)。
{
"name": "payment-core",
"version": "2.3.0",
"dependencies": {
"risk-engine": "^1.5.0",
"notification": "~0.4.0"
},
"exports": {
".": "./api/v2/entry.js",
"./internal": "./api/v2/internal.js"
}
}
导出映射(Exports Map)是构建模块边界的先进实践:它阻止消费者访问模块内部文件,仅允许通过指定的入口点交互。任何试图突破导出映射引用内部路径的行为,都应被视为违反模块边界契约。
4.4 向下兼容策略
向下兼容不仅仅是版本号规则,它更多关乎行为契约。一个 MINOR 版本升级如果改变了错误码或返回值格式,即使 API 签名未变,也属于破坏性变更。
实践中,团队应遵循以下策略来维护边界稳定:
- 仅追加原则(Additive Only):所有新增功能通过扩展接口实现,绝不修改或删除已有的公共接口定义。
- 废弃过渡期(Deprecation Window):计划删除接口时,先在当前 MAJOR 版本中标记为废弃(Deprecated),并提供替代方案,下一个 MAJOR 版本才彻底移除。
- 适配器桥接(Adapter Bridge):当依赖的第三方模块发生不兼容 MAJOR 升级时,在己方模块边界上实现一层适配器,隔离外部变化对内部核心逻辑的冲击。
钻石依赖风险: 模块A依赖模块B和模块C,而模块B和模块C分别依赖不同 MAJOR 版本的模块D。这会导致依赖图无法解析,或者运行时加载了错误版本。解决方案是:要么统一升级所有使用方,要么在架构层通过服务化或动态加载隔离版本冲突。
4.5 落地步骤与验收标准
版本约定需要在组织层面强制执行,才能发挥边界契约的效力。以下是推荐的实施步骤:
- 制定团队 SemVer 公约:明确哪些属于公共 API(必须受版本约束),哪些属于内部实现细节。
- 标准化描述文件模板:所有模块必须包含
name、version、dependencies、exports字段。 - 集成依赖审查工具:在 CI 流水线中加入
dependency-check、cargo deny或类似工具,拒绝带有宽松通配符(*)或已知安全漏洞的依赖。 - 生成锁文件并纳入版本控制:锁文件(如
package-lock.json、Cargo.lock)确保所有环境获得同一份依赖树,是构建可复现性的基础。 - 建立 MAJOR 版本升级协调机制:任何模块的 MAJOR 版本升级,必须提前通知所有消费者,并提供升级指南与过渡期。
验收标准:
- 所有模块严格遵循 SemVer 规范进行版本号递增。
- 模块描述文件中禁止出现
*或>=0.0.0等无限宽松约束。 - CI 中存在 API 兼容性自动化测试,MAJOR 变更会触发流水线告警。
- 项目根目录下维护锁文件,并确保锁文件内容与描述文件声明一致。