1.2 全栈Monorepo的关键设计原则
在使用JavaScript/TypeScript进行全栈开发时,采用Monorepo架构会有一些需要特别注意的原则。这些原则是笔者在实际开发中常常使用的,下面进行简要介绍。
1.从Single-repo逐渐演进到Monorepo
1.1.4节中介绍了Monorepo的优势,以及Single-repo和Multirepo存在的问题。然而,这些问题并非绝对的,很多时候需要根据具体场景来考虑。在经验不足的情况下,直接使用Monorepo可能会遇到很多困难。开发者只有实际地开发一个项目,经历代码量从几百行到几万行的过程,才能更好地理解Monorepo与其他代码管理模式之间的优劣。此外,在某些情况下,调整Monorepo项目的成本可能较高,使用Single-repo架构进行发布也是可以理解的。例如开源项目作者将所有项目整合到一个GitHub repo中,可能会使用户感到困难。相反,一个小而简洁的Single-repo项目可能会更容易被早期用户接受。对于私有的项目,笔者坚定地认为,值得使用Monorepo架构。
由于同时维护多个Monorepo并不容易,因此应该尽量减少维护的Monorepo的数量。根据笔者的经验,项目初始阶段可以采用Monorepo,但是当项目规模变大时,可能需要将其拆分。然而,在维护一段时间之后,可能会发现很多工作重复,因此又会考虑合并或者使用GitHub的工作区(workspace)功能将其集成到另一个Monorepo中。从外部看,Monorepo就像一个独立的文件夹,可以与Multirepo一起使用,因此两者并不冲突。读者应该根据自己的实际情况,在不同阶段尝试不同的组织方式,最终找到适合自己的方式。
2.明确划分不同技术栈的定位
使用Monorepo架构最具吸引力的地方之一是可以非常简单地引入多种技术栈。但是,随着项目的进展,协调多种技术栈的定位变得非常重要。每个Monorepo维护架构师的重要职责就是划分各个技术栈的定位。例如,如果计划引入Deno作为CLI的运行,是否整个Monorepo的所有CLI程序都需要使用Deno开发?如果计划引用Go作为后端服务器的核心语言,这种引入是否有意义?如果引入Deno和Go不能加快开发效率,反而成为一种拖累,那么容易引入新技术的优点就变成了缺点。
3.应用层类型协议与JSON Schema
使用TypeScript作为核心技术栈可以为前后端在开发时提供类型,JSON Schema可以作为运行时的约束。同时提供运行时和编译时约束,可以提高前后端代码的健壮性和可维护性。
4.最小配置、统一尽可能严格的标准
维护Monorepo中一个非常耗费精力的工作是配置管理,越少的配置,维护的成本越低。通过创建一个npm包来管理统一配置,并建立严格的代码检查规则(lint)来约束整个代码库,可以有效降低维护成本。在构建良好的Monorepo项目中,随着基础设施的完善,团队的开发效率会不断提高,严格的标准有助于管理日益庞大的代码库。
在开发初期,通常会使用可选的组件进行开发,但是随着时间的推移,应该逐渐放弃这种方式。需要自由的地方给予自由,但是不该有自由的地方应该完全不自由。越多不自由的东西,维护成本就越低。不自由代表模板化、标准化。在后续成员加入时,Monorepo的维护成本应该是新成员的学习成本,而不是颠覆性的。Monorepo的目的是共享已经解决的问题的解决方案。
5.每一个子项目都视为独立的项目
在Monorepo项目中,每个子项目都被视为一个独立的项目,这意味着它可以被其他项目直接使用。这一条原则更像是一种期望,就像在函数式编程里希望所有函数没有副作用一样。当Monorepo项目较为简单时,任意一个子项目也许可以直接拉到另一个代码库中直接使用。但是当项目较为复杂后,可能很难保证这一点。例如A项目依赖B项目,B项目依赖C项目,虽然这三个项目都是通过package.json文件分隔的独立项目,有一定的物理隔离性。但是实际上,这三部分代码要一起移动才可以使用。
虽然整个Monorepo项目里有统一的流水线构建工具,每个子项目仍可以拥有独立且完整的构建流水线。当项目变得复杂后,通常会采用基于依赖图的构建工具,提高构建速度。
6.任何非官方工具,准备至少两种解决方案
选择Monorepo作为开发模式,意味着开发者需要面对大量与编译和打包相关的问题。JavaScript/TypeScript的大部分工具都是由社区提供的。一方面这些社区工具都有其生命周期,如果作者不维护了,可能就需要替换;另一方面,这些社区工具之间本身也有依赖的上下游关系,上游项目出了问题,下游项目就需要等待,这样也会延长解决问题的时间。因此准备多种解决方案是必要的。
幸运的是,JavaScript/TypeScript社区足够庞大,几乎所有的方案都可以找到几种替代方案。因此在Monorepo项目中,最好使用至少两个不同的工具来替代除自己开发的工具外的其他工具,以确保在出现问题时有及时、可行的解决方案。