单一存储库:请不要(第2部分)

大家好!

因此,关于单一存储库的承诺的新内容的新部分。 在第一部分中,我们讨论了Lyft(和之前的Twitter)一位受人尊敬的工程师的文章翻译,内容涉及单一存储库的缺点是什么,以及为什么它们几乎使这种方法的所有优点都得到了体现。 我个人基本上同意原始文章中提出的论点。 但是,正如我所承诺的,结束这一讨论,我想再说几点,我认为这更重要,也更切合实际。

我会告诉您一些有关我自己的信息-我在小型项目和相对较大的项目中都工作过,我在一个包含100多个微服务(和SLA 99.999%)的项目中使用了多仓库。 目前,我正在从事从maven到bazel的小型单一存储库(实际上不是前端js + java后端)的翻译。 不适用于Google,Facebook,Twitter,即 我不喜欢使用经过正确配置和调整的单一存储库。

那么,对于初学者来说,什么是单一存储库? 对原始文章的翻译的评论表明,许多人认为,单一的存储库是指所有5家公司开发人员都在一个存储库上工作并将前端和后端存储在其中。 当然不是这样。 单一存储库是一种将所有公司项目,库,构建工具,IDE插件,部署脚本以及所有其他内容存储在一个大型存储库中的方法。 这里的详细信息是trunkbaseddevelopment.com

当公司规模很小,而公司却没有那么多项目,模块和组件时,该方法称为什么? 这也是一个单一存储库,仅很小。
自然,原始文章说,所描述的所有问题都开始在一定程度上出现。 因此,那些认为自己的1.5挖掘器的单一存储库运行完美的人绝对是正确的。

因此,我要修复的第一个事实: 单一存储库是您新项目的一个很好的开始 。 将所有代码放在一个堆中,起初您只会获得一个优势,因为 支持多个存储库肯定会增加一些开销。

那是什么问题呢? 如原始文章中所述,该问题在一定程度上开始。 最重要的是,不要错过这样一个规模的时刻。

因此,我倾向于断言实质上出现的问题不是“将所有代码放入一个堆中”方法的问题,而是简单的大型源代码存储库的问题。 即 假设您为不同的服务/组件使用了多存储库,而其中一项服务变得如此之大(有多大,我们将在后面讨论),那么您很可能会遇到完全相同的问题,但也没有单一存储库的优势(如果它们当然有)。

那么,存储库应该有多大才开始出现问题呢?
绝对有2个指标取决于该指标-代码数量和使用此代码的开发人员数量。 如果您的项目有TB级的代码,但是有1-2人使用它,那么很可能他们几乎不会注意到问题(或者,即使他们注意到:)至少,不做任何事情会更容易:

如何确定是时候考虑如何改进存储库了? 当然,这是一个主观指标,很可能您的开发人员会开始抱怨某些不适合他们的东西。 但是问题是改变某件事可能为时已晚。 让我给您一些个人的看法:如果克隆存储库需要10分钟以上,如果构建项目需要20-30分钟以上,开发人员超过50个,依此类推。

个人实践中一个有趣的事实:
我在一个由大约50个开发人员组成的团队中从事相当大的工作,分为几个小团队。 开发是在功能早午餐中进行的,并且合并发生在功能冻结之前。 在其他6个团队在我面前冻结之后,我花了3天时间在团队分支合并中。

现在,让我们浏览一下大型存储库中出现的那些问题的列表(其中一些是在原始文章中提到的,而某些不是)。

1)资料库下载时间


一方面,我们可以说这是开发人员在初始设置工作站时执行的一次性操作。 就个人而言,我经常遇到以下情况:要将项目克隆到相邻文件夹中,再进行深入挖掘,然后再删除它。 但是,如果克隆过程需要10到20分钟以上,那么将不太方便。

但是除此之外,不要忘记在CI服务器上组装项目之前,需要为每个构建代理克隆存储库。 在这里,您开始弄清楚如何节省此时间,因为如果每个程序集都花费10-20分钟以上的时间,而程序集的结果在10-20分钟后出现,那么这将不适合任何人。 因此,存储库开始出现在从中部署代理的虚拟机映像中,从而出现了额外的复杂性和支持该解决方案的额外成本。

2)建立时间


这是一个很明显的观点,已经讨论了很多次。 实际上,如果您有很多源代码,那么无论如何汇编都将花费大量时间。 熟悉的情况是,在更改一行代码后,您必须等待半个小时,直到重新组合并测试了更改。 实际上,只有一种出路-使用围绕缓存结果和增量构建构建的构建系统。

这里没有太多选择-尽管实际上已将缓存功能添加到同一gradle中(不幸的是,我没有在实践中使用它们),但是由于传统的构建系统没有可重复的结果这一事实,它们并没有带来实际的好处。 (可复制的版本)。 即 由于先前构建的副作用,无论如何,在某些时候,有必要调用缓存清理(标准的maven clean build方法)。 因此,只剩下使用Bazel / Buck / Pants和其他类似选项的选项。 为什么这不是很好,我们稍后再讨论。

3)索引IDE


我当前的项目在Intellij IDEA中建立索引的时间为30至40分钟。 那你呢 当然,您只能打开项目的一部分,也可以从索引中排除所有不必要的模块,但是...问题是,每次从一个分支切换到另一个分支时,都会发生重新索引。 这就是为什么我喜欢在相邻目录中克隆项目。 有人开始缓存IDE缓存:)
<DiCaprio narrow着眼睛的图片>

4)构建日志


您正在使用什么CI服务器? 它是否提供了方便的界面来查看和浏览数GB的构建日志? 不幸的是我不是:(

5)提交历史


您喜欢看提交历史吗? 我喜欢,尤其是在带有图形界面的工具中(我可以更好地视觉感知信息,不要责骂:)。
这就是我的存储库中的提交历史记录
图片

你喜欢吗 方便吗 我个人不!

6)测试失败


如果有人能够将损坏的测试/未编译的代码运行到主机中,会发生什么? 您肯定会说您的CI不允许您这样做。 作者通过的不稳定测试又如何呢? 现在想象一下,这段代码已经传播到300个开发人员的机器上,而其中没有一个人可以组装一个项目吗? 在这种情况下该怎么办? 等待作者注意并纠正? 对他正确吗? 回滚更改? 当然,理想情况下,仅提交良好的代码并立即编写而没有错误是值得的。 这样就不会出现这样的问题。
(对于那些不了解储罐提示的人,关于负面影响的讨论会稍有不同,如果这种影响发生在拥有10个开发人员的存储库中和拥有300个开发人员的存储库中)

7)合并机器人


听说过这样的事情吗? 你知道为什么需要吗? 您会笑的,但是这是另一个不应该存在的工具:)假设您的项目的构建时间为30分钟。 100位开发人员正在处理您的项目。 假设他们每个人每天推送1次提交。 现在想象诚实的CI,它允许您仅在将更改应用于主服务器的最新提交(变基)后才将其合并到主服务器。

请注意,问题是:这样一台诚实的CI服务器一天要花几个小时才能扼杀所有开发人员的更改? 正确答案是50。回答正确的人可以从架子上拿胡萝卜。 好吧,或者想像一下,您如何切断对母版的最后一次提交的提交,开始组装,完成后,母版已经提前20次提交。 再来一次?

因此,合并bot或合并队列是一项服务,该服务可以自动将所有合并请求重新设置为一个新的主服务器,运行测试以及合并本身,并且还可以将提交合并成批并对其进行测试。 非常方便的事情。 请参阅mergify.io ,来自Google的k8s test-infra Prowbors -ng等。(我保证将来会对此进行详细介绍)

现在减少技术问题:

8)使用一个构建工具


坦率地说,对于我来说,为什么使用一个通用的构建系统组装整个单一存储库仍然是一个谜。 为什么不使用Yarn,使用gradle的Java,使用sbt的Scala等构建javascript? 如果有人知道该问题的答案(不猜测或不建议,即知道),请在评论中写下。

当然,显然使用一个构建系统要好于使用多个构建系统。 但是他们仍然明白,任何普遍的事物显然都比专门的事物更糟糕,因为 它很可能只有所有专业功能的一部分。 但更糟糕的是,不同的编程语言在汇编,依赖项管理等方面可能具有不同的范例,这将很难包装在一个通用包装器中。 我不想详细说明,我将举一个有关bazel的示例(请参阅另一篇文章中的详细信息)-我们在GitHub上找到了5个独立的javascript汇编规则,分别来自GitHub上的5家不同公司以及Google的官方代码。 值得考虑。

9)一般方法


在回应厨师长的原始文章时,首席技术官写了他的答案Monorepo:请做! 。 在他的回应中,他辩称“ monorepo的主要特点是它使您说话并且使缺陷可见。” 他的意思是,当您想更改API时,必须找到其所有用途,并与这些代码的维护者讨论更改。

所以我的经历恰恰相反。 显然,这在很大程度上取决于团队中的工程文化,但是我认为这种方法存在很多不足。 想象一下,您使用的某种方法已经忠实地为您服务了一段时间。 因此,您出于某种原因决定解决一个类似的问题,使用稍有不同的方法,可能更现代。 添加新方法进行审核的可能性有多大?

在最近的几年中,我多次收到评论,例如“我们已经有一条可靠的道路,可以使用它”和“如果您想实施一种新方法,请在使用旧方法的所有120个地方更新代码,并从负责此问题的所有团队中获取更新。这些代码。” 通常,“创新者”的热情就在这里结束。

在您看来,用新的编程语言编写新服务将花费多少钱? 在存储库中-完全没有。 您创建一个新的存储库并编写,甚至采用最合适的构建系统。 现在,单一存储库中有同样的事情吗?

我完全理解“标准化,重用,代码共享”,但是应该开发该项目。 我的主观意见是,单一存储库会阻止这种情况。

10)开源


最近有人问我:“ 是否有用于单色存储库的开源工具? ”我回答:“问题是,奇怪的是,单色存储库工具是在单色存储库本身内部开发的。 因此,将它们开源是非常困难的!”

例如,查看Github上的项目,该项目带有Intellij IDEAbazel插件 。 Google会在其内部存储库中对其进行开发,然后在提交历史记录丢失的情况下在Github上“飞溅”其一部分,而无法发送拉取请求,依此类推。 我认为它不是开源的(这是我的小型PR的示例,该PR已关闭,而不是合并,然后更改出现在下一版本中)。 顺便说一句,在原始文章中提到了这一事实,即单一存储库阻止它们在开源中发布并在项目周围创建社区。 我认为许多人对此并不十分重视。

替代品


好吧,如果我们谈论如何避免所有这些问题? 仅有一个建议-努力使存储库尽可能小。
但是,单一存储库与它有什么关系? 即使这种方法使您失去了建立小型,轻型和独立存储库的机会。

多重存储库方法的缺点是什么? 我正好看到了1:无法跟踪谁是您的API的使用者。 对于微服务“不共享”的方法尤其如此,在该方法中,代码不会在微服务之间混乱。 (顺便说一句,您认为有人在单一存储库中使用此方法吗?)不幸的是,此问题需要通过组织方式解决,或者尝试使用支持独立存储库的代码浏览工具(例如, https://sourcegraph.com / )。

诸如“我们尝试了多存储库,但是随后我们不得不不断地在多个存储库中不断实现功能,这很烦人,并且我们将所有内容合并到一个锅炉中”这样的评论怎么样? 答案很简单: “不要将方法的问题与错误的分解混淆 没有人声称存储库应该只包含一个微服务,就是这样。 当我使用多重存储库时,我们将一组紧密相关的微服务完美地组合到一个存储库中。 但是,考虑到有100多个服务,因此有超过20个这样的存储库,就分解而言,最重要的考虑因素是如何部署这些服务。

但是关于版本的争论呢? 毕竟,单一存储库允许您没有版本,只需一次提交即可部署所有内容! 首先,版本控制是这里提出的所有问题中最简单的一个。 即使在像maven这样的老东西中,也有一个maven-version-plugin允许您单击一下就降级该版本。 其次,也是最重要的是,贵公司是否具有移动应用程序? 如果是这样,那么您已经拥有了版本,您将一无所获!

嗯,仍然有支持Mono-Repository的主要论点-它允许您在一次提交中对整个代码库进行重构! 其实没有 如原始文章中所述,由于部署所施加的限制。 您应该始终牢记,很长一段时间(持续时间取决于您的流程的构建方式),您将同时拥有同一服务的两个版本。 例如,在我的上一个项目中,每次部署我们的系统都处于这种状态几个小时。 这导致这样一个事实,即即使在单一存储库中,也无法在单个提交中进行影响交互接口的全局重构。

而不是结论:


因此,在Google,Facebook等公司工作的受人尊敬的同事很少。 来到这里捍卫他们的单一存储库,我想说:“别担心,您做对了所有事情,享受调整,这花费了数十万或数百万个工时。 它们已经花光了,所以如果您不使用它,那么没人会使用。”

对于其他所有人: “您不是Google,请不要使用单一存储库!”

附言 正如Radio-T播客中受人尊敬的Bobuk在讨论原始文章时指出的那样:“世界上有大约20家公司可以使用一个存储库。 其余的甚至都不应该尝试 。”

Source: https://habr.com/ru/post/zh-CN436264/


All Articles