Monorepo 与 Polyrepo:如何保持 Web、移动与后端同步
为同时交付 web、mobile 和后端的团队解释 monorepo 与 polyrepo。比较依赖、发布与 CI 策略,帮助保持高效。

真正的问题:跨三个代码库交付变更时的痛点
团队之所以争论 monorepo 与 polyrepo,并不是因为他们在意 Git 的哲学,而是因为一个小的产品改动会变成 web、mobile 和 backend 三处的改动,而在这过程中总会出问题。
最先出问题的往往不是 UI,而是看不见的粘合部分:API 合约在没有相应更新的情况下被改动、共享库在某处被升级而其他地方没有,或者构建流水线突然需要一个新步骤。当某个部分比其他部分先上线时,用户会把它感受到为 bug,例如“网页上有按钮但移动端显示不支持”或“因为后端响应变了,应用一直加载中”。
Web、mobile 和 backend 也有不同的发布节奏。Web 可以一天发布多次;后端也能经常发布,但需要谨慎的灰度和回滚;移动最慢,因为应用商店审核和用户更新带来真实延迟。一个“简单”的改动,比如重命名字段,可能会迫使你按最慢的那条路来计划,即便只有一个界面需要它。
如果这些问题经常出现,你很可能在为仓库协调付出额外成本:
- 破坏性 API 改动在合并后才被发现。
- 版本对齐依赖人工提醒和电子表格。
- 一个功能需要多个相互等待的 PR 来协同完成。
- CI 很慢,因为它构建和测试了远超改动范围的内容。
- 回滚风险高,因为不清楚哪个提交对应哪个发布。
团队规模和产品成熟度会影响最佳答案。早期团队通常通过降低协调成本和提高可见性获益更多,即便过程有些混乱。随着团队增长,边界开始重要,但前提是接口稳定且有清晰的归属。
如果每个重要变更都必须在三处落地,你迟早会为此买单。仓库策略主要是决定你以哪种方式买单。
用通俗话说的 monorepo 和 polyrepo 基础概念
仓库只是代码及其历史保存的地方。当你有 web、mobile、backend 时,选择很直接:把所有东西放在一起,还是拆开。
monorepo 是把多个应用和常用共享代码放在一个仓库里。Web、iOS/Android、后端服务和共享库并列存在。
polyrepo 则相反:每个应用(有时每个服务)都有自己的仓库。共享代码通常变成独立包,或者团队按需复制小片段。
日常体验上,monorepo 通常是:共享代码容易、跨应用改动可以在一个 PR 完成、规则一致。代价是社交性的:如果不划清责任,所有权会模糊,仓库级的检查会让人觉得严格。
polyrepo 通常是:每个团队能独立推进、仓库更聚焦、访问控制简单。代价是协调成本:共享代码需要计划,跨应用改动往往变成多个 PR 并需要精确时序。
很多团队采用混合方案:应用拆仓,契约与共享代码放一处;或者一个 monorepo 但设定严格边界,让各团队大多只在自己负责的区域活动。
如果你使用能从同一事实来源生成后端、Web 和移动的方案,就能减少漂移,因为契约和逻辑住在一起。AppMaster, for example, generates production-ready backend, web, and native mobile apps from a single model. 这不能消除发布的现实(移动仍然更慢),但能消除很多“我们是不是都改过了?”的开销。
依赖管理:保护共享代码不出乱子
共享代码是无论仓库布局如何团队都会耗时间的地方。共享库或 API 合约的一点小改动,会以不同方式打破 web 构建、移动发布和后端部署。
当你共享库(UI 组件、校验规则、鉴权助手)时,你在选择:让所有人用同一个版本,还是允许多个版本并存。
- 使用同一版本更简单,避免“在我分支上能跑”的惊喜。
- 多版本让团队按自己的节奏前进,但会带来杂乱并让安全修复难以快速回滚到所有地方。
API 客户端和 schema 值得格外小心。手动更新慢且易错。更好的做法是把 API schema 当作事实来源并从中生成客户端,然后要么把生成的客户端签入仓库,要么在 CI 中生成。目标是快速失败:如果后端新增了必填字段,移动客户端应在构建时报错,而不是三天后在 QA 报错。
破坏性变更会蔓延,当行为改变却没有安全迁移路径时就会发生。优先采用可兼容的添加性变更(新字段、新端点),再逐步弃用旧东西。如果必须做破坏性升级,使用版本化端点或短期兼容窗口。
举个具体例子:后端把 status 重命名为 state。如果 web 今天就更新但移动要一周后才能发布,后端需要在这一周同时接受两个字段,或发布一个映射旧字段到新字段的适配器。
一些能让依赖更新变得平淡无奇(以好事为准)的规则:
- 按节奏更新。小的每周更新比季度性的大更新更好。
- 对破坏性变更要求明确审批,并附上简短迁移说明。
- 自动化检查:依赖升级、客户端再生成和基本的契约测试。
- 把“完成”定义为“web、mobile 和 backend 的构建都通过”,而不是“我的仓库通过”。
生成代码能减少漂移,但不能代替纪律。你仍然需要单一契约、明确的弃用策略和可预期的更新节奏。
发布协调:让 web、mobile、backend 对齐
发布协调是仓库策略从理论变成现实的地方。如果后端改了 API 字段名,web 通常能当天更新并发布。移动不同:商店审核和用户更新时序可能把小的错配变成一周的客服工单。
实际目标很简单:无论哪一部分先更新,用户操作都应该可用。这意味着要为混合版本做计划,而不是假定完美同步发布。
团队常用的版本策略
大多数团队会采用下列方式之一:
-
统一发布列车:web、mobile 和 backend 作为一个有版本的单元一起发布。
-
按服务版本并带兼容规则:每个应用/服务有自己的版本,后端支持定义范围内的客户端版本。
统一发布列车看起来整洁,但在移动延迟出现时常常失效。按服务版本在纸面上更乱,但更贴近现实。如果采用按服务版本,写下一条规则并强制执行:哪些后端版本必须支持哪些移动版本,以及支持多久。
移动延迟也会影响热修复策略。后端热修能迅速发布;移动热修可能要几天才能到用户。优先选择服务端修复以保持旧的移动构建可用。必须改客户端时,使用功能开关,并在确认大多数用户已更新前避免移除旧字段。
举例:你在订单流里新增“配送说明”字段。后端把字段设为可选,web 立即展示,移动下一冲刺再展示。只要后端接受旧请求并把该字段设为可选,一切都能在移动赶上的过程中继续工作。
谁来管理发布日历
当“每个人都负责”时,就可能没人真正负责,从而导致协调失败。负责人可以是技术负责人、发布经理或有工程支持的产品经理。他们的工作是通过保持发布期望的可见性和一致性来避免惊喜。
他们不需要复杂流程,但需要一些可重复的习惯:一个简单的发布日历(含截止时间和冻结窗口)、在 API 改动发布前做快速跨团队检查,以及当移动延迟时的明确应对计划(暂停后端改动还是保持兼容)。
即便你的工作流能从同一模型生成 web、mobile、backend,你仍需一个发布负责人。你可能会更少遇到“我们是不是忘了改三处”的情况,但无法规避移动发布时间的问题。
控制 CI 时间消耗
无论哪种仓库设定,CI 变慢的原因大体相同:你重建了太多内容、重复安装依赖、对每次变更都运行所有测试。
常见耗时点包括:对微小改动做全量构建、缺失缓存、测试套件跑全量以及串行作业本可并行运行。
先从通用改进做起:
- 缓存依赖下载和构建产物。
- 在可能的地方并行运行 lint、单元测试和构建。
- 把快速检查(每次提交)和慢速检查(主分支、夜间或预发布)分开。
对 monorepo 有效的策略
当每次提交触发“构建全世界”的流水线时,monorepo 最容易变痛苦。解决办法是只构建和测试受影响的部分。
使用路径过滤和仅受影响策略:改了移动 UI,不要重建后端镜像;改了共享库,只构建和测试依赖它的应用。许多团队用一个简单的依赖图来让 CI 做决定,而不是猜测。
对 polyrepo 有益的做法
polyrepo 的单仓库较小、运行速度快,但常因重复和工具不一致而浪费时间。
保持一套共享的 CI 模板(相同步骤、相同缓存、相同约定),这样每个仓库不用重新发明流水线。固定工具链(运行时、构建工具、linter)以避免“在一个仓库能运行、另一个不能”的问题。如果依赖下载是瓶颈,配置共享缓存或内部镜像,避免每个仓库都从头拉取全世界依赖。
举例:某功能新增 status 字段。后端改了、web 展示了、移动也要展示。在 monorepo 中,CI 应运行后端测试以及仅依赖该 API 客户端的 web 和 mobile 部分。在 polyrepo 中,每个仓库应运行自己的快速检查,并用单独的集成流水线验证三方发布仍然一致。
无论你把源码导出并运行自己的 CI,原则一样:只构建改动部分、积极复用缓存,并把慢速检查留到真正有价值时再跑。
逐步方法:选择适合你团队的仓库策略
从日常工作出发比从意识形态出发更容易做出决定。
1) 写下必须一起改动的内容
挑 5–10 个最近的功能,记录哪些地方必须锁步变更。标注每个功能是否触及 UI 页面、API 端点、数据表、鉴权规则或共享校验。如果大多数功能需要三者协调,拆仓会很痛,除非你的发布流程非常有纪律。
2) 追踪共享代码和共享决策
共享代码不仅仅是库,还包括契约(API schema)、UI 模式和业务规则。记录这些现状、谁在改它们、如何审批。如果共享片段在多个仓库被复制,那说明你需要更严格的控制,要么通过 monorepo,要么通过严格的版本管理规则来解决。
3) 定义边界与负责人
决定单元是什么(应用、服务、库),并为每个单元指派负责人。边界比仓库布局更重要。没有负责人,monorepo 会变得嘈杂;没有负责人,polyrepo 会断联。
一个简单清单:每个可部署服务/应用一个仓库或文件夹、一个地方放共享契约、一个地方放真正共享的 UI 组件、明确业务逻辑所在位置,并记录每个单元的负责人。
4) 选择你能遵守的发布模型
如果移动发布滞后,你需要兼容策略(版本化 API、向后兼容字段或明确定义的支持窗口)。如果一切必须一起发布,发布列车可行,但会增加协调成本。
保持分支规则简单:短时分支、小型合并和明确的热修路径。
5) 根据常见改动设计 CI
不要一开始就为最坏情况设计 CI,而是为日常行为设计。
如果大多数提交只改 web UI,默认运行 web lint 和单元测试,把完整端到端测试放到定时或发布前。如果事故多数来自 API 漂移,优先投资契约测试和客户端生成。
示例:一个触及 web、mobile 和 backend 的功能
想象一个小团队同时做三件事:客户门户(web)、外勤应用(mobile)和 API(backend)。收到一个需求:在 job 上增加“Service status” 字段并在各端展示。
改动看似小,但其实是协调能力的考验。后端新增字段并更新校验与响应;web 展示并更新筛选;移动需要离线展示、同步并处理边缘情况。
真正的问题是 API 改动是破坏性的。字段名从 status 变为 service_status,旧客户端如果不处理就会崩溃。
monorepo 会带来的不同
这正是 monorepo 常让人感觉平静的地方。后端、web 与 mobile 的改动可以在一个 PR(或一组协调提交)中一起落地。CI 可以运行受影响的测试,你也能为三处改动打一个统一的发布标签。
主要风险是社会层面的,而非技术层面:一个仓库更容易快速合并破坏性改动,所以需要更严格的评审规则。
polyrepo 会带来的不同
在独立仓库场景下,每个应用有自己的节奏。后端可能先上线,web 与 mobile 则要补上。如果移动发布需要商店审核,即便代码改动很小,修复也可能要花好几天才能到用户手中。
团队通常通过更有结构化的方式来解决:版本化端点、向后兼容的响应、更长的弃用期和更清晰的上线步骤。这能奏效,但需要长期维护。
如果你基于证据来决定,看看最近几个月:
- 如果事故常来自版本不匹配,偏向更紧密的协调。
- 如果发布频繁且时间敏感(尤其是移动端),避免破坏性变更或把它们集中化处理。
- 如果团队独立且很少触碰同一功能,polyrepo 的开销或许值得。
常见错误与陷阱
大多数团队失败并非因为选错了仓库结构,而是日常习惯逐步增加摩擦,直到每次改动都变得危险。
共享代码变成垃圾场
共享库有诱惑力:助手、类型、UI 片段、临时变通。很快它会变成旧代码的收容所,没人知道哪些可以安全改动。
保持共享代码小且严格。“共享”应当意味着被多个团队使用、经过仔细审查并带有变更意图。
通过隐性假设造成紧耦合
即便在独立仓库中,系统也可能高度耦合,耦合只是转移成隐性假设:日期格式、enum 值、权限规则、字段总是存在的假设。
例如:移动端把 status = 2 当作“Approved”,web 当作“Confirmed”,后端改了 enum 顺序,结果看起来像随机崩溃。
通过把契约文档化(字段含义、允许值)并把它们当作产品规则来对待,可以避免这种问题。
所有权不明确
当任何人都能随意改动时,评审会变得浅薄、错误容易通过;当没人负责时,BUG 堆积数周。
为 web、mobile、backend 和共享模块定义负责人。所有权不应阻止贡献,而是确保改动得到合适的审查。
CI 无节制膨胀
CI 往往从小做起,然后每次事故又加一项“以防万一”的作业。几个月后,流水线既慢又贵,人们开始绕过它。
一个简单规则有效:每个 CI 作业都应有明确目的和负责人,如果它不再捕捉真实问题就应该移除。
警示信号包括:不同作业间重复测试、某些作业红了好几天、所谓“快速改动”验证耗时长于构建时间,以及后端改动触发移动构建等。
发布协调靠部落记忆
如果发布顺序依赖某个人记住那些细节,你们会越发慢且更容易出错。
把发布步骤写下来,使其可重复,并把枯燥的检查自动化。即便你的工具能生成一致的后端和客户端,你仍需要清晰的发布规则。
在决定 monorepo 或 polyrepo 前的快速检查
在重组仓库前,现实地检查团队当前的交付方式。目标不是完美结构,而是减少当一个改动触及 web、mobile 和 backend 时的意外。
问自己五个问题:
- 独立发布能力: 你能在不强制当天更新移动端的情况下发布后端修复吗?
- API 变更规则: 你是否有书面契约来说明弃用策略和旧行为支持多长时间?
- 共享代码纪律: 共享库(UI 组件、API 客户端、业务规则)是否被一致审查并有版本控制?
- CI 能运行关键检查: CI 能否判断改动范围并只运行受影响部分的构建/测试?
- 统一的发布视图: 是否有一处查看 web、mobile、backend 的发布状态、负责人和日期?
举个简单例子:在结账中新增“地址”字段。如果后端先发布,旧的移动应用仍然应可工作。通常这意味着 API 在一段时间内同时接受旧与新 payload,客户端更新为可选而非强制。
接下来的步骤:减少协调成本,自信交付
目标不是“正确”的仓库结构,而是更少的交接、更少的惊喜以及更少的“等等哪个版本在运行?”时刻。
写一份简短的决策记录:为什么选择当前方案、期望改善哪些问题、接受哪些权衡。每 6–12 个月复查一次,或在团队规模或发布节奏改变时更早复查。
在大规模移动文件前,先做最小化能缓解实际痛点的改变:
- 为共享包增加并遵循版本规则。
- 在 CI 中定义并执行契约测试与客户端生成。
- 就 web、mobile、backend 统一发布清单达成一致。
- 对触及多端的改动使用预览环境。
- 设定 CI 时间预算(例如:PR 检查控制在 15 分钟内)。
如果跨代码库耦合是真正瓶颈,减少必须同时改动的地方比只是改仓库结构更有意义。有的团队通过把更多逻辑和数据建模移入单一事实来源来做到这一点。
如果你想探索这种方式,AppMaster (appmaster.io) 可以从共享数据模型和业务逻辑生成后端服务、Web 应用和原生移动应用。低风险的评估方式是先做一个小型内部工具,再根据它减少了多少协调工作来决定下一步。
稳妥的道路刻意地无趣:记录决策、减少耦合、自动化检查,仅在数据表明有帮助时才改变仓库结构。
常见问题
从一个功能需要多频繁同时改动 web、mobile 和 backend 入手。如果大多数工作都是跨端的、且协调是你们的主要痛点,那么 monorepo 或强烈的“单一契约”方法通常能减少出错。若团队很少互相触碰相同领域且需要独立的发布控制,polyrepo 在有严格兼容性规则的情况下也能很好地工作。
通常是 API 漂移、共享库版本不一致和发布时序差异(尤其是移动应用商店的延迟)。解决办法是接受混合版本的现实,不指望完美同步发布,并尽量让破坏性变更少且有明确流程。
把 API 模式当作事实来源并从中生成客户端,这样不匹配会在构建阶段被发现,而不是在 QA 或生产中出现问题。优先采用添加性的变更(新增字段、端点),随后再弃用旧字段;必须重命名或移除时,提供短期兼容窗口。
保持小而频繁的更新(每周优于每季度),对破坏性变更要求明确审批,并随每次变更附上简短的迁移说明。把“完成”定义为“web、mobile 和 backend 的构建都通过”,而不是仅仅某个仓库通过。
默认做法是每个服务单独版本,并制定清晰的兼容规则:后端支持哪些客户端版本、支持多久。共享的发布列车在早期可行,但移动发布延迟常会带来痛点,除非你能容忍等待商店审核。
后端应保持向后兼容,使旧的移动版本在用户更新期间继续可用。采用添加性字段、避免过早移除旧行为,并在必须更改客户端时使用功能开关逐步发布。
把发布协调明确交给某个人负责,通常是技术负责人、发布经理或有工程支持的产品负责人。他们的目标是维护一个简单可重复的日历,并对延迟时的决策(是暂停变更还是保持兼容)给出明确规则。
只构建和测试受到影响的部分,并缓存尽可能多的内容。把快速检查(每次提交)与慢速检查(main 分支、夜间或预发布)分开,这样开发者能快速拿到反馈,而不用每次都跑完整测试集。
使用路径过滤和“仅受影响”策略,这样小改动不会触发对整个仓库的构建。如果共享模块变更,仅对依赖它的应用运行检查,同时保持明确的所有权和评审规则,防止仓库成为“垃圾箱”。
在各个仓库间统一工具和 CI 模板,避免各自重复造轮子。增加一个集成检查以验证关键契约,固定工具链版本,防止“在一个仓库能跑、在另一个仓库不能跑”的惊喜。


