前言
随着项目的复杂程度增加,Monorepo 这个词汇也时常出现,可惜的是相关介绍资源非常不足,趁着近期较为闲暇的时间来统整记录一下我对 Monorepo 的理解。受到不同好文章与影片的启发,我想写一篇最直白的 Monorepo 解释,希望能够帮助更多人理解 Monorepo 的概念。
什么是 Monorepo?为什么需要它?
要了解 Monorepo 可以先从分析现有解决方案存在的问题点,再来延伸至 Monorepo 的解决方案。
Multi-Repository / Polyrepo(多仓库)
当创建一个新项目时大多数时候直觉的想法是「再创建一个新的 Git 仓库」,这样的做法在项目规模小的时候并不会有太大的问题,但随着项目规模的增大却会衍生出不少麻烦,像是不同的应用使用到相同的逻辑、类型、配置文件或套件时都会发生难以统一管理或更新的问题,如果要同步仓库共享的程序片段会需要特地发布套件,再由其他项目通过套件管理工具进行追踪更新。
这种做法并没有任何问题,只是显然并不适合作为更新频繁项目的解决方案:
- 需要特别发布套件
- 每次套件更新都要对「任何」依赖的项目更新
- 需要管理「所有」仓库的套件版本
- 需要在不同的仓库之间切换
- CI 流程较不方便
- 回溯系统状态困难
Monolith Repo(单体仓库)
换个角度,如果架构上所有相互依赖的仓库都放在相同的仓库当中不就没有问题了吗?这样的做法便是:「把任何项目都塞到同个仓库就完事」。 虽然有点反直觉,但确实解决了多仓库带来的问题!但这样的做法也会导致整个项目庞大且牵一发动全身,并且对技术选择僵化缺乏弹性(由于共享相同的套件管理)。
最终还是有扩展性、维护性和灵活度的挑战:
- 代码相互牵连可能导致合并冲突和协作问题
- 任何改动都需要重新测试或部署整个项目
- 套件管理缺乏弹性
- 项目庞大造成性能问题
- 权限管理问题
Monorepo(单一仓库)
与 Monolith Repo 很像,同样是「把任何项目都塞到同个仓库就完事」特别容易与 Monolith 的做法搞混,差异在于每个仓库在套件管理器当中还是视为独立的项目,这点可以透过套件管理器相关设置或是透过像是Nx 通过 TypeScript Project References。
这么做主要可以改善套件管理缺乏弹性的问题,将套件共同的依赖管理在根目录当中,但也同时允许每个项目有自己的独立套件,至于扩展性、维护性和灵活度的挑战还是要依靠其他工具来解决 😑。
Monorepo 延伸
基于 Monolith Repo 与 Monorepo 都有效能、权限、协作……等问题,因此通常会通过其他工具来解决这些问题,像是:rush、Nx、Turborepo,通过团队共享缓存避免重复执行、平行化执行、自动化依赖更新各式各样的功能来强化体验。
总结
Monorepo 优势
- 易于共享代码
- 简化依赖管理
- 微小更动变得容易
- 易于大型代码重构
- 易于团队跨项目合作
- 统一 CI 流程
Monorepo 劣势
- 额外学习与维护成本
- 性能问题
- 权限问题
Monorepo 与 Monolith Repo 的差异在于多套件管理的弹性,如果项目间频繁的有需要共享的程序片段或套件,那么 Monorepo 是一种不错可以考虑的解决方案,但如果项目间没有太多关联使用 Monorepo 反而会增加不必要的复杂度,考虑使用多存储库的方式。
延伸阅读
- 你很常听到 monorepo,但为什么要用 monorepo? - ExplainThis
- The Concept Of Monorepos - Ahmed Elsakaan
- Monorepos - How the Pros Scale Huge Software Projects // Turborepo vs Nx - Fireship
- monorepo.tools