Monorepo 下 Git 工作流的最佳實踐

背景

沒有哪一種 Git 工作流是銀彈,合適的 Git 工作流往往取決於項目的代碼規模、協作人數、應用場景等;本次分享先從適合小型 Monorepo 的 Feature branch 工作流開始分享,接着分享適用於中大型 Monorepo 的 Trunk-based 工作流,並給出一些選型標準供同學們參考,**希望通過本次分享,大家能找到合適自己 **Monorepo 工程的 Git 工作流!

前置知識

我們最熟悉的 Git 工作流莫過於 Git flow, Gilab flow, Github flow,而對於 feature branch 和 trunk-based 比較陌生,那麼以上幾種 flow 有什麼關係呢?

  1. Feature branch 和 Trunk-based 工作流是比較新晉的概念,二者是相對的、互斥的,它們組成一個全集;

  2. Git flow, Gilab flow, Github flow 都屬於 feature branch development,它們有一個共同點:都採用『功能驅動式開發』,即:需求是開發的起點,先有需求再有功能分支(feature branch)或者補丁分支(hotfix branch);

適用場景

在 Monorepo 工程中,使用 feature branch development 開發模式時,隨着代碼庫複雜性和團隊規模的增長,並行的『長期分支』也會越來越多,這些分支在合入主幹時,將會頻繁遇到衝突或者不兼容的情況,而手動解決代碼衝突往往會引入 Bug。

而 trunk-based development 鼓勵開發者可以通過一些小的提交創建『短期分支』,從而大大緩解衝突問題,有助於保持生產版本的流暢。

總的來說,選擇一個工作流不僅僅是一系列操作工具的流程,我們往往還需要對它背後的思想買單;下面的表格是兩種工作流模式在各個維度的適用情況:

目前大部分業務場景使用的都是 feature branch 的開發模式,如果你的業務是多人開發一個巨型應用(如抖音主站、飛書文檔等),應該嘗試使用 Trunk based 開發模式,這會提高倉庫整體工程質量和管理水平。

展開說說

Feature branch development

什麼是 feature branch development?

『功能分支開發模式』的核心思想是所有特性開發都應該在專用的分支,而不是主分支中進行。這種封裝使多個開發人員,可以輕鬆地在不干擾主代碼庫的情況下處理特定功能。這也意味着主分支永遠不會包含損壞的代碼,這對於持續集成環境來說是一個巨大的優勢。-- Git Feature Branch Workflow | Atlassian Git Tutorial

  1. 從 master 分支創建一個功能分支(Feature Branch)

  2. 開發者們在功能分支中完成開發工作

  3. 構建功能分支,並通知 QA 進行驗證

  4. 如果發現任何問題

  5. 開發者創建一個基於功能分支的修復 MR

  6. 經過代碼審閱和合並過程將修復 MR 合入功能分支

  7. 再重新構建部署,並通知 QA 進行驗證

  8. QA 驗證通過後,將功能分支發佈至線上,然後將其合併入主幹後刪除

爲什麼使用 feature branch development?

使多個開發人員可以輕鬆地在不干擾主代碼庫的情況下處理特定功能。

主分支永遠不會包含損壞的代碼,這對於持續集成環境來說是一個巨大的優勢。

僅需瞭解簡單的操作即可實踐,無需瞭解 cherry-pick, feature flag 等概念。

Trunk-based development

什麼是 trunk-based development?

『基於主幹的開發模式』是一種版本控制管理實踐,開發者將小而頻繁的更新合併到核心『主幹』(通常是 master 分支)。

這是 DevOps 團隊中的一種常見做法,也是 DevOps 生命週期的一部分,因爲它簡化了合併和集成階段。事實上,它也是 CI/CD 的必備實踐。

與其它存在『長期分支』的功能分支策略相比,開發者可以通過一些小的提交創建『短期分支』。隨着代碼庫複雜性和團隊規模的增長,『基於主幹的開發模式』有助於保持生產版本的流暢。-- Trunk-based Development | Atlassian

從部署分支上線

半自動化流程,適用於低頻率部署,以及自動化測試不全面的項目

(A dot represents an MR merged into master. Green dots means good commits that passed e2e tests, and red dot means a buggy commit which should be avoided when deploying/rollback)

  1. 從 master 分支創建一個部署分支(RC)

  2. 構建部署分支(RC),並通知 QA 進行驗證

  3. 如果發現任何問題

  4. 開發者創建一個基於 master 分支的修復 MR

  5. 經過代碼審閱和合並過程將修復 MR 合入 master

  6. 將 commits cherrypick 到部署分支(RC),再重新構建部署,並通知 QA 進行驗證

  7. QA 驗證通過後,將部署分支(RC)發佈至線上,然後刪除該分支(RC)

從主幹分支上線

全自動化流程,適用於需要高頻率部署,以及自動化測試較爲全面的項目

(A dot represents an MR merged into master. Green dots means good commits that passed e2e tests, and red dot means a buggy commit which should be avoided when deploying/rollback)

  1. 定時部署: 每天或者每小時到了特定時間,部署機器人自動找到當前最新通過全部端到端測試的代碼 (特定的 commit hash),然後將之部署上線。

  2. 持續部署: 每當有新代碼合併進主幹分支時,部署機器人自動驗證新代碼是否通過所有端到端測試,以及是否與該項目相關,如果是則自動部署上線

爲什麼使用 trunk-based development?

在『基於主幹的開發模式』中,源源不斷的提交合入主幹分支。爲每個提交添加自動化測試套件和代碼覆蓋率監控可以實現持續集成。當新代碼合併到主幹中時,會運行自動集成和代碼覆蓋測試以驗證代碼質量。

『基於主幹的開發模式』的快速、小型提交使代碼審查成爲一個更有效的過程。藉助小型分支,開發人員可以快速查看和審查小的更改。與評審者閱讀大面積代碼變更的長期功能分支相比,這要容易得多。

團隊應該每天頻繁地合併到主分支。『基於主幹的開發模式』努力使主幹分支保持 “綠色”,這意味着它可以在每次提交合並後進行部署。自動化測試、代碼收斂和代碼審查,保證了基於主幹的項目可以隨時部署到生產環境中。

大型 Monorepo 下的多人協作場景更易出現代碼衝突,不僅消耗的大量的人力解決衝突,還增加了『長期分支』合入『主幹分支』引入 bug 的可能性。與其它存在『長期分支』的功能分支策略相比,開發者可以通過一些小提交創建『短期分支』進行快速迭代。因此,隨着代碼庫複雜性和團隊規模的增長,『基於主幹的開發模式』也能保證順暢的多人協作。

Trunk-based development 更容易做到線性的 commit 歷史,它有如下幾個好處:

  1. 方便查看和跟蹤歷史記錄

  2. 方便回溯變更,比如:Feature A 是在 Bugfix B 之前或者之後引入的?

  3. 方便排查 bug,比如:使用 Git bisect 二分排查,而非線性歷史則難以操作

  4. 撤銷變更,比如:當你發現一個有問題的 commit,簡單的 revert 對應的 commit 即可,而非線性的歷史會有很多跨分支的合併,使 revert 變得困難

有效的兩個前提

在每次代碼合併前後,開發者都需要知道自己的代碼對主幹帶來了什麼影響,因此持續集成和測試的能力必不可少。

因爲在基於主幹開發時,大的功能被分解爲小改動,因此對於還未完成而之後部分合並進主幹的功能,我們需要功能開關來不讓他們過早地暴露給用戶。

功能開關通常是一套獨立的控制系統,線上的代碼有兩套邏輯,然後通過實時讀取功能開關的取值來決定是否隱藏或暴露某個功能。通常,我們在部署完一個功能相關的所有代碼之後打開某個功能開關。然後當此功能已經穩定並且被永久加入產品後,會把功能開關和相關的邏輯代碼刪除掉。

參考

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tNs5CVdmj4DepYmELWjdzA