什么是真正的单体仓库?深入解析Monorepo技术架构

本文深入探讨了单体仓库(Monorepo)的技术概念,包括原子提交、标准化目录结构、单一版本规则等核心特性,分析了其在大型软件公司中的实际应用场景和优缺点。

什么是真正的单体仓库?

在软件公司中,经常会有关于是否应该采用"单体仓库"的讨论,即"公司所有代码都存放在单一版本控制仓库中"。通常,人们基于这是Google存储代码的方式来做决定。

我在拥有非常先进单体仓库的公司(Google)和拥有非常先进多仓库系统的公司(LinkedIn)都工作过,我必须告诉你:人们与单体仓库关联的大多数有价值特性与你有多少个源代码仓库无关。

事实上,人们(和Google)认为的单体仓库实际上是多个不同的概念:

  • 跨项目的原子提交(因此有一个原子的"头"提交,对所有代码进行原子性前进)
  • 通用目录层次结构和所有源代码的单一视图
  • 检出或提交代码的单一位置(包括所有读取或写入内容的工具)
  • (有时)检出、提交和依赖的最小单位是文件
  • (通常)没有项目的概念,只有目录和文件的概念
  • (有时)单一版本规则:任何时候仓库中任何依赖只能有一个版本
  • 要求库维护者解决他们引起的问题的能力

跨项目的原子提交

假设我们有两个独立的项目A和B。我们想要进行影响这两个项目的更改。“单体仓库"的一部分是保证你可以同时原子性地提交到这两个项目。不存在项目A在提交#1而项目B在提交#2的仓库视图。

当你想要进行更改,而如果项目A或B没有同时更改就会被破坏时,这一点尤其重要。例如,假设我们有一个名为App的项目,它依赖于一个名为Library的项目。我们想要更改Library中函数的签名并同时更新App。如果我们只更新Library或只更新App,那么App就会被破坏。

这个特性最依赖于将内容放在单一源代码仓库中,因为实际上"仓库"的定义是"你可以原子性地提交多个文件的位置,它跟踪这些原子提交,并且你可以在该原子提交历史中的任何点检出”。

这个特性还意味着整个仓库有单一的"头"(最新提交)定义。这一点很重要,因为当开发人员从仓库检出时,他们通常在"头"处检出。这意味着当开发人员检出时,无论同时检出多少项目,他们都保证获得整个源代码树的一致视图。他们永远不必考虑是否以两个相互不兼容的版本检出了App和Library。

标准化的跨项目目录结构

单体仓库中的所有代码都被认为是在单一目录结构中。这在开发时和浏览代码时都有优势。

开发期间:检出是标准化的

在开发期间,如果项目A存储在仓库中的/path/to/project/A,项目B存储在仓库中的/path/to/project/B,当我同时检出它们时,它们将在彼此相邻的目录中。我可以保证这将是目录结构。如果我需要在开发期间让它们协同工作,我永远不必考虑应该将项目A放在磁盘上的什么位置与项目B相关。

统一的代码浏览方式

由于你有单一的目录结构,在代码搜索工具中浏览目录相对简单,并且可以使用搜索该单一仓库的单一代码搜索工具。

单一检出和提交位置

这似乎不重要,但单体仓库的价值之一是不必思考"我应该从哪个仓库检出?“相反,开发人员只需要考虑需要检出什么代码。类似地,所有提交都进入同一个仓库。

这也意味着你对整个历史的所有提交有单一视图,这有时会很有帮助(例如当你试图找出时间A和时间B之间可能改变的所有内容以进行调试时)。

最后,所有工具只需要担心访问单一仓库——它们只需要关心目录和文件名。

文件是检出、提交和依赖的最小单位

在大多数单体仓库中,你可以提交的、由版本控制系统跟踪的最小东西是文件。系统知道"一个文件"是改变的内容。

在一些单体仓库中,你还可以在不检出整个仓库的情况下检出单个文件。实际上,如果仓库变得非常大,这就成为一个非常重要的生产力特性。

此外,在一些单体仓库中(特别是Google的),依赖的最小单位是文件。这意味着构建系统可以知道一个文件依赖于另一个文件。

没有项目的概念

由于所有内容都在同一个仓库中,没有固有的概念表明不同目录的集合都可以代表单个"项目”。构建系统可能知道一些目录被编译在一起以产生特定的工件,但没有通用的方法可以通过查看目录结构等轻松看到这一点。

单一版本规则

通常,单体仓库会强制规定任何给定软件在仓库中同时只能存在一个版本。如果你签入库,在整个仓库中只能签入该库的一个版本。由于你有一个单体仓库,这最终意味着在公司中任何时候该库只能存在一个版本。

这样做有多个原因。

首先,它使推理系统行为变得更加容易。你总是理解你将获得依赖的哪个版本。

但也许这样做最重要的原因是大多数编程语言强制规定最终程序中任何特定依赖只能存在一个版本。否则,当你在程序中包含同一事物的多个版本时,它们在运行时会出现奇怪的行为。

让库维护者解决他们引起的问题

在单体仓库世界中,如果你拥有一个库,你可以通过签入与这些项目不兼容的内容来破坏每个依赖你的项目的构建。在单一版本世界中尤其如此,库所有者必须签入每个人都依赖的单一版本库。

这意味着库维护者不能仅仅强制他们的消费者完成升级到新版本库的所有工作。库维护者必须深入并自己完成工作。

总结

所以你可以看到,“单体仓库"实际上远不止是将所有东西放在一个源代码仓库中。有些人将所有这些事情组合在一起,因为上面基本上是对Google单体仓库的描述,大多数人在谈论"单体仓库"时似乎都在考虑那个系统。但分离这些概念很重要,因为其中许多可以在你今天拥有的系统中实现。另外,也许并非所有这些事情实际上都是好的,也许你应该有意识地决定要在业务中采用哪些。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计