我理解的 Monorepo
《工欲善其事,必先利其器》
既然点进来了,那就坚持看下去,希望你有不一样的收获。
(图片来自百度,侵删)
一、为什么是 Monorepo?
作为一名前端开发,其实很长一段时间我只会用 Git
,并不知道 Monorepo
是什么技术,更不知道他有什么作用。—— 来自前端 Loser 的我。
但事实是,现代的前端工程已经越来越离不开 Monorepo
了,无论是业务代码还是工具库,越来越多的项目已经采用 Monorepo
的方式来进行开发。例如:Google 宁愿把所有的代码都放在一个 Monorepo
工程下面,Vue 3
、Yarn
、Npm7
等等知名开源项目的源码也是采用 Monorepo
的方式来进行管理的。
由于谷歌在 Monorepo
上的实践,Monorepo
受到了越来越多的关注,这意味着把所有项目的所有代码统一维护在一个单一的代码版本库中。和多代码库方案相比,两者各有千秋,实际落地情况还需根据公司文化和产品特性进行取舍。
二、单一代码库和多代码库的区别
(图片取自百度,侵删)
多代码库(Multirepo
),由上图可见,每个项目都存储在一个完全独立的代码库之中,均拥有它们自己的版本控制流程和工作流。实际上多代码库是很自然的一种选择,大多数程序员在开始一个新的项目时都愿意开启一个新的代码库。
那么,多代码库有什么弊端吗?还真有。当你开发的同种类型的项目足够多时,你就会发现以下这些痛点:
- 公共代码复用问题
例如现在公司有一个项目,希望开发PC端以及H5端,且都要求使用 Vue
进行开发。这两个端的接口后端代码全部都集中运行在一台服务器上。这时,在开发的过程中你会发现 axios
的配置是复用的;utils
的代码是复用的;一些基础的组件是复用的。
在 Multirepo
的环境下,我们普遍的做法是直接复制复制复制,把相同的部分都复制到每个代码库中。那么,当这些公共代码出现bug时,我们就要修改多份,修改得很蛋疼。有没有其他更好的办法呢?有,把它构建成 npm
包。
此时再出现 bug
,啊哈!你可以拍拍胸脯跟组长表示,这事交给我了!
你可以修改代码,重新 npm publish
发布一个新版本,然后再去每个代码库中更新这个 npm
包。是不是很聪明呀!聪明你个der,你可能只是修改了一行代码,却做了这么多多余的事情,你说烦不烦?开发过程中如果产品一天变一个样式,你每天就都要做这么多事情,浪费时间不说,你烦不烦?
只要是处于 Multirepo
的开发环境下,上面的这种情况是实打实会存在的问题。没办法,由于不同的仓库工作区割裂的原因,导致复用代码的成本很高,开发调试的流程繁琐。甚至在基础库频繁改动的情况下让人感到很抓狂,体验感很差。
- 依赖包的更新问题
延续上面一点,Multirepo
环境下,你把 utils
发布成 npm
包了。
但是吧。你总会在业务上遇见一些尤其奇葩的点。例如在本项目下的某个业务中,产品又来给你找事了,说这里要添加一个有个性化需求。我们程序现状就会无法满足个性需求,咋办? 只有改通用程序代码迎合个别业务项目,但是这个改动不是其他业务项目需要的。
面对场景的无奈,且不说你需要重新发布的问题,这时会有三个选择:
- 要么保留俩版本,需要用到的地方升级版本;
- 要么两个合并,但是通用程序体积功能臃肿了;
- 要么把产品给捶死,一了百了。
题外话——面对这种问题,前期设计包时,就需要考虑以下几点:
- 要高度抽象设计对外规范和接口。
- 要更加细粒度抽象化单个功能。
- 思考如何让代码更加高内聚,低耦合,保持强大扩展性,减少break change。
- 工作流重复问题
上面也提及到,Multirepo
开发环境下, 每个端的工作流是割裂的,因此每个代码库都需要单独配置开发环境,配置 CI/CD
流程,甚至每个项目都要有自己单独的一套脚手架工具。
做完这些配置,你会很容易发现,这特么其实很多逻辑又是重复的。逻辑重复不说,各个项目间存在构建、部署和发布的规范不能统一的情况,这样维护起来也不是一般的麻烦。。
Monorepo
,译为单一代码库。在版本控制系统的单个代码库中,包含了许多项目的代码和公用的工具代码或组件。这些项目可能是同个公司的项目,但可以由不同的团队维护,相互独立,只有公用代码可以被随机引入使用。
从多代码库到单一代码库(Monorepo
)的变化就意味着将所有的项目移入到一个代码库之中。
在 Monorepo
环境下开发有什么好处呢?那可多了去了,它能把 Multirepo
解决不了的难题给解决了:
- 公共代码不用再复制。 所有的公共代码,每个项目和应用都用同一份;
- 依赖包版本统一。 在设计包时保持强大扩展性,杜绝高耦合、低内聚;
- 统一的工作流。 每个项目都使用同一份
CI/CD
部署流程;
除此之外,还有很多其他的特性,例如:
- 可见性。 每个人都可以看得到其他人的代码,可以带来更好的写作和团队贡献;
- 一致性。 执行代码质量标准和统一的风格会更容易;
- 原子提交。 原子提交使大规模重构更容易,开发人员可以在一次提交中更新多个包或项目;
- 隐式
CI
。 所有代码都统一维护,因此可以保证持续继承; - 统一构建流程。 代码库中的每个应用程序可以共享一致的构建流程。
哇~ 好棒棒!单一代码库原来这么厉害!这时你肯定很高兴!唧唧歪歪的就要去试一下这种开发模式,你选择阅读 Vue3
的源码开始,决定先参观一下!可是你却越看越迷糊,根本不知道它在写什么啊!!!
这时,其实 Monorepo
的弊端也显现出来了:
- 学习成本。 如果代码库包含了许多紧密耦合的项目,那么学习的曲线会很陡峭;
- 性能差。 项目一大,IDE开始变得缓慢,生产力收到影响;
- 大量的数据。 单一代码库每天都要处理大量的数据和提交;
- Code reviews。 代码审阅的任务变得更加繁重。
面对这种情况,我们该怎么优化?才能打造一套合理有效的单一代码库实践方案呢?
- 定义一个便于探索的统一的目录组织;
- 维护分支整洁,保持较小的分支,考虑采用基于主干的开发;
- 为每个项目使用固定的依赖项,一次升级所有依赖项,迫使每个项目都跟上依赖项。只为真正的例外情况保留例外。
- 如果正在使用
Git
,学习如何使用shallow clone
和filter-branch
来处理大容量代码库。 - 货比三家,寻找像
Bazel
或Buck
这样的智能构建系统,以加速构建和测试。 - 如果需要限制对某些项目的访问,请使用
CODEOWERS
。 - 使用
Semaphore
等CI/CD
云平台来大规模测试和部署应用程序。
三、总结
可见,每项技术都是一把双刃剑。它的背后,总会存在利与弊。因此,选择代码库策略和优化的手段,不仅是一个技术问题,也是关于人们如何交流的问题。正是我们开头所讲,两者各有千秋,实际落地情况还需根据公司文化和产品特性进行取舍。
本篇文章到此结束,感谢你的阅读~ 希望你的未来一片光明!
参考文章: