Monorepo 部署策略:实用指南
在 Monorepo 中管理部署并非易事。了解高效构建、智能缓存以及服务独立部署的相关策略。
Monorepo(单一代码仓库)越来越受欢迎,这是有充分理由的:它简化了依赖管理,支持跨服务的原子化变更,并改善了代码复用。但它也带来了部署方面的挑战。
当只有一个服务发生变更时,如何避免重新构建所有内容?如何管理各自独立的发布节奏?本指南将介绍我们在 Virex 总结出的实用策略。
Monorepo 的部署挑战
在传统的多仓库模式下,每个仓库都有自己的 CI/CD 流水线,变更只会触发对应服务的构建。
Monorepo 打破了这一模式。一次提交可能涉及多个服务,也可能一个服务都不涉及(例如文档变更)。简单粗暴的做法会导致:
- 不必要的漫长构建时间
- 计算资源的浪费
- 更慢的反馈循环
策略一:受影响包检测
第一项优化是检测哪些包真正发生了变更。大多数 Monorepo 工具都支持这一功能:
# Turborepo
turbo run build --filter=...[origin/main]
# Nx
nx affected --target=build
# Lerna
lerna run build --since=origin/main
这种方式只会构建发生变更的包,以及依赖于这些变更包的包。
Virex 的处理方式
当你把一个 Monorepo 接入 Virex 时,我们会自动检测你使用的包管理器和 Monorepo 工具。我们的构建系统会:
- 分析提交差异
- 确定受影响的包
- 只构建必要的内容
- 缓存其余所有内容
这通常可以将构建时间缩短 60%—80%。
策略二:智能缓存
缓存对 Monorepo 的性能至关重要,但缓存失效向来是出了名的困难。
对缓存进行分层
我们推荐三层缓存策略:
依赖缓存:基于锁文件哈希缓存 node_modules。这部分很少变化,节省的时间也最多。
构建缓存:基于源文件哈希缓存构建产物。Turborepo 和 Nx 会自动处理这部分。
部署缓存:缓存部署产物。如果没有任何变更,则完全跳过部署。
远程缓存
在 CI 环境中,每次构建都从零开始,本地缓存发挥不了作用。远程缓存解决了这个问题:
- 构建产物存储在共享缓存中
- CI 任务直接拉取缓存结果,而无需重新构建
- 团队所有开发者共享同一份缓存
Virex 为所有 Monorepo 部署提供内置的远程缓存能力。
策略三:独立部署
并非每个服务都需要一起部署。事实上,把部署耦合在一起会带来不必要的风险。
服务边界
在服务之间定义清晰的边界:
apps/
web/ # 主网站
dashboard/ # 管理后台
api/ # 后端 API
packages/
ui/ # 共享组件
utils/ # 共享工具函数
每个应用都可以独立部署。共享包发生变更时,则触发依赖它的应用重新构建。
部署配置
在 Virex 中,你可以在同一个 Monorepo 里配置多个部署目标:
{
"deployments": [
{
"name": "web",
"root": "apps/web",
"buildCommand": "turbo run build --filter=web"
},
{
"name": "dashboard",
"root": "apps/dashboard",
"buildCommand": "turbo run build --filter=dashboard"
}
]
}
每个部署都拥有独立的 URL、环境变量和部署历史。
策略四:预览部署
预览部署在 Monorepo 中更有价值。当一个 PR 涉及多个服务时,你会希望预览所有这些服务。
关联预览
Virex 会创建相互关联的预览部署:
- 每个受影响的服务都有自己的预览 URL
- 各服务被配置为与彼此的预览环境通信
- PR 页面会在一处集中展示所有预览链接
这样审查者就可以测试完整的变更,而不只是一个个孤立的片段。
常见陷阱
陷阱一:过度耦合
代码放在同一个 Monorepo 中,并不意味着它们就应该紧密耦合。要在服务之间保持清晰的接口。
陷阱二:忽视构建性能
Monorepo 的构建可能会变得异常缓慢。要尽早投入缓存和受影响包检测,不要等问题出现再补救。
陷阱三:共享配置漂移
各服务的配置很容易逐渐产生差异。应在代码检查、TypeScript 和测试方面使用共享配置。
快速开始
如果你要把 Monorepo 部署到 Virex:
- 接入你的代码仓库
- 我们会自动检测 Monorepo 结构
- 为每个应用配置部署目标
- 推送代码,见证效果
需要帮助?我们的文档详细介绍了 Monorepo 的配置方法,你也可以联系我们的支持团队。