seansie's blog

程式碼的圍牆與市集:Monorepo 與 Multirepo 的權衡與清醒選擇

軟體工程領域每隔幾年就會上演一次「分久必合,合久必分」的輪迴。在架構設計上,我們從單體架構(Monolith)走向微服務(Microservices),如今又在討論要不要回頭擁抱宏石(Modular Monolith);而在代碼組織上,這場爭論則演化為 Monorepo(單一倉庫)Multirepo / Polyrepo(多倉庫) 之間的信仰之戰。

很多人把 Monorepo 視為大廠靈藥,以為換上了就能享有 Google 或 Meta 那樣的研發流暢感;也有人誓死捍衛 Multirepo 的邊界感,認為混在一起只會走向失控的泥潭。但脫離業務場景談架構都是耍流氓。這兩種模式從來不是先進與落後的對立,而是「協作摩擦力」「工具鏈複雜度」之間的清醒交換。


一、 Multirepo:界限分明的「城邦自治」

Multirepo 是業界最直覺、最自然的模式。一個專案、一個倉庫、一組獨立的 CI/CD 流程。每個倉庫就像一個封閉自治的城邦,井水不犯河水。

1. 為什麼人人一開始都愛 Multirepo?

  • 權限與所有權極其純粹:團隊 A 的工程師不需要也不應該接觸到團隊 B 的核心邏輯。如果你的專案包含外包團隊、第三方合作廠商,或跨國敏感業務,Multirepo 是天然且唯一的權限防火牆。
  • CI/CD 直觀、心智負擔低:一個 Commit 觸發一條流水線,構建、測試、部署,邏輯線性且可預測。你不需要為了「這次變更到底影響了誰」去算一張複雜的依賴拓撲圖。
  • 輕量與敏捷:Clone 專案只需三秒,IDE 打開絲滑順暢,沒有幾百萬行代碼壓垮記憶體的痛苦。

2. 隨規模擴大而來的「多倉庫地獄」

然而,當系統業務擴張、出現多個前端應用或微服務共用某些底層邏輯時,Multirepo 的隱形維護成本會呈指數級暴增:

  • 發布地獄(Release Hell):改了一個共用 UI 元件庫的 API,流程變成:修改共用庫倉庫 => 跑 CI $\rightarrow$ 發布 npm 新版本 => 分別切換到 App A、App B、App C 倉庫開 PR 升級依賴 => 分別測試發布。一個小改動,耗費半天在 Git 與發版流程中切換。
  • 依賴分裂與技術債隱蔽:每個倉庫各自為政,App A 用著 React 17,App B 升級到了 React 19,底層工具鏈版本百花齊放。當安全漏洞爆發時,往往沒有人清楚全公司究竟有幾個倉庫引入了漏洞版本。
  • 重構的巨大阻力:跨倉庫重構就像盲人摸象。你改了共用庫,很難在本地直接確認是否會打破某個應用的執行邏輯,只能祈禱下游團隊升級時測試足夠覆蓋。

二、 Monorepo:打破隔閡的「超級聯邦」

Monorepo 把多個獨立但相關的專案、元件庫、甚至前後端代碼統一收納在一個 Git 倉庫中。它追求的不是代碼的「混雜」,而是代碼透明度與極致的重構體驗

1. Monorepo 讓人回不去的致命優點

  • 原子化提交(Atomic Commits):前端 App、共用 UI 元件庫、後端 TypeScript DTO 可以在同一個 Commit 與 PR 中修改完成。一次變更,全局校驗,告別「版本不同步」的噩夢。
  • 零成本程式碼共用:透過現代工具(如 pnpm workspace),子專案之間以符號連結(Symlink)互通。修改底層 Utility 函式,上層業務 App 在本地立即熱更新,開發體驗極為流暢。
  • 單一真相來源(Single Source of Truth):全專案共用同一套 TypeScript 設定、ESLint 規則與依賴版本。升級基礎設施只需要改一個檔案,就能讓全團隊享有最新的工具鏈。

2. Monorepo 的殘酷代價:工具鏈門檻

天下沒有免費的午餐。如果你沒有做好準備就衝進 Monorepo,迎面而來的將是另一種維護噩夢:

  • 構建時間爆炸:如果每次 PR 都把所有專案全量 build 一遍,CI 管道會直接癱瘓。你必須引入 Turborepo、Nx 這類任務編排工具,配置嚴謹的快取(Caching)與受影響分析(Affected Analysis)。
  • 幽靈依賴與邊界模糊:模組間引用太過容易,容易導致架構設計崩壞,寫出錯綜複雜的網狀依賴(Spaghetti Dependencies),最終失去邊界。
  • Git 倉庫膨脹與權限困境:缺乏細粒度權限控制;隨著時間推移,倉庫體積(.git 目錄)龐大,Git 檢索變慢。

三、 深度對決:關鍵維度橫向評估

維度 Multirepo(多倉庫) Monorepo(單一倉庫)
跨專案重構效率 差(需跨倉庫多階段發版驗證) 極高(單一 PR 即時驗證)
代碼共用成本 高(必須透過 npm 私有源發布流程) 極低(本地 Workspace 軟連結直接引用)
依賴與工具鏈一致性 差(版本極易分裂、各自維護) 極佳(全域集中配置、一處升級全域生效)
CI/CD 與構建難度 簡單(各專案獨立,線性流水線) 複雜(需依賴受影響分析與分散式快取)
權限控制與安全性 極佳(以倉庫為邊界天然隔離) 弱(預設全量可見,需額外權限工具)
基礎設施維護成本 低(開箱即用) 高(需要工程師具備 Monorepo 工具維護能力)

四、 選擇的藝術:如何做決定?

架構的本質是取捨。與其爭論優劣,不如檢視當前團隊的痛點與資源:

強烈建議選擇 Monorepo 的情境:

  1. 強依賴共用模組的生態系:例如公司有獨立的 Design System,同時供給前台官網、中後台管理系統、行動端 H5 共同使用。
  2. 全端 TypeScript 協作:前後端同構(Node.js/NestJS + Next.js/React),需要高度共用 Type DTO 與驗證邏輯。
  3. 中小型團隊、協作密切:全體工程師同在一個業務邊界內,追求最快的迭代與重構速度,且團隊願意採用 pnpm + Turborepo 建立標準化流程。

維持或回歸 Multirepo 的情境:

  1. 團隊界限與業務生命週期完全獨立:各業務線技術棧天差地別(如 Java 後端、Python 數據分析、Vue 前端),彼此幾乎沒有代碼共用需求。
  2. 存在嚴格的外包或資料權限邊界:無法容許外部開發者拉取整個組織的程式碼。
  3. 沒有餘力維護基礎設施:團隊缺乏心力調校構建快取、Task Pipeline 與版本發布自動化。

代碼放在一個目錄還是多個目錄,從來不會直接決定專案的成敗。

Monorepo 解決的是協作的溝通成本與共用的流暢度,代價是提高的工具鏈門檻;Multirepo 提供的是清晰的物理邊界與簡潔的維護心智,代價是跨模組協作的摩擦力。

理性的工程決策不是追逐流行,而是認清團隊當下最大的瓶頸在哪裡——然後,為你選擇的代價買單。