为了解决代码共享、重复和一致性等问题,越来越多的开发团队转向了Monorepo。本文就来了解一下什么是 Monorepo,它们可以带来哪些好处,以及常用的 Monorepo 开发工具。帮助我们更好地理解和应用这一代码管理策略。让我们一起进入Monorepo的世界,打破传统的代码管理方式,迎接更高效的软件开发体验!
什么是 Monorepo ?
Monorepo 是一个包含多个独立项目,并且具有明确定义的关系的单个代码库。一个真正的Monorepo不仅仅是将多个项目的代码放在同一个代码库中。它还需要这些项目之间有明确定义的关系。如果这些项目之间没有良好定义的关系,那么就不能称之为Monorepo。
类似地,如果一个代码库中包含了一个庞大的应用,而没有对其进行分割和封装,那么这只是一个大型的代码库,而不是真正的Monorepo。即使你给它取一个花里胡哨的名字,也不能改变它的本质。
事实上,一个优秀的Monorepo正好与庞大的代码库相反,它应该具有良好的结构和模块化。
Monorepo 解决了什么问题?
Polyrepo
为了讨论的方便,假设多个代码库("polyrepo")是与单一代码库("monorepo")相对应的概念。polyrepo 表示多代码库,是当前开发应用的标准方式:每个团队、应用或项目都有一个代码库。通常情况下,每个代码库只有一个构建产物,并且拥有简单的构建流程。行业之所以采用多代码库的方式进行开发,有一个重要原因:团队自治。团队希望能够自主决定使用哪些库,何时部署应用或库,以及谁可以贡献或使用他们的代码。
团队自治是一种组织架构和开发模式的趋势,它赋予了团队更大的自主权和灵活性。每个团队拥有自己的代码库,可以根据自己的需求和优先级进行独立的决策和开发。这种方式能够提高团队的效率和创造力,同时也减少了团队之间的依赖和冲突。这些都是好的方面,那么为什么需要采取不同的方式呢?因为这种自治是通过隔离来实现的,而隔离却会损害协作。具体来说,在多代码库环境中存在以下常见问题:
- 繁琐的代码共享:要在多个代码库之间共享代码,可能需要为共享代码创建一个专门的代码库。这样就需要设置工具和持续集成(CI)环境,将贡献者添加到代码库中,并设置包发布,以便其他代码库可以依赖它。更不用说在多个代码库之间解决不兼容的第三方库版本的问题了…
- 大量代码重复:由于不愿意费事设置共享的代码库,团队通常会在每个代码库中编写自己的常见服务和组件的实现,这导致了很多代码重复。这种做法不仅浪费了时间,而且在组件和服务发生变化时增加了维护、安全性和质量控制的负担。
- 对共享库和消费者进行成本高昂的跨存储库更改:在多个代码库中进行共享库的跨库更改是具有挑战性且成本较高的。当一个共享库需要进行关键 bug 修复或重大变更时,开发人员需要在各个代码库中逐一应用这些修改,而这些代码库之间可能存在孤立的修订历史。这也意味着必须在每个代码库中进行相应的测试和验证,确保更改不会引入新的问题。
- 工具不一致:工具链的不一致性会给开发人员带来额外的负担。不同的项目使用不同的命令和工具,可能需要花费时间和精力来适应和记忆这些差异。而且,当需要在多个项目之间进行切换时,频繁地切换命令和工具可能会导致混淆和错误。
Monorepo
在多代码库环境("polyrepo")中工作时,可能会遇到一些问题。 那单一代码库("monorepo")是如何解决这些问题的呢?
- 创建新项目无需额外开销:创建新项目无需额外的开销。如果所有消费者都在同一个代码库中,可以直接在现有的开发环境中创建和设置新项目,避免了额外的资源和配置成本。
- 原子提交跨项目:每次提交都能确保所有项目能够协同工作。当在同一次提交中修复所有问题时,不存在破坏性变更的问题,因为所有修改都是一起完成的。
- 同一版本的所有内容:可以避免因项目依赖冲突而导致的不兼容性问题。所有项目都共享同一份代码库,使用相同的第三方库版本,减少了版本管理和冲突解决的复杂性。
- 开发灵活性:可以建立一种统一的构建和测试应用程序的方式,即使这些应用程序使用不同的工具和技术。开发人员可以自信地为其他团队的应用程序做出贡献,并验证其更改是安全的。
Monorepo 工具有哪些?
Monorepo 拥有许多优势,但为了使其发挥作用,需要使用正确的工具。随着工作空间的扩大,这些工具必须帮助开发者保持高效、可理解和可管理。
那 Monorepo 工具应该提供些什么呢?
- 本地计算缓存
- 本地任务编排
- 分布式计算缓存
- 分布式任务执行
- 透明远程执行
- 检测受影响的项目/包
- 工作区分析
- 依赖图可视化
- 代码共享
- 一致的工具
- 代码生成
- 项目约束和可见性
注意:除了功能以外,还有其他因素会影响使用体验。尽管一个工具可能在某些方面功能不如其他工具强大,但由于用户体验、成熟度、文档、编辑器支持等因素的差异,用户可能会更喜欢使用它。有些功能可以很容易地通过自定义实现来添加,而有些功能则无法通过简单的修改来实现。因此,除了关注功能外,还需要综合考虑其他因素来选择适合自己需求的工具。
比较流行的 Monorepo 工具如下:
工具 | 开发团队 | 简介 |
Bazel | 谷歌 | 快速、可扩展、多语言且可扩展的构建系统。 |
Gradle Build Tool | Gradle | 专为多项目构建而设计的快速、灵活的多语言构建系统。 |
Lage | 微软 | JS monorepos 中的任务运行器。 |
Lerna | Nrwl | 一个用于管理具有多个包的 JavaScript 项目的工具。 |
Nx | Nrwl | 下一代构建系统具有一流的 monorepo 支持和强大的集成。 |
Pants | Pants Build | 一个快速、可扩展、用户友好的构建系统,适用于各种规模的代码库。 |
Rush | 微软 | 适合拥有大量团队和项目的大型单一仓库。属于 Rush Stack 项目系列的一部分。 |
Turborepo | Vercel | JavaScript 和 TypeScript 代码库的高性能构建系统 |
接下来将逐个功能来比较各个 Monorepo 工具在这些功能上的表现。
本地计算缓存
本地计算缓存是指在进行任务执行时,将任务的输入、输出和中间结果存储在本地机器上,以便在后续的构建或测试过程中可以直接使用这些已经计算过的结果,而不需要重新执行相同的任务。
工具 | 是否支持 |
Bazel | ✅ |
Gradle Build Tool | ✅ Gradle Build Tool 本身提供本地构建缓存。Gradle Enterprise 添加了对复制和管理的支持。 |
Lage | ✅ |
Lerna | ✅ Lerna v6 具有由 Nx 提供支持的内置本地计算缓存。 |
Nx | ✅ |
Pants | ✅ 像React一样,Nx在从其缓存中恢复结果时进行了树差异比较,这使得它在平均情况下比其他工具更快 |
Rush | ✅ 利用系统 tar 命令更快地恢复文件。 |
Turborepo | ✅ |
本地任务编排
本地任务编排是指在执行任务时,按照规定的顺序和并行方式来管理和执行这些任务。任务之间可能存在依赖关系,需要按照正确的顺序进行执行,同时还可以利用并行计算的优势,提高任务的执行效率。
工具 | 是否支持 |
Bazel | ✅ |
Gradle Build Tool | ✅ Gradle Build Tool 通过配置提供对并行任务的支持,并通过其灵活的 Groovy 或 Kotlin DSL 提供任务编排支持。 |
Lage | ✅ |
Lerna | ✅ Lerna v6 充分利用了 Nx 来高效地协调和并行执行任务。 |
Nx | ✅ |
Pants | ✅ |
Rush | ✅ 支持此功能。命令可以被建模为简单的脚本,也可以作为独立的“阶段”(例如构建、测试等)。 |
Turborepo | ✅ |
分布式计算缓存
分布式计算缓存允许在不同的环境中共享缓存结果。通过共享缓存,整个组织中的不同部门或团队可以共享已经计算过的结果,避免重复构建或测试相同的内容。
工具 | 是否支持 |
Bazel | ✅ |
Gradle Build Tool | ✅ Gradle Build Tool 提供远程分布式缓存。 Gradle Enterprise 添加了对复制和管理的支持。 |
Lage | ✅ |
Lerna | ✅ Lerna v6 可以连接到 Nx Cloud 以启用分布式缓存。 |
Nx | ✅ |
Pants | ✅ |
Rush | ✅ Rush 内置了对 Azure 和 AWS 存储的支持,并具有允许自定义缓存提供程序的插件 API。 |
Turborepo | ✅ |
Hello Monorepo(下)https://developer.aliyun.com/article/1411558