模块依赖与版本约定

模块拆分的必然产物是依赖关系。没有规范的版本体系,微架构或模块化系统会迅速退化为“依赖地狱”。版本号是模块间通信的契约核心——它不仅标识了模块的快照,更定义了模块边界对外部消费者的承诺与约束。

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(范围版本):复合条件区间。

依赖解析器会根据这些约束,在满足所有模块声明的条件下推导出依赖图,并将其结果固化到锁文件中。锁文件是消除环境差异的关键保障。

模块依赖解析流程与边界契约传导 设计时(模块边界声明) 解析时(依赖图构建) 运行时(确定性加载) 模块A(v2.0.0) 声明依赖:B@^1.0.0 模块B(v1.4.0) 模块C(v2.1.3) 锁定(B@1.4,C@2.1) 运行时环境 注意 锁文件消除环境差异, 确保所有环境拉取 同一组模块版本, 避免“在我机器上是好的”。

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 落地步骤与验收标准

版本约定需要在组织层面强制执行,才能发挥边界契约的效力。以下是推荐的实施步骤:

  1. 制定团队 SemVer 公约:明确哪些属于公共 API(必须受版本约束),哪些属于内部实现细节。
  2. 标准化描述文件模板:所有模块必须包含 name、version、dependencies、exports 字段。
  3. 集成依赖审查工具:在 CI 流水线中加入 dependency-check、cargo deny 或类似工具,拒绝带有宽松通配符(*)或已知安全漏洞的依赖。
  4. 生成锁文件并纳入版本控制:锁文件(如 package-lock.json、Cargo.lock)确保所有环境获得同一份依赖树,是构建可复现性的基础。
  5. 建立 MAJOR 版本升级协调机制:任何模块的 MAJOR 版本升级,必须提前通知所有消费者,并提供升级指南与过渡期。

验收标准:

  • 所有模块严格遵循 SemVer 规范进行版本号递增。
  • 模块描述文件中禁止出现 * 或 >=0.0.0 等无限宽松约束。
  • CI 中存在 API 兼容性自动化测试,MAJOR 变更会触发流水线告警。
  • 项目根目录下维护锁文件,并确保锁文件内容与描述文件声明一致。