monorepo 项目改造反思

在(前)公司做的最后一件事情,就是做项目改造,把原有的前端工程进行模块拆分。原来的前端工程基于 dva1.x 构建,整个项目非常庞大,不好维护,运行起来耗时,热编译也非常慢。促成拆分的重要原因是平台代码和业务代码由两个团队来做,平台代码 会拆分独立升级,而现在的业务和平台耦合,升级时需要在两遍重复更新,繁琐。而拆分后变为模块依赖的方式,就能够解耦,保持各团队的独立升级。

在升级的过程中,有两种方案,一种是采用 multirepo 的方式,将各个模块分别维护,主工程仅提供必要的运行环境,业务模块全部靠引入,各业务模块分别属于独立的仓库,管控各自的版本发布。另一种方式是建一个独立工程,采用 lerna 来做管理,根目录 packages 目录下管理各个模块的源代码。也就是 monorepo 的仓库管理模式。最后选择了 monorepo 的方式来进行代码管理。

为什么采用 monorepo

虽然不清楚 monorepo 的代码管理风格是从何时开始流行的,但是越来越多的开源项目采用了这种模式来管理,必然有他的好处。简单来讲,我的感受是,各子模块之间存在紧密联系,而又确实存在明显的业务边界,适合采用 monorepo 管理。

举个例子,我们的项目各个模块的业务有时互相引用,有时也会存在代码上的引用(抽取出一个公共的业务依赖模块,通用的业务方法会写在这一模块内),这时候,如果采用 monorepo 的模式,由于同处于一个项目内,就很方便的维护、对比查看代码,而采用多个仓库的方式就需要在不同的仓库之间切换,带来比较高的维护成本。

monorepo 的问题

monorepo 的问题在于,各个模块都在同一仓库,不好进行模块的版本管理,除非各个业务模块统一发同样的版本,否则采用打 tag 的方式,一个 tag 对应统一仓库不同时刻的代码快照,逻辑上是有问题的。之前在项目中加入了 standard-version, cz-conventional-changelog, commitlint, commitizen 等一系列插件来帮助代码管理。套路就是靠 commitlint 来在 commit 时检测 commit message 的规范,如果符合规范,在调用 cz-conventional-changelog 时可以生成两个 tag 版本间的 changelog,自动追加相应的版本变更到 CHANGELOG.md 文件内。所以,用 standard-version 来管理的时候,只会在总的仓库添加 tag,无法区分各个模块的 tag 版本,也就无法维护各个模块的 CHANGELOG 了,这一套方案也就作废。

出于代码安全层面考虑,monorepo 不能很好的控制各个模块的代码权限,无法做权限隔离,维护一个模块可以看到所有模块的代码。亦是缺点。

出于代码的分支管理考虑,monorepo 不得不采用一套复杂的分支管理机制来处理模块间的代码关系,需要考虑到各模块的开发进度,整体发布到开发环境,测试环境,以及生产环境的隔离等一系列问题。所以需要设计完善的分支管理方案,并做规约,很显然,人为操作的规约是最难的,难免误操作。这一块的解决也有很大难度。

multirepo 的问题

multirepo 好像看起来解决了上述一系列问题,然而除了代码维护需要在各个仓库间切换不太方便外,还有一个致命缺点。

当需要模块间依赖时,比如 utils 仓库中需要添加一个方法,A 模块仓库的开发人员不得不找到 utils 仓库的开发人员,添加相应方法,敦促 utils 仓库重新发布新的版本,在本地更新依赖,这样开发效率低下,在整个业务模块体系初期阶段这种操作可能是非常常见的场景。

在代码发布时也有同样的问题,由于开发环境采用 jenkins 构建,而开发环境基本上每天不同的模块团队会发布几十次,不可能每次去更新依赖包的版本。在 nexus 搭建的私有仓库中可以设置私有源可以重复发版,也就是版本号不变,可以重复发版,看似解决了这个问题,但是这样一来由于 npm 或 yarn 安装时会校验哈希,yarn.lock 和 package-lock.json 文件就作废,每次都要删掉才可重新安装依赖。也是不正确的操作。

小结

对于我们的项目而言,由于项目处在快速的迭代周期内,multirepo 的问题是致命的,所以选择了更复杂的 monorepo 的管理模式,也随之设计了一套复杂的版本管理方案。但是猜测在项目较为稳定的阶段,是会迁移到 multirepo 的模式的(已离职,做个猜想哈)。

monorepo 需要开发人员对代码熟悉,对 git 熟悉,否则几十人的团队去维护同一个 monorepo 仓库是玩不转的。著名开源项目采用这种模式,并且玩的有声有色,也许是因为他们都是非常优秀的程序员吧。对于上面提到的一系列问题,业务未来会找到好的解决方案,在两种模式间采用一种更为平衡的方式吧。到时再重写一篇文章做思考。

还有一个反思就是,standard-version, cz-conventional-changelog, commitlint, commitizen 这些炫酷的方案,属于锦上添花的东西,在项目的代码质量很高之前,不太有必要追求,比如上面,虽然引用,但是是用不起来的东西,模式是不对的。代码优雅才有灵魂,工具只是花架子。

-EOF-

本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处! © 雨落
  1. 1. 为什么采用 monorepo
  2. 2. monorepo 的问题
  3. 3. multirepo 的问题
  4. 4. 小结