該用 Monorepo 嗎?比較 Monolith vs Multi-Repo vs Monorepo

專案隨著開發時間愈長,伴隨而來的除了體積和複雜度增加之外,也產生難以擴充、缺乏彈性以及打包和部署時間長、效率差等問題。這時候就會開始考慮切分專案,在這裡來聊聊三種管理專案的架構 - MonolithMulti-RepoMonorepo,這篇文章會談到過去在建置專案時遇到的問題,以及根據不同情境而選用的解法,並在最後推薦工具與提供利用 Module Federation 達成 Micro Frontends 的範例來快速上手總結比較

Monolith vs Multi-Repo vs Monorepo 示意圖

Monolith

首先來看的是 Monolith。過去在建置專案時,大多選用 Single-Repo Monolith 的架構,意即將所有的功能都統一放在同一個 repository (簡稱 repo) 底下。如下圖所示,在 WebGether 這個產品的 repo 底下,包含 News、Mall 和 Chat 功能。

Monolith

檔案配置示意如下。

├── assets
├── components
│   ├── Button.js
│   ├── Modal.js
│   └── ...
├── node_modules
├── pages
│   ├── Chat
│   │   ├── Contact.js
│   │   ├── Message.js
│   │   └── ...
│   ├── Mall
│   │   ├── Cart.js
│   │   ├── Product.js
│   │   └── ...
│   ├── News
│   │   ├── Article.js
│   │   ├── Topic.js
│   │   └── ...
├── utils
├── package.json
├── jest.config.js
├── webpack.config.js
├── yarn.lock
└── README.md

這在產品開發初期或非大型規模專案,的確是個簡單方便的好方法。在團隊與產品不斷擴張的狀況下,這個 repo 變得超級肥大,導致打包和部署時間長、效率差;又因為不同團隊可能會用到不同的環境建置與技術線 (tech stack) 而造成相依管理 (dependency management) 困難、難以擴充、缺乏彈性。

例如:WebGether 原本統一使用 React v15 作為前端開發框架,並且用 Redux 作為狀態管理工具,但某天 News 團隊希望改用潮到出水的 Vue,Mall 團隊希望能用 useReducer + useContext 取代 Redux,Chat 團隊無法接受 React 升版 v16.8 導致某最愛的 package 失效。

這時候就要考慮分家了,將不同功能切分為不同專案,切分方式可分為兩種 - Multi-Repo 與 Monorepo。

Multi-Repo

Multi-Repo (或稱 Polyrepo、Many-repo) 是指將個別功能放在不同 repo 底下,如前面的例子,在拆分 News、Mall 和 Chat 後,這三個功能便擁有各自獨立的 repo,同時也能擁有各自的環境和工具的設定檔與技術線。

Multi-Repo

Chat 的檔案配置示意如下。

├── assets
├── components
│   ├── Button.js
│   ├── Modal.js
│   └── ...
├── node_modules
├── pages
│   ├── Contact.js
│   ├── Message.js
│   └── ...
├── utils
├── package.json
├── jest.config.js
├── webpack.config.js
├── yarn.lock
└── README.md

Mall 的檔案配置示意如下。

├── assets
├── components
│   ├── Button.js
│   ├── Modal.js
│   └── ...
├── node_modules
├── pages
│   ├── Cart.js
│   ├── Product.js
│   └── ...
├── utils
├── package.json
├── jest.config.js
├── webpack.config.js
├── yarn.lock
└── README.md

News 的檔案配置示意如下。

├── assets
├── components
│   ├── Button.js
│   ├── Modal.js
│   └── ...
├── node_modules
├── pages
│   ├── Article.js
│   ├── Topic.js
│   └── ...
├── utils
├── package.json
├── jest.config.js
├── webpack.config.js
├── yarn.lock
└── README.md

於是 News 果真改用 Vue 來做前端開發的框架,Mall 不再需要遷就 Chat 的守舊而能快樂使用 React Hooks 以及 Chat 可以繼續使用他們最愛的恐龍級 package,皆大歡喜 ヽ(●´∀`●)ノ

由此看來,使用 Multi-Repo 帶來的優點是…專案體積小、高效率開發、技術線獨立、相依管理簡單、高彈性,因此權責切分乾淨、不同框架時更顯優點。

但缺點就是優點的反面,像是…

與 Multi-Repo 這個概念的相似解法,可參考 Git SubmoduleGit Subtree。Git Submodule 是指建立 main repo 與 sub repo 的 HEAD commit 連結,而 Git Subtree 是指將 main repo 包含 commit log 全部 copy 到新的 repo 中。

看完 Multi-Repo,有沒有什麼能同時兼顧彈性與共用的解法呢?接下來看 Monorepo。

Monorepo

Monorepo

檔案配置示意如下。

├── shared
│   ├── assets
│   ├── components
│   │   ├── Button.js
│   │   ├── Modal.js
│   │   └── ...
│   ├── utils
│   └── ...
├── apps
│   ├── chat
│   │   ├── components
│   │   ├── pages
│   │   │   ├── Contact.js
│   │   │   ├── Message.js
│   │   ├── utils
│   │   └── ...
│   ├── chat-e2e
│   ├── mall
│   │   ├── components
│   │   ├── pages
│   │   │   ├── Cart.js
│   │   │   ├── Product.js
│   │   ├── utils
│   │   └── ...
│   ├── mall-e2e
│   ├── news
│   │   ├── components
│   │   ├── pages
│   │   │  ├── Article.js
│   │   │  ├── Topic.js
│   │   ├── utils
│   │   └── ...
│   └── news-e2e
├── node_modules
├── package.json
├── jest.config.js
├── webpack.config.js
├── yarn.lock
└── README.md

Monorepo 將個別功能放在同一 repo 的不同 package 底下,如前面的例子,在拆分 News、Mall 和 Chat 後,這三個功能雖然仍在同一個 repo 底下,但放置於不同的 pacakge 資料夾。

這麼做的優點是…

就個人開發經驗來說,會依據專案的團隊成員組成、實作細節與維運的複雜度來決定要選用 Multi-Repo 還是 Monorepo 的架構。簡單來說,需要高度共用、緊密合作的狀況下會選用 Monorep;但若功能差異大、彼此沒什麼交集、不想被彼此影響,就會選用 Multi-Repo。看來 Monorepo 在易於分享配置與資源的同時,也能擁有各自想要的彈性,可說是共享與獨立兼顧。

但缺點就是…

順道一提,有些專案是將前後端一同整合至 Monorepo 上,這樣的考量點可能是前後端的開發成員是同一批人、使用同一種語言或便於 Ops 統一管理與版控,優點是能做到有效的資源共享、前後端整合是很便利的,但也意味著複雜度變高,一旦 repo 出現問題前後端都會受影響等,關於這個議題這篇文章提到不錯的觀點與說明。個人經驗來說,由於大多待在前後端分離的團隊,開發人員、語言和環境差異甚大 (React vs Go),並且專案規模龐大,因此都是在這樣的配置下選擇此架構來達成 Micro Frontends。

快速上手

最後,在管理 Monorepo 的工具方面,推薦可用 PNPM、Lerna、Bit、NX 等,有選擇困難症的這裡有重點整理和比較;而 NX 提供了很棒的 Webpack Module Federation 範例,加上 Vercel 對 NX 支援也很友善 (還提供樣板) (註 1),可以參考看看。

總結

比較 Monolith、Multi-Repo 與 Monorepo 如下表。

Monolith Multi-Repo Monorepo
特色 將所有的功能放在單一 repo 將個別功能放在不同 repo 將個別功能放在同一 repo 的不同 package
優點 簡單方便 1. 專案體積小、高效開發;2. 技術線獨立、相依管理簡單、高彈性 共享與獨立兼顧
缺點 1. 專案過於龐大,開發低效;2. 相依管理困難、難以擴充、缺乏彈性 共享不易 repo 龐大、開發需明確規範、檔案權限難以控管、Git 效能差
工具 - - PNPM Lerna Bit NX 等
適用情境 產品開發初期或非大型規模專案 切分大型專案、相依和共享狀況少 切分大型專案、相依和共享狀況多
相似解法 - Git Submodules 或 Git Subtree -

這些架構都有各自適用的狀況,評估後選用適合的解法即可 (๑•̀ㅂ•́)و✧


備註


Monorepo Micro Frontends 微前端 Webpack Module Federation front-end architecture Git Submodule NX