团队不足50人,盲目上Monorepo那是自找麻烦

2021年的一个周五下午,我在工位上盯着CI/CD的进度条,手心全是汗。

当时我们维护着12个独立的Git仓库(Polyrepo模式),前端业务组件库发了一个小版本的Bug Fix。理论上这只是个补丁,但因为三个业务线项目依赖的组件版本不一致——有的锁死了旧版本,有的用了^前缀自动更新,结果上线后,核心的支付弹窗在A项目中正常,在B项目中直接报错白屏。

为了修复这个问题,我们不得不排查所有仓库的package.json,逐个发版、打包、部署。原本10分钟的修复,硬是折腾到了凌晨两点。

那一刻我发誓:一定要把这些代码合到一个仓库里去。

配图

但当我真正带着团队转向Monorepo(单体仓库)半年后,我才意识到:解决了一个旧地狱,往往意味着开启了一个新地狱。

如果你也在犹豫是该拆还是该合,希望我这段"反复横跳"的血泪史,能给你一些真实的参考。

这种"各自为政"的痛,你也经历过吗?

在Polyrepo模式下,最大的痛点不是代码分散,而是**“上下文丢失”**带来的协作熵增。

回到2021年那个阶段,我们的团队结构是典型的"竖井式":3个前端小组,分别维护PC端、H5端和管理后台。后端则是微服务架构,拆分了8个服务仓库。

这种看起来"解耦"的架构,实际上带来了隐形成本:

  • 代码复用变成了通过网络发包: 修改一行公共工具代码,需要经历 修改 -> Commit -> Publish NPM -> 各业务项目 npm install -> 测试 的漫长链路。
  • 版本依赖地狱: 就像开头的案例,项目A用了utils v1.0,项目B用了utils v2.0,当两者在浏览器运行时共享某个全局状态时,诡异的Bug就诞生了。

这就是Polyrepo的原罪:它在物理上隔离了代码,也在逻辑上隔离了团队的认知。 开发者变得只扫门前雪,没人关心公共基建的演进。

盲目跟风Monorepo,基建不仅没变好,反而崩了

带着对"Google全公司几十亿行代码都在一个仓"的憧憬,我们在2022年初启动了迁移。

我们简单粗暴地把所有前端项目移动到了一个Git仓库下,用lerna做管理。起初的蜜月期确实很爽:

  • 原子化提交:改公共库和改业务代码可以在一个Commit里完成;
  • 依赖统一:大家终于都用同一个版本的React和Lodash了。

但仅仅过了三个月,噩梦开始了。

数据不会说谎: 我们的CI(持续集成)构建时间,从原本单项目平均5分钟,暴涨到了40分钟。

因为Monorepo如果不配合高阶的构建工具,每次提交代码,CI都会傻傻地把仓库里所有项目重新跑一遍测试、构建一遍镜像。

配图

更可怕的是开发体验的恶化。

那时候新招了一个实习生,光是git clone代码下来就花半小时,IDE打开项目光建立索引就卡死。我记得很清楚,有次周会上,后端组长直接拍桌子:“我现在改个文案都要等半小时流水线,这到底是进步还是退步?”

我的反思: Monorepo 不是 把代码放在一个文件夹里就完事了。它本质上是一套**“重型工程化体系”**。如果没有增量构建、远程缓存(Remote Caching)、细粒度权限控制这"三板斧",中小团队上Monorepo就是自掘坟墓。

适合中小团队的"中间路线":工具链+工作区

经过那次40分钟构建时间的教训,我们没有退回Polyrepo,而是引入了更现代的工具链进行改造。

现在的架构,我称之为**“务实的Monorepo”**。我们没有追求Google那种极致的单体,而是采用了Turborepo + pnpm workspace 的组合。

1. 引入增量构建,只做该做的事

我们调整了CI脚本,利用Turborepo的依赖拓扑分析能力。

具体操作: 当开发者修改了packages/ui-lib时,CI只会构建依赖这个库的APP A和APP B,而完全无关的APP C会被直接跳过(或使用缓存)。

// turbo.json 简略配置
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"], // 依赖上游构建
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"], // 依赖当前项目的构建
      "inputs": ["src/**/*.tsx", "test/**/*.ts"]
    }
  }
}

结果: 在缓存命中的情况下,我们的平均构建时间从40分钟骤降回了3-5分钟。这直接挽救了团队的幸福感。

2. 代码边界与Code Owner机制

为了防止"大锅饭"导致代码质量失控(比如谁都去改两行核心库的代码),我在Gitlab上配置了严格的CODEOWNERS规则。

  • 公共库(libs/): 必须由架构组核心成员Review通过才能合并;
  • 业务项目(apps/): 由对应的业务组长Review。

这既保留了"谁都能看代码"的透明度,又保证了"核心逻辑不被随意篡改"的安全感。

到底怎么选?给PM和Tech Lead的建议

回顾这两年的折腾,关于技术选型,我总结出一条核心逻辑:架构必须服务于组织结构(康威定律),而不是反过来。

如果你的团队正面临选择,不妨对照以下标准:

建议坚守 Polyrepo (多仓库) 的情况:

  • 团队之间完全隔离,业务几乎无交集(外包性质强)。
  • 没有专职的基建/DevOps人员维护构建工具。
  • 对权限极度敏感(A项目代码绝对不能被B项目组看到)。

建议尝试 Monorepo (单仓库) 的情况:

  • 多个项目之间共享大量的UI组件、工具函数、TypeScript类型定义。
  • 前端/全栈团队人数在10-50人之间,沟通成本开始显著上升。
  • 关键点: 有人愿意花时间去配置Nx、Turborepo或Rush等现代化工具,而不是仅仅靠cp命令。

总结与行动

没有完美的技术架构,只有最适合当下的权衡。

对于大多数中小团队(尤其是SaaS类、中台类产品),我强烈推荐**“基于Workspace的轻量级Monorepo”**方案。它不需要你像Google那样自研构建系统,只需利用现有的Node.js生态工具,就能获得80%的收益。

如果你决定迈出这一步,请从这3件小事做起:

  1. 资产盘点: 统计目前有多少重复代码散落在各个仓库中(尤其是Utils类和Type定义),用数据量化重构价值。
  2. 工具先行: 不要手动搞,直接试用 pnpm workspaceTurborepo 的官方脚手架初始化一个Demo,把两个关联性最强的项目放进去跑通流程。
  3. 制定规矩: 在合并之前,先定好目录结构规范(如 apps/ 存放应用,packages/ 存放库),否则三个月后你的仓库会变成一个巨大的垃圾场。

最后,做个小调查:

在你的日常开发中,你是更享受 Monorepo 带来的"全局掌控感"(A),还是更喜欢 Polyrepo 的"互不打扰"(B)?

欢迎在评论区告诉我你的选择和理由,也许你的痛点就是我下一篇文章的主题。