浅谈 monorepo 单体仓库
1 引言
在版本控制系统中,单体仓库是一种软件开发策略,其中许多项目的代码存储在同一个仓库中。截至2017年,这种软件工程实践的一些形式已经有十多年的历史,但一般概念只是最近才被命名。
Google、Facebook、微软、Uber、Airbnb 和 Twitter 都采用了非常庞大的单体仓库,以不同的策略来扩展构建系统和版本控制软件,代码量大,每天都在变化之中。
2 单体仓库的优势
与单个存储库相比,单一存储库有一些潜在的优势:
2.1 易于代码重用
类似的功能或通信协议可以被抽象成共享库,并直接被项目所包含,而不需要依赖包管理器。
2.2 简化依赖管理
在一个多仓库环境中,多个项目依赖一个第三方依赖,该依赖可能会被多次下载或构建。在monorepo中,由于被引用的依赖关系都存在于同一个代码库中,因此可以轻松优化构建。
2.3 原子提交
当一起工作的项目被包含在不同的仓库中时,发布需要同步一个项目的某些版本与另一个项目的某些版本。而在足够大的项目中,管理依赖之间的兼容版本可能会成为依赖的地狱。 在monorepo中,这个问题可以被规避,因为开发人员可以原子性地改变多个项目。
2.4 大规模代码重构
由于开发人员可以访问整个项目,因此重构可以确保项目的每一个部分在重构后继续发挥作用。
2.5 跨团队的协作
在使用源码依赖(从源码编译的依赖)的monorepo中,团队可以改进其他团队正在进行中的项目。这展示了灵活的代码所有权。
3 限制和缺点
3.1 丢失版本信息
虽然不是必须的,但一些monorepo构建在版本库中的所有项目上使用一个版本号。这将导致每个项目的转专有版本信息丢失。
3.2缺乏每个项目的安全性
有了分离的存储库,可以根据需要授予对存储库的访问权。单一项目允许读取项目中所有软件的访问权,可能会带来新的安全问题。
3.3需要更多的存储空间
使用拆分仓库,你可以只取你感兴趣的项目。
如果是单仓库,你可能需要获取所有的项目。
当然,这也取决于版本系统。
比如如果你使用SVN,这就不是问题,因为你可以下载repo的任何单独部分(甚至是单个目录)。
4 可扩展性挑战
拥有大型项目的公司在使用monorepos时遇到了一些障碍,特别是关于构建工具和版本控制系统的问题。 Google的monorepo,据推测是世界上最大的,符合超大规模系统的分类,必须在一个超过80TB的存储库中每天处理数以万计的提交。
4.1 缩放版本控制软件
很多公司发现,软件无法有效处理大型单体所需的数据量。
Facebook和微软分别选择了版本控制软件Mercurial和Git,而谷歌则最终创建了自己的版本控制系统。
十多年来,谷歌一直依赖托管在单机上的Perforce。2005年,谷歌的构建服务器一次可以被锁定10分钟。进过改进以后,Google在2010年将这一时间改进为30秒--1分钟。 由于扩展问题,Google最终开发了自己的内部分布式版本控制系统,被称为Piper。
Facebook遇到了版本控制系统Mercurial的性能问题,并对客户端做出了大量上游贡献,并在2014年1月使其比Git中的竞争解决方案更快。
2014年3月,微软宣布转而使用Git进行单机版的开发,在过渡过程中,微软对Git客户端做出了大量的上游贡献,通过Virtual File System for Git删除了不必要的文件访问,并改进了对大文件的处理。
4.2 缩放构建软件
很少有构建工具能在单体中很好地工作,而在签入时对整个版本库进行构建和持续集成测试的流程将导致性能问题,像Buck、Bazel、Pants和Please这样的定向图构建系统通过将构建和测试划分到开发的活动区域来解决这个问题。
Twitter在2011年开始开发Pants,因为当时Facebook的Buck和Google的Bazel都是闭源的,Twitter在2012年以Apache 2.0 License开源了Pants。
Please是一个基于Go的构建系统,由Thought Machine在2016年开发,他们也是受到谷歌的Bazel的启发,并对Facebook的Buck表示不满。
Bazel、Buck、Pants和Please,都使用相同的Starlark(原Skylark)构建语言,这是一种基于Python的特定领域语言。
一些专门的单体构建工具,如Lerna,可以解决重复依赖的获取,但缺乏有向图的能力。