关于代码的管理问题已经讨论多年,随着企业业务的复杂度提高、软件行业技术栈的选择度变宽泛,现代软件的代码仓库也变得越来越庞大和复杂。一个中型项目,将测试代码、核心业务代码、编译构建、部署打包等基础设施的代码全部加起来,几十万行都是家常便饭。并且一个项目往往由多个团队进行协作,如何让多团队在对同一个项目的代码进行协作时不会相互干扰、相互制约,也是每个企业研发团队在实践中不断摸索的难题。
对于上文所说的一些问题,业界已经归纳了常见的代码仓库存放方式,常见的如单仓库和多仓库。大部分企业会针对不同的项目采用不同的仓库管理机制,所以对于企业来说,经常会两种方式并存:
将所有项目代码存放在一个代码仓库当中,这个好处在于项目的所有开发者可以共享看到项目中的所有代码;在项目规模较小的时候,一个库可以更好地管理和维护,发版本只要统一发布即可;对于持续集成,也只需要针对一个库维护若干条流水线。但再好的实践以及工具都有它适用的范围。Git 已经是非常流行的代码托管工具,但 Git 会把所有历史记录以及代码同步到各个用户的本地机器,所以对于大型项目而言,如果使用单仓库,就意味着某个模块开发者的本地可能有大量冗余代码和提交记录的信息,这个时候拆分成更小的库显得更加合适。
谷歌与 Facebook 就是业界典型的单仓库派代表。作为代码行数已经超过数十亿行、commit 数量累计达到千万次的团队( 2015 年的统计数据),如果没有强悍的基础设施,也很难运转顺利。Google 曾发表论文介绍其强大的代码管理系统 Piper 以及客户端工具 CitC,但对于大部分企业来说是否有必要投入如此之大的研发成本去自研一个代码管理系统值得商榷,所以谷歌的实践对于大部分企业来说不一定具备参考性。
谷歌代码仓库每周的提交数量
将项目代码进行一定的拆分放在多个库当中,好处就是将代码进行一定的解耦,对于体型较为庞大的项目来说管理上会更加清晰和富有弹性。将代码按照一定逻辑分库之后,仓库与模块有了自描述的特征,让一起协作的开发者可以一目了然。发布源码版本、持续集成构建时,负责各仓库的研发组织可以按照自己的节奏来发布,同时将一些“坏代码”的影响控制在某个仓库中,而不会影响项目全部代码。分库也有要注意的地方,在同一个项目里的代码多多少少都有业务上或者是技术上的联系,比如编译依赖:以一个 Java 项目为例,客户端接口的调用代码究竟是直接依赖服务端接口代码的定义,还是间接依赖?如果是间接依赖,那么分库管理是非常方便的,但同时客户端就无法快速感知到服务端接口定义的变化。所以在进行多仓库划分时,要注意划分的一些常用原则。
多仓库在业界使用的非常广泛,在腾讯、华为、阿里的开源项目中我们都能看到,比如腾讯的 Tars 开源项目( RPC 开发框架)就按照不同编程语言以及技术栈进行了分库:包括 Java、Go、PHP 等子项目。作为开源项目,一个清晰的分库可以让开发者更好地协作,避免不必要的沟通成本。
Tars 的开源项目子仓库
CODING 在多仓库实践上也遇到过问题。由于前端、后端、git-server 三个模块的代码放置在同一仓库中,以至于代码版本的 tag 需要保持同步,制约了各个团队的开发节奏。每个模块的进度都得齐头并进,才能保证最终版本是一致的。尽管它们在业务上紧密相连,但实际上这几个模块本身没有编译依赖,所以在没有多仓库功能时,我们只能建立了三个项目,使用三个项目的代码仓库能力,只集中在一个项目当中进行项目管理工作。
在千呼万唤中,CODING 近期终于正式上线了多仓库功能,我们的开发人员也终于可以告别傻乎乎地使用一个项目进行管理,又用多个项目进行代码仓库管理的尴尬问题,我们将那些没有编译依赖的项目,但在业务上又有联系的代码仓库,放置在同一项目的多仓库下,开发人员无需在多个项目中切换。
多仓库功能一直是 CODING 想要投入做的一个特性。随着近几年 CODING 企业用户的快速增长,CODING 的架构也面临着持续的挑战。如何让交付更加顺滑,让特性更快、更好地服务开发者,是我们进行架构演进的初衷。所以我们在很早之前就开始了容器化、微服务化的规划与实施,而在微服务化的过程当中,包括代码仓库管理在内的研发流程与组织方式也在配套前进着。多仓库这项基本能力就可以让多个微服务独立存放在独立的代码仓库当中,配套独立的持续集成流水线,让架构演进变得水道渠成。 我们知道很多企业用户对多仓库有很大诉求,CODING 的多仓库已正式上线,欢迎大家前去体验。
综上我们可以看到,代码仓库的组织方式往往和人员组织架构息息相关,而且代码库的拆分也往往和软件架构的演进息息相关。在现代软件架构逐渐由单体朝着分布式、微服务演化时,代码仓库和研发团队的粒度也在逐渐变小,从以前的集中式慢慢变为网状。但无论是单仓库还是多仓库,最终目的都是为了让开发者更加高效地进行研发。那究竟该如何选择?笔者总结了几条业界的通用实践来供大家思考:
不同技术栈的编译环境、构建环境、发布环境往往不同,代码之间的硬性依赖也不大,可以考虑分库存放。大部分的开发者还是倾向于在工作中持续使用某一种熟悉的编程语言,所以按照技术栈划分是一个常用的实践。
拆库要拆到什么粒度呢?有些研发组织微服务化后,给每个微服务都分配了一个代码库,随着拆分深入,一个项目积攒了几百个代码库。但一个 two-pizza 团队往往会负责多个微服务,不仅仅是一个。所以建议不要盲目使用大量代码库,避免到后期难以管理,可以考虑按照团队组织来划分代码仓库。
不少企业代码拆分之后可能顺便就把团队之间的代码权限也做了划分,建议研发团队慎重考虑。关闭了代码权限就意味着,团队与团队之间不再互相 review 代码,相应的工作上的交流也会逐渐减少。如果读者的企业属于内部开放型氛围的公司,或者想要成为开放型的公司,那么关于此点请三思。