通过 Git 托管和管理代码有两种主要策略:单版本(monorepo)和多版本(multi-repo)。两种方法各有利弊。
我们可以在任何语言的任何代码库中使用任何一种方法。无论是包含少量库还是数千库的项目,都可以使用其中任何一种策略。即使涉及的团队成员少则几人,多则上百人,或者您想托管私有代码或开源代码,您也可以根据各种因素选择使用 monorepo 或 multi-repo。
每种方法都有哪些优点和缺点?什么时候应该使用其中一种?让我们一起来了解一下!
什么是 repo?
repo (版本库的简称)是一个项目中所有变更和文件的存储空间,使开发人员能够在整个开发阶段对项目资产进行 “版本控制”。
我们通常指的是 Git 仓库(由 GitHub、GitLab 或 Bitbucket 提供),但这一概念也适用于其他版本控制系统(如 Mercurial)。
什么是 Monorepo?
monorepo 方法使用单个版本库来托管构成公司项目的多个库或服务的所有代码。在最极端的情况下,一个公司的整个代码库(跨越多个项目并使用不同语言编码)都托管在一个单一的版本库中。
Monorepo 的优势
将整个代码库托管在单个版本库中有以下好处。
降低入职门槛
当新员工开始为公司工作时,他们需要下载代码并安装所需的工具,才能开始执行任务。假设项目分散在多个资源库中,每个资源库都有自己的安装说明和所需工具。在这种情况下,初始设置将非常复杂,而且文档往往不完整,这就需要这些新团队成员向同事寻求帮助。
使用 monorepo 可以简化问题。因为只有一个位置包含所有代码和文档,所以可以简化初始设置。
集中管理代码
有了单一版本库,所有开发人员都能看到所有代码。它简化了代码管理,因为我们可以使用单一的问题跟踪器来观察整个应用程序生命周期中的所有问题。
例如,当一个问题跨越两个(或多个)子库,而错误存在于依赖库中时,这些特性就非常有价值。在多个版本库中,要找到发生问题的代码片段可能具有挑战性。
除此之外,我们还需要确定使用哪个版本库来创建问题,然后邀请并交叉标记其他团队的成员来帮助解决问题。
不过,有了 monorepo,无论是查找代码问题还是协作排除故障都变得更加简单。
无障碍的全应用程序重构
在创建应用程序范围内的代码重构时,多个库都会受到影响。如果您通过多个版本库托管它们,那么管理所有不同的拉取请求以保持它们之间的同步就会成为一项挑战。
使用 monorepo 可以轻松地对所有库的所有代码进行修改,并在单个拉取请求下提交。
更难破坏相邻功能
使用 monorepo,我们可以设置所有库的所有测试,以便在修改任何一个库时都能运行。因此,对某些库进行修改对其他库产生不利影响的可能性降至最低。
团队共享开发文化
尽管并非不可能,但使用 monorepo 方法,在不同团队中激发独特的亚文化就变得具有挑战性。因为他们共享同一个资源库,所以很可能共享相同的编程和管理方法,并使用相同的开发工具。
单版本库方法的问题
将我们所有的代码都放在一个版本库中有几个缺点。
开发周期较慢
当某个库的代码包含破坏性修改,导致依赖库的测试失败时,也必须在合并修改之前对代码进行修复。
如果这些库依赖于其他团队,而这些团队正忙于其他任务,无法(或不愿意)调整自己的代码以避免破坏性更改并使测试通过,那么新功能的开发可能会停滞。
更有甚者,项目很可能只能以公司中最慢团队的速度开始推进。这种结果可能会让最快团队的成员感到沮丧,为他们想要离开公司创造条件。
此外,一个库还需要为所有其他库运行测试。需要运行的测试越多,运行这些测试所需的时间就越长,从而降低了我们迭代代码的速度。
需要下载整个代码库
当 monorepo 包含一个公司的所有代码时,它可能会非常庞大,包含数千兆字节的数据。任何人都需要下载整个代码库,才能为其中的任何库做出贡献。
处理庞大的代码库意味着我们的硬盘空间利用率低,交互速度慢。例如,执行 git status
或使用 regex 在代码库中搜索等日常操作可能要比使用多个版本库时多花几秒甚至几分钟。
未修改的库可能是新版本
当我们标记 monorepo 时,其中的所有代码都会被分配新标记。如果该操作触发了新版本发布,那么该版本库中的所有库都将使用标签中的版本号进行新版本发布,即使其中许多库可能没有任何修改。
分叉更加困难
开源项目必须尽可能方便贡献者参与。有了多个版本库,贡献者就可以直接前往他们想要贡献的项目的特定版本库。但是,如果一个单源库承载多个项目,贡献者必须首先找到正确的项目,并了解他们的贡献会如何影响所有其他项目。
什么是 Multi-Repo?
Multi-Repo 使用多个版本库来托管公司所开发项目的多个库或服务。在最极端的情况下,它会将每一套最基本的可重用代码或独立功能(如微服务)托管到自己的版本库中。
Multi-Repo 的优势
将每个库独立于所有其他库托管,可带来诸多好处。
独立的库版本管理
标记一个版本库时,其整个代码库都会被分配 “new” 标记。由于资源库中只有特定库的代码,因此该库可以独立于托管在其他地方的所有其他库进行标记和版本控制。
每个库都有一个独立的版本,这有助于定义应用程序的依赖树,让我们可以配置使用每个库的哪个版本。
独立的服务发布
由于资源库只包含某些服务的代码,没有其他内容,因此它可以有自己的部署周期,与访问它的应用程序的进展无关。
服务可以使用快速发布周期,如持续交付(新代码通过所有测试后即可部署)。访问该服务的某些库可能会使用较慢的发布周期,例如每周只发布一次新版本的库。
帮助定义整个组织的访问控制
只有参与开发库的团队成员才需要添加到相应的资源库并下载其代码。因此,应用程序中的每一层都有一个隐含的访问控制策略。参与库开发的人员将被授予编辑权限,其他人可能无法访问资源库。或者,他们可以获得阅读权限,但没有编辑权限。
允许团队自主工作
团队成员可以设计库的架构,并在与所有其他团队隔离的情况下实施代码。他们可以根据库的总体情况做出决策,而不会受到外部团队或应用程序特定要求的影响。
多版本库方法的问题
使用多个资源库会产生几个问题。
库必须不断重新同步
当包含破坏性更改的新版本库发布时,依赖于该库的库需要进行调整以开始使用最新版本。如果库的发布周期快于其依赖库的发布周期,那么它们之间很快就会脱节。
团队需要不断追赶,以使用其他团队的最新版本。由于不同的团队有不同的优先级,有时可能很难做到这一点。
因此,跟不上的团队最终可能会坚持使用所依赖库的过时版本。这种结果会对应用程序产生影响(在安全性、速度和其他方面),而且不同库之间的开发差距可能会越来越大。
可能导致团队分散
当不同的团队不需要互动时,他们可能会各自为政。从长远来看,这可能会导致团队在公司内部形成自己的亚文化,如采用不同的编程或管理方法,或使用不同的开发工具。
如果某些团队成员最终需要在不同的团队中工作,他们可能会受到一些文化冲击,需要学习新的工作方式。
Monorepo vs Multi-Repo:主要区别
这两种方法的最终目标是相同的:管理代码库。因此,它们必须解决相同的难题,包括发布管理、促进团队成员之间的协作、处理问题、运行测试等。
它们的主要区别在于团队成员做决定的时间:单发布(monorepo)是在前期,多发布(multi-repo)是在后期。
让我们来详细分析一下这个想法。
在多版本中,所有库的版本号都是独立的,因此团队在发布具有破坏性更改的库时,只需为最新版本分配一个新的主版本号即可。其他小组可以让其依赖的库使用旧版本,并在代码调整后切换到新版本。
这种方法将何时调整所有其他库的决定权留给了每个负责团队,他们可以随时调整。如果他们做得太晚,而新的库版本又已发布,那么缩小各库之间的差距就会变得越来越困难。
因此,虽然一个团队可以快速、频繁地迭代代码,但其他团队可能无法跟上,最终导致产生的库出现分歧。
另一方面,在 monorepo 环境中,我们不能在发布一个库的新版本时破坏其他库,因为它们的测试会失败。在这种情况下,第一个团队必须与第二个团队沟通,以便将更改纳入其中。
这种方法迫使团队在必须对单个库进行更改时,必须对所有库进行调整。所有团队都必须相互沟通,共同达成解决方案。
因此,第一个团队将无法以他们希望的速度进行迭代,但不同库的代码在任何时候都不会开始出现分歧。
总之,多版本库方法有助于在团队中创建一种 “快速行动、打破常规” 的文化,让灵活的独立团队以自己的速度产出成果。相反,monorepo 方法更倾向于建立一种意识和关怀的文化,即团队不应该被抛在后面独自处理问题。
多-单混合版本库方法
如果我们无法决定使用多版本库还是单版本库,还有一种介于两者之间的方法:使用多个版本库,并使用一些工具保持它们的同步,使其类似于单版本库,但更具灵活性。
Meta 就是这样一种工具。它将多个版本库组织在子目录下,并提供一个命令行界面,可同时在所有版本库中执行相同的命令。
meta 仓库包含了组成项目的仓库信息。通过元克隆该版本库后,将递归克隆所有需要的版本库,使团队新成员更容易立即开始项目工作。
要克隆 meta 仓库及其定义的所有多个仓库,我们必须执行以下操作:
meta git clone [meta repo url]
Meta 会为每个仓库执行 git clone
,并将其放置在一个子文件夹中:
克隆 meta 项目 (图片来源)
从那时起,执行 meta exec
命令将在每个子文件夹上执行命令。例如,在每个版本库中执行 git checkout master
的操作如下:
meta exec "git checkout master"
单-多混合版本方法
另一种方法是在开发时通过 monorepo 管理代码,但在部署时将每个库的代码复制到其独立的资源库中。这种策略在 PHP 生态系统中非常普遍,因为 Packagist(Composer 的主要版本库)需要一个公共版本库 URL 才能发布软件包,而且无法指明软件包位于版本库的子目录中。
鉴于 Packagist 的限制,PHP 项目仍可使用单版本库进行开发,但必须使用多版本库方法进行部署。
要实现这种转换,我们可以使用 git subtree split
执行脚本,或者使用能执行相同逻辑的可用工具之一:
谁在使用 Monorepo 或 Multi-Repo
几家大型科技公司倾向于使用 monorepo 方法,而其他公司则决定使用 multi-repo 方法。
谷歌、Facebook、Twitter 和 Uber 都公开表示支持 monorepo 方法。微软运行着全球最大的 Git monorepo,用于托管 Windows 操作系统的源代码。
与此相反,Netflix、亚马逊和 Lyft 等著名公司则使用 multi-repo。
在多-单混合版本库方面,Android 更新了多个版本库,这些版本库的管理方式类似于单版本库。
在多-单混合版本库方面,Symfony 将其所有组件的代码都保存在一个 monorepo 中。他们将其拆分成独立的软件源进行部署(如 symfony/dependency-injection
和 symfony/event-dispatcher
)。
单版本库和多版本库示例
GitHub 上的 WordPress 账户提供了单版本库和多版本库方法的示例。
WordPress 的区块编辑器 Gutenberg 由几十个 JavaScript 包组成。这些包都托管在 WordPress/gutenberg monorepo
上,并通过 Lerna 进行管理,以帮助将它们发布到 npm 代码库中。
Openverse 是开放许可媒体的搜索引擎,其主要部分托管在独立的软件源中: 前端、目录和 API。
Monorepo vs Multi-Repo:如何选择?
与许多开发问题一样,您应该采用哪种方法并没有预先确定的答案。不同的公司和项目会根据其独特的条件从一种或另一种策略中获益,例如:
- 代码库有多大?是否包含数千兆字节的数据?
- 有多少人会在代码库上工作?是 10 人、100 人还是 1000 人?
- 有多少软件包?是 10 个、100 个还是 1000 个?
- 团队需要同时处理多少个软件包?
- 软件包的紧密耦合程度如何?
- 是否涉及不同的编程语言?是否需要安装特定软件或使用特殊硬件才能运行?
- 需要多少部署工具,设置起来有多复杂?
- 公司的文化是什么?是否鼓励团队合作?
- 团队知道如何使用哪些工具和技术?
小结
托管和管理代码有两种主要策略:单版本库(monorepo)和多版本库(multi-repo)。单发布(monorepo)方法是将不同库或项目的代码,甚至是一个公司的所有代码,都存储在一个版本库中。而多版本系统将代码划分为多个单元,如库或服务,并将其代码托管在独立的版本库中。
使用哪种方法取决于多种条件。两种策略各有优缺点,我们将在本文中一一详述。
关于单版本库或多版本库,你还有什么问题吗?请在评论区告诉我们!
评论留言