在我们开始之前,让我先介绍一下我的背景,这样您就可以更好地了解我对Monorepo的想法。
我在一家IT服务机构负责技术工作。我构建的大多数产品都使用微服务架构,有多个前端(Web和移动)。我最近开发的最大的产品有近30个微服务,1个用React编写的Web客户端,以及使用React Native构建的原生移动应用程序。这些数字与大型产品公司分享的数字相去甚远。
与微观服务相比,我更喜欢宏观服务。我认为大多数产品不需要超过10个微服务。
我之所以明确说明我属于IT服务领域,是因为我们在软件开发上消费的大部分东西都是由产品公司的工程师和高级技术人员编写的。他们写和分享的东西是基于他们在工作中面临的真正问题和挑战。有时这些问题会与其他软件工程师在工作中面临的问题产生共鸣,但有时它们是我们没有的问题的解决方案。因此,我们必须从我们问题的角度来看待这些解决方案。
这篇文章是基于我在构建软件、领导和管理软件交付团队,以及从使用Monorepos的工程师撰写的优秀文章中学习的经验。有关monorepos的好资源,请参阅参考部分。
Monorepo是一种软件开发策略,其中单个版本控制存储库具有多个项目、库和应用程序的源代码,而与它们的编程语言无关。此外,使用Monorepo策略的组织通常使用一个通用的构建工具(如Bazel、Pants、Buck)来管理所有源代码。一些采用Monorepo策略的组织的流行示例是Google、Facebook、Twitter、Microsoft和Uber。
Monorepo的替代方案是polyrepo/multirepo。在multirepo中,每个组件都有单独的版本控制存储库。这是大多数组织用来构建代码结构的通用策略。在我看来,这在很大程度上是由微服务架构风格和小模块移动推动的。
正如论文[1](单一存储库的优点和缺点-Google的案例研究)中提到的,Monorepos具有以下属性:
完整性:repo中的任何项目都只能从也签入repo的依赖项构建。依赖项是未版本化的;项目必须使用位于repo头的依赖项的任何版本。
标准化:一组共享的工具控制着工程师如何与代码交互,包括构建、测试、浏览和审查代码。
我的理解是,要成功使用monorepo,您必须满足所有属性。否则,您将无法从monorepo中获得预期的好处。
许多大型产品组织更喜欢Monorepo是有充分理由的。主要原因如下:
您可以轻松地依赖Monorepo中的其他项目/模块,而不需要Nexus、ArtiFactory等工件管理工具。
你避免了钻石依赖问题。当项目有两个依赖于同一基础库的依赖项时,就会发生菱形依赖项。当开发人员升级依赖项时,他们会冒着打破依赖关系图中菱形的风险。
通过使用集中方式管理版本号,可以更容易地将所有依赖项保留在同一版本上。
使用单个构建工具可以进一步简化这一过程。我没有用过巴泽尔、巴克斯或裤子。我在Twitter Monorepo Travel上看了一场演讲,他们在会上谈到Gradle对于他们的用例来说太慢了。就我构建的应用程序的规模而言,Gradle运行得很好。
Monorepo的第二大好处是开发人员可以跨项目共享代码。通过使用monorepo可以更容易地在代码库中实施最佳实践。另一个相关的问题是,使用monorepo,我们最终不会创建竖井。这在企业设置中很重要,因为它会导致推卸责任,并导致错误从边界的裂缝中落下。在我使用Multirepo设置的经验中,人们只关心他们的微服务是否运行良好。他们忽略了通过集成软件和协作来实现价值这一点。在IT服务组织中,有更多的官僚作风和熟练开发人员的不均匀分布,随着多资源设置,问题会非常迅速地扩展。是的,我知道这是一个文化问题,但大多数IT服务机构不能烧掉投资者的钱来建设这种文化。
这是我在阅读关于莫诺波的文学之前没有意识到的。在一次提交中看到相关更改有很多好处。如果您正在处理一个需要在多个组件中进行更改的故事,那么在多存储库场景中,您将必须查看多个存储库中的更改,并按一定顺序合并PR,以便您处于健康状态。使用monorepo,您可以省去尝试跨多个存储库协调提交的痛苦。而且,这会带来更好的代码审查,因为所有更改都在一个地方。
这与原因3相关。使用monorepo,您可以在一次提交中重构API及其所有调用方。您可以在单个位置查看API的所有用法,这比使用Multirepo要容易得多,在Multirepo中,您甚至可能没有签出所有代码。根据我在Multirepo设置方面的经验,大多数开发人员不会让所有的repo都随着上游的更改进行更新。Monorepos支持在全局级别上持续改进,就像您在本地级别上所做的Multirepo一样。
在我工作过的一些组织中,您必须创建ServiceNow票证才能创建存储库。可能需要几天时间才能获得空的存储库。使用monorepo,您不必经历这种痛苦。
没有什么是免费的。总是需要权衡取舍。作为一名软件工程师,您的工作是弄清楚优势是否比取舍更重要。
Monorepos可能会因为缓慢的构建时间、糟糕的工具和合并冲突而减慢开发人员的速度。
这涉及到认知开销,因为开发人员必须适应比使用Multirepo设置大得多的代码库。
要做好Monorepo,需要对工具进行投资,而大多数组织的非技术领导层将无法理解这一点
在我谈论我对Monorepo的看法之前,让我们先了解一下IT服务组织的三个主要限制。
我们与多个客户一起工作,因此我们不能将所有客户的代码保存在同一个存储库中,即使我们将他们的代码托管在我们的版本控制中也是如此,原因很明显。此外,由于安全和IP相关问题,我们不能将所有存储库的访问权限授予所有开发人员。因此,我们将继续集中讨论如何管理单个客户的回购。
IT服务组织的初级工程师(<;5年)与高级工程师(>;10年)的比例很高,介于10:1到100:1之间,在较大的IT服务组织中可能更高。我提出这一点的原因是,Monorepos需要纪律,如果没有高级工程师使用定义良好的过程驱动它,就很难实现这一点。
考虑到上述两个限制和monorepos的缺点,monorepos似乎不适合我们。但是,我认为软件交付团队面临的真正问题可以通过monorepos来解决。
我们为不同的客户生产产品。这些产品通常遵循微服务架构,具有多个前端-Web和移动、功能测试、用于部署自动化的脚本。在多存储库策略中,您将至少创建5个存储库-1个用于所有微服务的后端,1个用于SPA前端,1个用于移动存储库,具体取决于您是构建纯本机还是使用某些本地框架(如Reaction Native或Ffltter),1个用于功能测试,1个用于部署自动化脚本。但通常情况下,您的团队将为每个微服务使用一个存储库,那么只有上帝知道您最终会创建多少存储库。
让我给你讲一个真实的故事。我曾经与一个客户一起工作,该客户的版本控制系统中有1000多个存储库。他们使用的是GitLab版本控制平台。他们有5个产品,每个产品都由多个微服务组成。我问他们的第一个问题是帮助我们了解哪些所有服务及其各自的代码存储库都是产品A的一部分。他们的首席架构师必须花一天时间弄清楚构成产品A的所有存储库。花了一天时间之后,她仍然不确定自己是否已经涵盖了所有服务。
让我们讨论一下我在使用多资源策略的软件交付团队中遇到的问题。我只想重申,这些问题都是在单个客户的背景下出现的。
缺乏责任感:人类擅长创造边界和竖井。他们不在乎那些边界之外会发生什么。他们不关心更大的图景。
版本漂移。10个不同的Spring Boot版本,3个不同的JDK版本,多个Reaction版本,天知道有多少个不同版本的库。
项目的健康状况。对所有项目使用相同的工具。代码一致性。单一的真理来源。
当您使用multirepo时,所有内容最终都会变成一个单独的存储库。很快你就看不清你有多少回忆录了。
是。对于单个客户,我们不必扩展到数百万行代码和1000个开发人员。对于单个客户,我们的代码不到一百万行,我们的交付团队不到100行。我们没有很大的版本控制历史记录,我们的开发人员一周提交的数量不到1000个。因此,我们远远低于谷歌和Facebook分享的数字。
我们已经在为我们的一个大客户这么做了。下面是我们用于新产品的单声道起动机回购(Mono Starter Repo)。