Published on

將公司專案轉換成 Monorepo 遇到的那些事 - 第六篇 如何管理 Monorepo 中的版本控制與發布流程

Authors
  • avatar
    Name
    Alex Yu
    Twitter
Git Version ontrol

第六篇 如何管理 Monorepo 中的版本控制與發布流程

前言:Monorepo 下的版本管理挑戰

在先前的系列文章中,我們已經深入探討了 Monorepo 的概念、遷移策略、Nx 的 Library 型別以及如何解決循環依賴問題。現在,我們將面對 Monorepo 架構中另一個至關重要的挑戰:版本控制與發布流程
在傳統的多儲存庫(Polyrepo)架構中,每個專案都有自己獨立的版本號和發布週期,相對簡單明確。然而,當數十個相互依賴的套件或應用程式共存於同一個 Monorepo 中時,版本管理與發布流程就變得複雜許多:

  • 如何協調不同套件之間的版本依賴?
  • 當一個套件變更時,如何決定其他依賴它的套件是否需要更新版本?
  • 如何避免「版本地獄」,確保發布過程順暢?
  • 如何在 Monorepo 架構下適當運用 Git Flow 或其他工作流?

本文將從以下幾個方面深入探討這些問題,並提供實用的解決方案:

  1. Monorepo 中的版本管理策略與模式
  2. Git Flow 在 Monorepo 中的調整與應用
  3. 依賴管理與版本控制的整合
  4. 自動化發布流程的實現方案
  5. 實戰案例:我們如何解決公司專案中的版本管理難題

一、Monorepo 中的版本管理策略

1.1 三種主要版本管理模式

在 Monorepo 架構中,主要有三種版本管理模式,每種模式各有優缺點:

模式一:獨立版本(Independent Versioning)

每個套件擁有自己獨立的版本號,可以單獨發布更新。
優點:

  • 版本語意明確,每個套件的版本號反映了自身的變更
  • 專案間耦合度低,可以獨立演進
  • 使用者只需更新真正有變更的套件

缺點:

  • 版本依賴管理複雜,需要精確指定所有套件間的依賴關係
  • 當多個套件需要同時更新時,流程繁瑣
  • 需要更強大的工具支援來追蹤版本依賴圖

模式二:鎖步版本(Lockstep Versioning)

所有套件共用同一個版本號,一起發布更新。
優點:

  • 版本管理簡單直觀
  • 不會有版本依賴不相容的問題
  • 發布流程統一,減少協調成本

缺點:

  • 即使只有一個套件的微小變更,也會導致所有套件版本號更新
  • 版本號失去對單一套件變更的語意
  • 可能導致不必要的頻繁更新

模式三:混合版本(Hybrid Versioning)

將相關套件組織為版本群組,每個群組內採用鎖步版本,不同群組之間採用獨立版本。
優點:

  • 在相關性強的套件間保持版本一致性
  • 不相關的套件可以獨立演進
  • 在簡便性和精確性之間取得平衡

缺點:

  • 需要明確定義套件群組
  • 實作複雜度高於前兩種模式
  • 仍需處理群組間的依賴問題

1.2 選擇合適的版本管理策略

如何在這三種策略中做出選擇?以下是一些參考因素:
1.專案規模:小型 Monorepo(少於 10 個套件)通常適合鎖步版本,而大型 Monorepo 可能需要考慮獨立版本或混合版本。
2.專案間耦合度:如果套件間高度耦合且經常同時變更,鎖步版本可能更合適;如果套件間相對獨立,獨立版本更合理。
3.團隊組織:多團隊維護不同套件時,獨立版本或混合版本可能更易於管理,因為它們允許不同團隊按自己的節奏發布。
4.使用者體驗:考慮套件的消費者如何處理版本更新。獨立版本允許更精確的升級,但可能增加依賴衝突的風險。
5.維護成本:鎖步版本的維護成本較低,而獨立版本需要更多的依賴管理工作。

實務經驗分享:
在我們公司的實際經驗中,我們採用了「鎖步版本」策略,即所有套件共用同一個版本號。這種做法對於我們的中小型團隊來說非常實用,因為:

  1. 我們的套件數量適中,且套件間有較高的相互依賴性
  2. 團隊規模不大,所有人都在同一個專案上工作
  3. 我們希望盡量簡化版本管理流程,不需要複雜的工具如 Lerna 或 Changesets

這種簡單的版本管理策略讓我們能夠專注在功能開發上,而不必花費過多時間在版本協調上。

1.3 語意化版本(Semantic Versioning)在 Monorepo 中的應用

無論採用哪種版本管理策略,遵循語意化版本(SemVer)的原則都至關重要。語意化版本使用 主版本號.次版本號.修訂號 的格式(如 1.2.3):

  • 主版本號 :當進行不相容的 API 變更時遞增
  • 次版本號 :當以向後相容的方式新增功能時遞增
  • 修訂號 :當進行向後相容的錯誤修正時遞增

在 Monorepo 環境中,嚴格遵循語意化版本有助於:

  1. 清晰地傳達變更的性質和範圍
  2. 使依賴管理更可預測
  3. 減少由版本升級導致的意外問題

以下是在 Monorepo 中應用語意化版本的實踐建議:

// 在 package.json 中明確定義依賴關係
{
  "name": "@myorg/feature-x",
  "version": "1.2.3",
  "dependencies": {
    "@myorg/shared-utils": "^2.0.0", // 主版本號鎖定
    "@myorg/ui-components": "~3.4.0" // 次版本號鎖定
  }
}

關於版本範圍的最佳實踐

  • 對於內部套件,優先使用 ^ 前綴以允許不破壞相容性的更新
  • 對於核心基礎設施套件,考慮使用 ~ 前綴來限制在修訂更新範圍內
  • 在版本穩定前,可考慮使用更嚴格的版本範圍(如確切版本)

二、Git Flow 在 Monorepo 中的調整與應用

2.1 傳統 Git Flow 回顧

Git Flow 是一種廣泛使用的 Git 工作流程,由 Vincent Driessen 提出。傳統的 Git Flow 包含以下主要分支:

  • master/main :永遠處於可發布狀態的分支
  • develop :開發中的主分支,包含最新的開發功能
  • feature :用於開發新功能的分支
  • release :準備發布的分支,用於最終測試
  • hotfix :用於修復生產環境問題的分支
    這種工作流程設計初衷是為單一應用或套件服務,但在 Monorepo 環境中需要一些調整。

2.2 Monorepo 環境下的 Git Flow 調整

在 Monorepo 中,傳統的 Git Flow 面臨的主要挑戰是:

  1. 如何處理不同套件/應用的並行開發
  2. 如何管理多個套件的發布週期
  3. 如何隔離不同團隊的工作

以下是針對 Monorepo 的 Git Flow 調整建議:

分支命名規範擴展

feature/[套件名稱]-[功能描述]
release/[套件名稱]-[版本號]
hotfix/[套件名稱]-[問題描述]

例如:
-feature/auth-service-oauth-integration
-release/ui-components-2.5.0
-hotfix/api-client-connection-timeout

引入範圍標籤(Scope Tags)

在提交訊息中使用範圍標籤來指明變更影響的套件:

feat(ui-components): 新增日期選擇器元件
fix(api-client): 修復連線逾時問題
chore(monorepo): 更新構建配置

這種做法有助於:

  1. 自動化工具追蹤特定套件的變更
  2. 生成更有條理的變更日誌
  3. 更容易理解提交的影響範圍

調整發布流程

在 Monorepo 中,發布流程需要區分「整體發布」和「單套件發布」:
整體發布流程

  1. develop 分支創建 release/vX.Y.Z 分支
  2. 在發布分支上進行最終測試和版本號更新
  3. 合併到 main 分支並打上標籤
  4. 同時合併回 develop 分支

單套件發布流程

  1. develop 分支創建 release/[套件名稱]-vX.Y.Z 分支
  2. 僅更新相關套件的版本和依賴
  3. 完成測試後合併到 main 並打上標籤
  4. 同時合併回 develop 分支

2.3 分支策略比較與選擇

除了調整後的 Git Flow,還有其他分支策略可以考慮:

GitHub Flow

簡化版的工作流程,只有 main 分支和特性分支。
優點

  • 流程簡單,容易理解和執行
  • 適合持續部署環境
  • 減少分支管理的複雜性

缺點

  • 缺少專門的發布分支,不易管理複雜的發布程序
  • 在多套件環境中可能缺乏足夠的結構

Trunk-Based Development

所有開發者直接在 trunk(通常是 main 分支)上工作,使用短期特性分支。
優點

  • 鼓勵頻繁整合,減少合併衝突
  • 與 CI/CD 實踐高度相容
  • 簡化版本控制

缺點

  • 需要強大的自動化測試支持
  • 可能不適合有多個並行版本的複雜產品
  • 對開發者紀律要求較高

2.4 實際案例:我們公司的分支策略

在我們的實踐中,我們採用了修改版的 Git Flow,關鍵調整包括:
1.套件前綴的分支命名:所有分支名包含開發的Jira票號

feature/myorg-123
feature/myorg-456

2.基於標籤的版本發布:使用 Git 標籤來標記發布版本,並遵循特定的命名規則

# 整體發布標籤
  v3.2.1

3.提交訊息約定:採用 Conventional Commits 規範,包含套件範圍

feat(ui): 新增響應式導航組件
fix(api): 修復 CORS 問題
chore(build): 更新 webpack 配置

這種策略與我們使用的 CI/CD 工具和發布自動化流程相協調,使團隊能夠靈活地管理版本和發布。

三、依賴管理與版本控制的整合

3.1 內部依賴管理的挑戰

在 Monorepo 中,套件之間的依賴關係是版本管理的核心挑戰。當一個套件更新時,需要考慮:

  1. 哪些套件直接依賴於它?
  2. 這些依賴套件是否需要更新其版本號?
  3. 變更的性質是否需要更新依賴版本範圍?

3.2 工具與策略

以下是幾種管理內部依賴的工具和策略:

Nx 的依賴管理

Nx 提供了強大的依賴分析工具,可以幫助理解和管理套件間的依賴關係:

# 分析特定套件的依賴
nx dep-graph --file=dep-graph.html --focus=shared-ui-components

# 檢測受特定變更影響的套件
nx affected:dep-graph --base=main --head=feature/new-component

這些工具可視化的依賴圖有助於理解變更的影響範圍,指導版本更新決策。

Changesets 策略

另一個流行的選擇是 Changesets,它提供了一種聲明式的變更管理方法:

# 創建一個變更集
npx changeset

# 準備發布
npx changeset version

# 發布變更
npx changeset publish

Changesets 的工作流程:

  1. 開發者在提交變更時創建一個變更集文件,描述變更並指明版本遞增類型
  2. 這些變更集文件被提交到版本控制系統
  3. 在發布前,變更集被用來更新版本並生成變更日誌
  4. 發布後,變更集文件被清理

3.3 實際案例:我們的版本更新流程

在我們的 Monorepo 中,由於採用鎖步版本策略,版本更新流程相對簡單:

  1. 決定版本升級類型:
    • 有不相容變更:major
    • 新增功能但向後相容:minor
    • 錯誤修復和小改進:patch
  2. 執行版本升級腳本,更新所有套件版本
  3. 提交變更並打上標籤

我們的版本更新流程不需要複雜的工具,僅使用簡單的自製腳本和 Nx 的依賴分析功能,就能保持所有套件版本一致,維持良好的開發體驗。

四、自動化發布流程的實現方案

4.1 發布流程自動化的重要性

在 Monorepo 環境中,發布流程的複雜性顯著增加。自動化發布有以下優勢:

  1. 減少人為錯誤
  2. 確保一致的發布流程
  3. 減輕手動協調多個套件發布的負擔
  4. 支持持續交付/持續部署的實踐

4.2 發布流程的關鍵步驟

一個完整的自動化發布流程應包含以下步驟:
1.版本確定:根據變更性質確定版本號遞增
2.依賴更新:更新內部套件間的依賴關係
3.變更日誌生成:自動生成詳細的變更日誌
4.構建與測試:確保所有受影響的套件能成功構建和通過測試
5.版本提交:提交版本更新和變更日誌
6.打標籤:為發布版本打上 Git 標籤
7.發布套件:將套件發布到套件註冊表(如 NPM)
8.部署應用:如需要,部署更新後的應用

4.4 發布流程實例

在沒有自動化部署的情況下,我們的發布流程通常包含以下步驟:
1.準備發布

  • 確保所有要發布的變更已合併到主分支
  • 確認所有測試通過2.版本更新
  • 手動執行版本管理工作流程
  • 選擇適當的版本升級類型(major/minor/patch)3.部署前準備
  • 拉取最新的主分支和標籤
  • 在本機或測試環境執行建置4.手動部署
  • jenkins build最新版號5.發布後檢查
  • 確認應用程式正常運作
  • 執行關鍵功能的快速測試
  • 更新發布文件

雖然沒有自動化部署,但我們仍通過部分自動化的版本管理來確保版本號的一致性和可追溯性。

五、實戰案例:解決公司專案中的版本管理難題

5.1 背景與挑戰

在將公司專案轉換為 Monorepo 後,我們面臨了一系列版本管理挑戰:
1.複雜的依賴網絡:系統包含超過 30 個相互依賴的套件
2.不同發布節奏:核心套件和應用層套件有不同的發布需求
3.多團隊協作:不同團隊負責不同套件,需要協調發布
4.向後相容性要求:需要確保 API 和共享元件的變更不會破壞依賴方

5.2 我們的解決方案

經過多次嘗試和調整,我們實施了以下解決方案:

依賴結構重組

1.建立明確的依賴層次:將套件組織為核心、共用、功能和應用程式四個層級

core-libs/      # 核心套件(實用工具、通用型別等)
shared-libs/    # 共用套件(UI 元件、API 客戶端等)
feature-libs/   # 功能套件(業務邏輯、頁面等)
apps/           # 最終應用程式

2.單向依賴原則:嚴格執行依賴方向,禁止低層級套件依賴高層級套件

apps → feature-libs → shared-libs → core-libs

版本管理策略簡化

我們採用了簡單直接的鎖步版本策略:
1.整體專案統一版本號

  • 所有套件共用同一個版本號
  • 在根目錄的 package.json 中維護主要版本號
  • 發布時一次性更新所有套件的版本號2.專注功能開發而非版本管理
  • 不需要複雜的版本管理工具
  • 避免團隊成員花費過多時間在版本協調上
  • 簡化依賴關係管理

這種策略在我們的中小型團隊環境下運作良好,提供了很好的平衡點。

CI/CD 考量

雖然我們目前沒有實施自動化部署,但我們仍然使用 CI/CD 來確保程式碼品質:
1.Pull Request 檢查

  • 自動執行單元測試
  • 檢查程式碼風格
  • 使用 Nx 的 affected 指令檢查變更影響範圍

CI通過後會觸發CD流程:
1.CD流程

  • 根據分支觸發不同的jenkins job
  • jenkins job執行Build/Deploy流程
  • Build完的結果寫回PR的comment

我們的手動部署流程文件如下:

# 部署檢查清單

## 發布前準備
- [ ] 確認所有測試通過
- [ ] 確認版本號已更新(使用版本管理工作流程)
- [ ] 準備發布說明
- [ ] 通知相關團隊即將發布

## 發布後檢查
- [ ] 確認主要功能正常運作
- [ ] 確認監控系統正常
- [ ] 向團隊通知發布完成
- [ ] 更新發布文件

5.3 效果與收穫

即使沒有自動化部署,我們的版本管理策略和半自動化流程仍帶來了顯著改進:
1.版本管理更一致:統一的版本號讓專案各部分保持一致
2.部署流程更標準化:明確的部署檢查清單減少人為錯誤
3.更好的追蹤能力:每個發布都有對應的標籤,易於追蹤變更
4.團隊協作更順暢:清晰的版本管理流程改善了團隊溝通
關鍵收穫包括:

  1. 版本管理和部署流程可以分開優化 - 即使沒有自動化部署,良好的版本管理仍然非常有價值
  2. 文件化的手動流程比糟糕的自動化流程更可靠
  3. 從簡單的版本管理自動化開始,可以為未來更全面的 CI/CD 打下基礎

關鍵收穫包括:

  1. 版本管理策略需要隨著團隊和產品的成長而演進
  2. 自動化是處理 Monorepo 版本管理複雜性的關鍵
  3. 清晰的依賴結構是有效版本管理的基礎
  4. 工具和流程需要根據團隊實際情況定制

六、未來趨勢與最佳實踐

6.1 版本管理的未來趨勢

隨著 Monorepo 工具和實踐的發展,我們看到了幾個新興趨勢:
1.更智慧的依賴分析:工具能夠更精確地分析變更影響範圍,減少不必要的更新
2.IDE 整合:版本管理功能直接整合到開發環境中,提供即時反饋
3.更靈活的版本策略:介於嚴格鎖步和完全獨立之間的更多彈性選項
4.自動化程度提高:從提交到部署的全流程自動化,減少人為干預

6.2 Monorepo 版本管理最佳實踐總結

基於我們的經驗,以下是針對中小型團隊的最佳實踐建議:
1.優先選擇簡單性

  • 對於中小型團隊,鎖步版本通常是最簡單有效的策略
  • 只有在專案規模和團隊成長到一定程度時才考慮更複雜的版本管理

2.自動化關鍵環節

  • 版本號更新
  • 標籤管理
  • 變更日誌生成
  • 部署流程

3.清晰的依賴結構

  • 遵循單向依賴原則
  • 避免循環依賴(請參考本系列第五篇)
  • 將套件組織為邏輯層級

4.簡化發布流程

  • 建立明確的發布步驟和檢查清單
  • 在文件中清楚說明發布流程
  • 考慮使用發布分支隔離發布相關變更

5.一致的版本語意

  • 嚴格遵循語意化版本規範
  • 在團隊中建立一致的版本遞增準則
  • 使用一致的提交訊息規範(如 Conventional Commits)

結語:適合你的團隊才是最好的版本管理

Monorepo 的版本管理沒有放諸四海皆準的最佳解法,關鍵在於找到適合自己團隊規模和專案特性的方案。對於大多數中小型團隊來說,簡單的鎖步版本策略往往是最實用的選擇,它能夠平衡管理複雜度和開發效率。
我們的經驗表明,過度複雜的版本管理策略可能會帶來比解決的問題更多的麻煩。理想的版本管理應該是「幾乎看不見的」— 它應該在背後默默運作,讓團隊成員能夠專注於創造價值,而不是疲於處理版本依賴問題。
隨著團隊和專案的成長,你可以根據需要逐步引入更複雜的版本管理策略和工具。重要的是保持靈活性,定期評估現有流程是否仍然適合團隊需求。
在下一篇文章中,我們將討論如何優化 Monorepo 的測試流程,敬請期待!

相關資源

  1. Nx 官方文件:版本管理
  2. 語意化版本規範
  3. Conventional Commits 規範
  4. Git Flow 工作流程
  5. GitHub Actions 使用手冊

你在 Monorepo 中是如何處理版本管理的?你使用鎖步版本還是其他策略?歡迎在評論區分享你的經驗!