跳转到内容

Monorepo 还是多个 Repository?

一个 Remote,就应该一个 Repository 吗?

Section titled “一个 Remote,就应该一个 Repository 吗?”

这个想法一开始很顺:

一个 Team
→ 一个 Microfrontend
→ 一个 Repository
→ 一条 Pipeline
→ 一个 Release

如果微前端的目标是形成独立 Frontend Area,那么把所有东西放在同一个 Repository 里,看起来似乎会破坏这种独立性。毕竟 Source Code 在一起,Dependency 彼此可见,理论上所有 Team 都能访问相同文件;Root Configuration 的修改还可能影响整个 Workspace。

于是很容易得出结论:每个 Remote 都应该拥有自己的 Repository。

但真实产品里还有同样合理的另一种结构:

一个 Monorepo
├── Host
├── Remote A
├── Remote B
├── Remote C
├── Shared Platform Libraries
└── 分离的 Build 与 Deployment

在这种结构中,Remote 仍然可以独立 Build、Test、Publish,并由不同 Team 负责。共同 Repository 只说明这些 Project 被管理在同一个 Source Space 中。

它并不意味着它们必须生成同一个 Artifact,也不意味着必须一起 Release。

反过来,多个 Repository 也不会自动产生真实独立性:

Repository A
Repository B
Repository C
但是:
├── 共同 Approval
├── 中央 Test Environment
├── 同步 Release
├── 手工 Contract Migration
└── 共同 Operations Calendar

Source Code 被物理分开了。

Change Process 与 Release Process 仍然耦合。

因此,第一步不是决定 Repository 数量,而是先问:

Repository Boundary 实际表达的到底是什么边界?

微前端允许使用多个 Repository,但并不要求这样做。

Repository Boundary 不会自动等于 Deployment Boundary,也不自动等于业务边界或组织边界。只要 Deployable、Ownership 和 Pipeline 仍然分开,Monorepo 完全可以支持 Independent Release。多个 Repository 可以强化自治,但会提高 Shared Toolchain、本地开发和 Cross-Repository Change 的成本。

这个决定还有一个组织层面:Monorepo 尤其适合共享 Governance 的范围。多个 Repository 则经常在 Access Right、Budget Ownership、Release Authority 和 Organizational Control 真正分离时更有价值。

讨论 Monorepo 时,很多人默认下面这些边界应该全部重合:

Repository Boundary
Deployment Boundary
Team / Ownership Boundary
Business Boundary

它们可以重合。

但不必须。

Repository Boundary 决定的包括:

  • 哪些 Source Code 一起 Version;
  • 哪些变化可以在一个 Commit 中 Atomic 完成;
  • 哪些 Toolchain 与 Rule 集中可用;
  • 哪些人访问同一个 Source Space。

它首先描述的是共同 Development/Control Space。

Deployment Boundary 决定:

  • 哪个 Artifact 可以独立 Build;
  • 哪个单元可以单独 Publish;
  • 一个 Deployable 使用哪条 Pipeline 和 Runtime;
  • 一个 Release 是否可以不带上其他 Deployable。

它出现在 Source Code 变成可独立发布和运营单元的地方。

Team/Ownership Boundary 决定:

  • 谁负责修改;
  • 谁批准 Release;
  • 谁在 Incident 时响应;
  • 谁做 Architecture/Product Decision。

Ownership 因此远远不只是“谁有权限改文件”。

它包括行为、质量、运行与长期演进责任。

业务边界决定:

  • 产品区域拥有什么 Capability;
  • 哪些 Model/Rule 在这个 Context 中成立;
  • 哪些 Data 与 Use Case 属于这个 Responsibility。

它应该跟随产品业务,而不是文件夹结构。

一个 Repository 可以包含多个业务和技术边界。多个 Repository 也完全可能共同组成一个高度耦合的 Change Unit。

Repository Structure 应该支持期望的工作与 Governance 结构。

它不会自动创造这种结构。

四层视图分别展示 Repository、Deployment、Ownership 与业务边界;一个 Monorepo 内可以同时存在多个独立 Deployable,对应不同 Team 与 Capability。

微前端的技术优势,是允许不同 Frontend Area 被独立 Integration 与 Release。

这种能力可以对应很多组织模型:

  • 一个共享 Monorepo;
  • 每个 Remote 一个 Repository;
  • 每个 Business Unit 一个 Repository;
  • 一个总产品里有多个 Monorepo;
  • External Partner Repository;
  • 上述方式的 Hybrid。

“可以分布”并不意味着“必须最大化分布”。

技术自治意味着你可以分开,而不是要求所有可以分开的东西都立即被分开。

Remote 不应该因为技术 Integration 机制限制,就被迫放进同一个 Repository。

但它也不需要为了证明“自己是 Microfrontend”而被人工搬出去。

一个 Remote 一个 Repository,不是微前端的自然结论,而是组织决策。

尤其在一个共同产品中,多个 Remote 完全可以业务上分离、独立 Release,同时共享相同 Platform Standard、Development Tool 和 Quality Rule。

这种情况下,Monorepo 可以降低协作成本,却不会因此消除 Deployment Boundary。

为什么 Monorepo 不会阻止 Independent Release

Section titled “为什么 Monorepo 不会阻止 Independent Release”

一个 Monorepo 可以包含多个独立 Deployable:

workspace
├── host
├── todos-remote
├── tasks-remote
├── members-remote
├── platform-contracts
└── shared-tooling

这些 Remote 仍然可以拥有自己的 Release Unit:

todos-remote
├── 自己的 Artifact
├── 自己的 Pipeline
└── 自己的 Deployment Time
tasks-remote
├── 自己的 Artifact
├── 自己的 Pipeline
└── 自己的 Deployment Time

Repository 和 Release 处在不同层级。

一个 Commit 可以修改多个 Project,却只发布其中受影响的 Deployable。Build 与 Deployment 也可以只针对 Affected Unit。

Monorepo 只有在 Process/Pipeline 把所有 Project 人工绑在一起时,才会变成 Release Monolith。

例如:每次修改都强制 Full Product Build;所有 Test 不管是否受影响都全部运行;中央 Approval 永远一次发布全部 Deployable。

这种 Coupling 不是 Repository Root 造成的。

而是 Build、Test 与 Release Logic 的选择。

Monorepo 不会阻止 Independent Release。Global Build/Test/Approval Logic 会。共同 Source Space 不应该等于共同 Release。

Independent Release 也不是“每次修改只能碰一个 Project”。

它意味着不受影响的 Deployable 不应该仅因为 Source Structure 原因被迫一起 Release。

Monorepo 的优势有时会被描述成个人使用方便。

其实它们直接影响开发经济性。

Host、Remote、Platform Contract 和 Library 之间的 Dependency 可以统一分析。Graph 不会替代 Architecture,但可以让技术后果变得可检查。

一次跨 Project 修改可以在一个 Commit 完成:

一个 Commit
├── Platform Contract
├── Host Adapter
├── Remote A
└── Tests

变化作为一个完整事件保持可见。Contract 不会在一个 Repository 修改后,Consumer 在另一个 Repository 中被遗忘。

但 Atomic 不等于必须同时 Deployment。如果 Artifact 可以在不同时间 Publish,Contract 仍然必须处理 Version Gap。

Lint、Test、TypeScript Configuration、Build Tool 和 Quality Rule 可以集中维护。

这件事看起来不如架构图炫,但经济价值往往很高。

多个 Deployable 可以从同一个 Workspace 启动,并验证真实 Integration。对于跨 Host、Remote 与 Platform Service 的 Flow 尤其方便。

Rename、API Change 和 Structure Refactoring 可以 Repository-Wide 搜索、评估与执行,不需要在多个 Source Space 间来回跳。

这并不意味着 Team 可以随意修改别人的 Code。

只是影响范围更容易看见,技术上也更容易在同一次 Change 中处理。

Code、Contract、Test 和 Technical Documentation 位于共同 Navigation Space。新 Team Member 更容易理解产品关系;Platform Rule 的真实使用方式也更容易查到。

这些优势不只是 Comfort,它们会减少协调变化的真实成本。

Monorepo 的优势来自“近”。

风险同样来自“近”。

所有东西都在技术上可访问时,一个 Remote 很容易直接 Import 另一个产品区域的 Internal Implementation。需要一个 Type 或 Helper,隔壁目录就在眼前。

短期 Shortcut 很容易变成长期 Dependency。

Repository 中的物理接近,不应该被误解为 Architecture 上允许耦合。

相似 Code 很容易被过早集中。两个局部实现刚出现,就被提到 Shared Library;很快多个 Remote 都依赖它;之后一个小修改就影响整个产品。

Shared Library 并不天然错误。

但它需要稳定 Shared Purpose 和清晰 Ownership。

“看起来相似”本身还不是 Shared Purpose。

Central Toolchain 简化维护,同时也扩大 Change Radius。Root Configuration 的一次 Upgrade 可能影响 Workspace 很多区域,即使 Deployable 仍然独立 Release。

因此 Central Toolchain 本身就是 Platform Responsibility。

因为所有东西都在同一个 Pull Request 里可修改,一个 Team 可以顺手改别人 Code。

技术上很高效。

组织上可能绕过 Ownership。

Atomic Change 的便利,不应该变成“先改完再通知 Owner”。

损坏的 Root Configuration 或 Global CI Rule 可能同时阻塞大量 Deployable。Remote 自己 Code 完全没变,也可能无法 Release。

Central Library 的变化可能触发大量 Remote Build/Test。

这不只是 CI 慢,也显示多少 Responsibility 被集中在一个 Shared Component 中。

Monorepo 正因为 Cross-Boundary Change 太容易,才尤其需要 Governance。

Affected Strategy 使用 Project/Dependency Graph,从 Change 推导受影响 Project:

Change
Project / Dependency Graph
Affected Projects
├── Builds
├── Tests
├── E2E Scenarios
└── Deployments

Nx Affected 是这个思想中很典型的实现。

重点不是具体 Tool,而是 Graph-Based Selection。

没有变化、也不依赖变化区域的 Project,不必每次都重新 Build 或完整 Test。

不过结论质量取决于 Graph 是否真实。Direct Project Dependency 可以被分析;Hidden Runtime Coupling、Shared External Configuration 或 Implicit Contract 可能不在 Graph 中。

Affected 让 Coupling 可见,但不会消除 Coupling。

简单例子:

修改 Remote A
└── 只影响 Remote A

一个真正 Local Change 会拥有很小 Affected Radius,可以只 Build、Test、Deploy Remote A。

Central Library 则可能变成:

修改中央 Platform Library
├── Host
├── Remote A
├── Remote B
└── Remote C 全部受影响

这可能完全合理。例如 Authentication Context 或稳定 Lifecycle Contract,本来就应该影响所有 Integration Partner。

也可能说明 Shared Library 太宽,把业务上独立的东西揉在了一起。

大 Affected Radius 不是 Monorepo 的缺点,而是 Architecture Signal。

为什么多个 Repository 会让 Autonomy 更明显

Section titled “为什么多个 Repository 会让 Autonomy 更明显”

理想化的 Polyrepo Model 把 Remote 物理分开:

Remote A Repository
├── 自己的 Source Code
├── 自己的 Pipeline
├── 自己的 Toolchain
└── 自己的 Release
Remote B Repository
├── 自己的 Source Code
├── 自己的 Pipeline
├── 自己的 Toolchain
└── 自己的 Release

某些 Boundary 会因此立即可见。

Technical Access Boundary 更明确。Team 可以独立升级 Toolchain。Pipeline 与 Release 在组织上分开。一个 Repository 损坏不一定阻塞全部。外部组织也不需要访问完整产品 Source Code。

Ownership 更不容易被“顺手”越过,因为修改外部 Remote 必须明确进入另一个 Responsibility Space。

多个 Repository 可以保护 Autonomy,因为跨 Owner 修改不再是顺手动作。

物理分离仍然不会自动创造 Autonomy。

但它会让组织 Coupling 更显眼,也更昂贵:共同 Change 必须明确穿越 Repository、Version 或 Coordination Boundary。

Monorepo 技术上也可以实现同样自治,只是需要用 Governance 主动保护,而不是依靠物理距离。

这种物理分离通常被称为 Polyrepo。只要技术边界对应真实 Responsibility,它完全可能合理。

但它仍然不保证 Architecture 独立。

多个 Repository 依旧可能被同步 Release、Central Approval、Shared Test Environment、Manual Integration 或尚未发布的 Artifact Dependency 绑在一起。

Code 分开了。

Change Mechanics 仍然可以耦合。

多个 Repository 经常给组织带来更清晰的边界。

技术和运维上却并不免费。

Repository 可以使用不同 Framework、Build Tool、Lint 与 Test Version。

这种自由在 Lifecycle 差异很大时可能有价值。

也可能增加 Maintenance:Security Fix、Quality Rule 和 Build/Test Knowledge 被分布到多个 Variant。

每个 Repository 往往都需要自己的:

  • CI Configuration;
  • Dependency Update;
  • Quality Rule;
  • Release Automation;
  • Local Start Documentation;
  • Security / Compliance Configuration。

Central Template 可以减少重复。

但那又会重新引入一个需要 Version 和维护的 Shared Platform Dependency。

需要验证 Product-Wide Flow 时,开发者必须 Checkout 多个 Repository、启动多个项目,并保证 Version 兼容。

平时不是每个 Team 都需要完整产品。

但一旦排查 Integration Bug,分布式 Source Structure 就会变得非常真实。

Contract、Implementation、Test 与 Owner 分散在多个位置。

Global Search 被 Catalog、Documentation 和 Package Registry 取代。

可以做好。

但必须有意识维护 Navigation 与 Information Structure。

共同修改需要多个 Pull Request、Version 与协调 Rollout。

好处是它清楚显示多个 Responsibility Area 被触碰。

代价是 Operations Work 增加。

Consumer 和 Provider 无法在一个 Atomic Commit 中完整切换。

Transition Compatibility 变成必要条件。新旧 Contract Variant 往往必须暂时并存。

组织分离不会消除 Coordination,只会把它从 Repository 移到 Contract、Version 与 Release Flow。

多 Repository 会让 Coupling 更明显,但不会自动让它更小。

Atomic Change,还是 Compatible Migration

Section titled “Atomic Change,还是 Compatible Migration”

Monorepo 与多个 Repository 一个重要 Trade-Off,就是 Shared Change 的 Mechanism。

一个 Commit
├── 修改 Contract
├── 调整 Provider
├── 调整 Consumer
└── 更新 Tests

这种 Change 很容易协调,整体 Context 保持可见,错误也能较早发现。

但 Atomicity 有一个风险:它会掩盖多个独立 Deployable 实际上都受到影响。

如果只有全部一起 Publish 后才工作,那么即使所有代码在一个 Commit 完成,Release Coupling 依然存在。Monorepo 让 Implementation 很方便,却没有解决 Deployment Time 不一致的问题。

所以即使 Deployable 位于同一个 Monorepo,只要 Release Time 可以分开,仍然需要 Compatible Transition。

在分离 Repository 中,Transition 通常更显式:

1. Provider 以兼容方式扩展 Contract。
2. 发布新 Contract Version。
3. Consumer 独立迁移。
4. 新旧 Variant 暂时并存。
5. 最后删除旧 Variant。

这个方式支持 Independent Schedule。Consumer 不需要同时 Release,组织自治被技术上认真对待。

代价是更多 Transition Logic、Versioning、更长 Migration Period 和更多 Coordination。

Monorepo 让 Coordinated Change 很便宜。多个 Repository 更经常强迫团队做 Compatible Change。

分离 Repository 会让 Independent Change 更贵,但通常也更诚实。

这并不是反对 Atomic Change。

Atomic Change 很有价值,只要不要把它与 Forced Joint Deployment 混为一谈。

左侧是在 Monorepo 中原子修改 Contract、Provider、Consumer 与 Test;右侧是多个 Consumer Repository 按时间逐步迁移到兼容扩展的 Contract。

Repository Decision 经常被当作纯技术 Scaling 问题。

很多产品里,Governance 才是更重要维度。

Monorepo 很适合这样的结构:

共同产品
├── 共同 Governance
├── 共同 Technical Leadership
├── 共同 Access Rule
├── 共同 Quality Standard
├── 相似 Toolchain
└── 协调 Platform Responsibility

此时组织确实有共同兴趣:Central Technical Rule 集中维护、Dependency 可见、Coordinated Change 高效完成。

这不意味着每个 Team 必须相同 Release Time 或相同 Priority。

只意味着共享 Control/Development Space 本身是被需要的。

Monorepo 优化的是 Shared Governance 范围内的协作。

共同 Governance 必须具体。例如谁负责 Root Configuration、Platform Library、Architecture Rule、Central Pipeline 和跨项目 Quality Standard。

Monorepo 付出的成本是 Governance。

如果没有 Governance,共同 Workspace 很快会变成一个“人人都能改,没人真正负责中央决策后果”的空间。

Geographic Distance 不是 Repository Boundary。

一个 Product Team 可以分布在 Berlin、Munich、New York 和 Singapore,却仍然共享 Governance:同样的 Access Rule、Architecture Standard、Product Goal 和 Platform Owner。

这种情况下,Monorepo 甚至可以用共同 Technical Context 抵消空间距离,让 Dependency、Contract 和 Quality Rule 保持可见。

反过来,两个 Team 即使坐在同一栋楼,也可能组织上完全分离。如果 Budget、Approval、Access Right 和 Responsibility Model 都不同,物理距离对 Repository Decision 几乎没有意义。

真正相关的距离不是地理距离,而是组织距离。

多家公司与 Business Unit 会改变答案

Section titled “多家公司与 Business Unit 会改变答案”

当一个总产品由多个真正独立的组织单元参与时,情况会变化:

共同 Product
├── Business Unit A
├── Business Unit B
├── Subsidiary
├── External Vendor
└── Partner Company

它们之间可能拥有不同 Access Right、Confidentiality、Budget Ownership、People Responsibility、Release Approval、Compliance、Operations Model、Priority、Lifecycle、Liability 与 Support。

技术上仍然可以放进一个 Monorepo。

组织上,它就变成共同 Control Space。

于是必须长期回答:

  • 谁能读哪些区域?
  • 谁能改 Global Rule?
  • 谁拥有 Central CI?
  • 谁决定 Toolchain Upgrade?
  • Main Pipeline 被破坏时谁负责?
  • 谁有权 Release 其他组织的 Product Area?

这些问题都能解决。

但长期解决它们的成本,可能比 Shared Source Space 带来的收益更高。

Monorepo 在 Shared Governance 内可以很好扩展;跨 Governance Boundary 时,同样的集中性可能变成冲突。

Ownership、Access Right 和 Release Responsibility 分离得越远,共同 Repository Root 就越不应该成为前提。

此时多个 Repository 可以保护组织自治:

Organisation A
├── 拥有 Repository A
├── 拥有 Pipeline A
├── 拥有 Remote A
└── 发布 Remote A
Organisation B
├── 拥有 Repository B
├── 拥有 Pipeline B
├── 拥有 Remote B
└── 发布 Remote B

一个组织不需要访问其他组织完整 Source Code。Toolchain Decision 可以本地完成。某个 Pipeline 失败不自动阻塞所有人。Release Approval 留在负责组织内。Contract 与监管边界也可以技术上体现。

Multiple Repository 在这里不一定技术更简单。

但组织上可能更平静。

Monorepo 优化 Shared Governance 内的协作。多个 Repository 可以保护跨 Governance Boundary 的自治。

左侧是地理分布 Team 在共同 Governance 下使用 Monorepo;右侧是多个 Business Unit、Partner、Supplier 分别维护自己的 Repository/Monorepo;中间展示 Hybrid Model。

整个产品不必统一选择一种 Repository Strategy。

例如:

Overall Product
├── BU A Monorepo
│ ├── Remote A1
│ ├── Remote A2
│ └── API A
├── BU B Monorepo
│ ├── Remote B1
│ └── Remote B2
├── Partner Repository
│ └── Remote C
└── Versioned Platform Contracts

Business Unit 内相关 Remote、API 与 Library 可以留在一个 Monorepo,从 Shared Toolchain、Discoverability 和 Atomic Change 中获益。

其他 BU、Company 或 Supplier 使用自己的 Repository/Monorepo,在各自 Governance 下 Publish Deployable,并通过 Versioned Platform Contract 进行 Integration。

这些 Contract 应该只保留真正必要内容,例如:

  • Mounting/Lifecycle Contract;
  • Authentication Context;
  • Navigation;
  • Design System 或 Tokens;
  • Observability;
  • Published API/Event Contract。

Repository Structure 不需要一比一复制 Remote Structure。

每个 Governance Space 一个 Repository,可能比每个 Remote 一个 Repository 更合理。

Hybrid Model 不是 Universal Best Practice。

它只是说明:Remote 是 Deployment/Integration Decision,而 Repository 还额外组织 Collaboration、Access 与 Control。

不需要物理分离,也可以拥有 Ownership

Section titled “不需要物理分离,也可以拥有 Ownership”

Monorepo 不会阻止清晰 Ownership。

但它要求 Ownership 变得可见、可检查。

可以使用:

  • Code Ownership;
  • Project Tag 与 Module Boundary;
  • Restricted Import;
  • 分离 Pipeline;
  • 分离 Release Permission;
  • 明确 Owner;
  • Architecture Test;
  • 不同 Deployment Target。

这些机制不会替代 Collaboration。

但可以避免 Shared Source Space 被理解成“谁都可以跨界”。

只存在于 Organigram 的 Ownership 很弱;完全依靠分离 Repository 强制 Ownership,又可能过于昂贵。

很多时候合理强度在中间:

Shared Workspace
+
Visible / Enforced Boundaries
+
Separate Deployments
+
Clear Owners

当 Shared Governance 存在、Coordinated Change 经常发生、Toolchain 相似、Platform Contract 经常共同演进时,Monorepo 很合理。Shared Local Development、Central Discoverability 和可控制 Access Right 也支持这个选择。

前提始终是:尽管 Source Space 共同,Deployment 仍然独立,Module/Ownership Boundary 也真正有效。

多个 Repository 更适合:不同 Company 或 Contract Partner、Business Unit 真正自治、Access Right 差异很大,或者 Compliance/Approval Process 各自独立。

明显不同 Lifecycle、独立 Funding、差异很大的 Toolchain、几乎没有 Atomic Cross-Project Change、或者必须严格 Confidentiality,也都可以构成 Repository Boundary 的理由。

Repository Boundary 在映射真实 Governance、Trust 或 Access Boundary 时最有意义。

Monorepo 与多个 Repository 不是信仰问题。

它们只是成本分布不同。

Monorepo
Strengths
├── Atomic Changes
├── Shared Toolchain
├── Visible Graph
├── Easy Discoverability
└── Efficient Local Development
Costs
├── Governance
├── Module Boundaries
├── Central Toolchain Decisions
├── Potentially Large Affected Radius
└── Protection Against Accidental Coupling
Multiple Repositories
Strengths
├── Clear Access Boundaries
├── Autonomous Toolchains
├── Separate Pipelines
├── Organizational Isolation
└── Visible Ownership
Costs
├── Cross-Repository Coordination
├── Version Management
├── Duplicate Automation
├── Harder Local Integration
└── Compatible Migrations

Monorepo 用 Governance 付账。多个 Repository 用 Coordination 付账。

一个 Monorepo 可以显著简化由多个 Independent Remote 组成的产品,但这种接近需要清晰 Ownership、有效 Module Boundary 和分离 Release Process。

多个 Repository 则尤其适合 Governance、Access Right、Company 或 Release Responsibility 真正分开时。这里 Organizational Autonomy 可能比 Atomic Change 的效率更重要。

Repository Boundary 不是 Deployment Boundary。

Affected 可以让 Coupling 可见,却不会删除 Coupling。

Repository Structure 应该表达这样一条边界:在这条边界内部,共同修改和共同控制本来就是被需要的。

Repository 不决定 Release 是否独立。它决定共同变化与组织自治分别要付出多少成本。