Monorepo:不 是 Design Pattern,是 Repo 管理策略
今天第一次聽到 Monorepo 這個詞。
一開始我還以為同事在講 "moto repo",後來才知道是 mono-repo:
mono= 單一repo= repository
所以 Monorepo 的意思很直白:把多個專案、服務、套件,放在同一個 repository 裡管理。
先講結論
Monorepo 不是 design pattern。
Design pattern 比較像 Singleton、Factory、Observer 這種東西,主要是在講「程式碼裡面的物件或模組要怎麼設計」。
Monorepo 講的是另一個層級:程式碼要放在哪裡、repo 要怎麼切、不同服務要怎麼一起管理。
比較精準的說法是:
- Repository strategy
- Code organization strategy
- 或者把它看成一種工程管理上的架構選擇
我先把它放在 Design Pattern 這區,是因為它跟「軟體怎麼組織」很有關,但要記得它不是傳統 GoF design pattern 那種東西。
Monorepo vs Polyrepo
平常比較直覺的做法通常是 Polyrepo,也有人叫 Multi-repo。
也就是每個服務一個 repo:
app-repo/
worker-repo/
shared-lib-repo/
Monorepo 則是把它們放在同一個 repo 裡:
my-project/
├── apps/
│ ├── app/
│ └── worker/
├── packages/
│ └── shared/ # 共用 types、models、utils
├── package.json
└── ...
簡單說:
| 策略 | 做法 |
|---|---|
| Polyrepo | app 一個 repo、worker 一個 repo、shared lib 也可能一個 repo |
| Monorepo | app、worker、shared package 都放在同一個 repo 裡 |
它解決什麼問題?
以 app + worker 這種場景來看,兩邊很常會共用東西:
- data model
- type 定義
- protobuf schema
- validation rule
- 共用 utils
如果這些東西分散在不同 repo,就很容易遇到版本同步問題。
例如 app 改了某個 field,但 worker 還沒更新,就可能變成:
app 已經送出 new_field
worker 還只認得 old_field
→ runtime 才爆
Monorepo 的好處是可以把這種跨服務修改放在同一個 PR 裡:
改 shared type
改 app
改 worker
CI 一起跑
一次 merge
這就是很常被提到的 atomic commit。
意思是:相關的修改可以在同一個 commit / PR 裡一起完成,不會卡在「A repo 已經改了,但 B repo 還沒跟上」的中間狀態。
Monorepo 的好處
共用程式碼比較自然
shared type、model、utils 可以直接放在 packages/shared 之類的地方。
不用每次改一個 type 就發新版套件,也不用到處更新 dependency version。
跨服務重構比較舒服
如果今天要把欄位從 user_id 改成 account_id,Polyrepo 可能要開好幾個 PR。
Monorepo 可以一次改:
- app
- worker
- shared package
- tests