Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn vừa gia nhập một team 8 người tại một startup fintech ở TP.HCM. Ngày đầu tiên, bạn git clone repo về, và câu hỏi đầu tiên bật ra trong đầu: "Mình sẽ code lên nhánh nào? Đẩy code đi đâu? Khi nào thì được merge vào production?". Đây không phải câu hỏi vặt vãnh — nó chính là chiến lược phân nhánh (branching strategy) của team, và nó quyết định phần lớn tốc độ giao hàng lẫn mức độ "cháy" mỗi lần release.
Trong một pipeline CI/CD, branching strategy là luật chơi nền tảng. Mọi thứ phía sau — build tự động chạy khi nào, môi trường nào được deploy, ai được review code của ai — đều phụ thuộc vào cách team tổ chức các nhánh Git. Chọn sai chiến lược, bạn sẽ rơi vào cảnh "merge hell": mỗi lần gộp code mất nửa ngày giải conflict, hoặc tệ hơn, một nhánh develop treo lơ lửng ba tuần không dám release vì sợ vỡ.
Ngược lại, chọn đúng và làm kỷ luật, team có thể deploy nhiều lần một ngày mà tay không run. Bài này sẽ giúp bạn hiểu bản chất các mô hình phân nhánh phổ biến — đặc biệt là Git Flow — biết khi nào dùng cái nào, và tránh những cái bẫy mà rất nhiều team Việt Nam mắc phải khi mới áp dụng CI/CD.
Khái niệm cốt lõi
Trước khi đi vào từng mô hình, cần thống nhất: một branching strategy không phải là một công cụ, mà là một quy ước (convention) về việc các nhánh Git sinh ra, sống, và biến mất như thế nào. Nó trả lời bốn câu hỏi:
- Nhánh nào là "nguồn sự thật" của production?
- Feature mới được phát triển ở đâu?
- Code đi từ tay developer đến production qua những chặng nào?
- Bug khẩn cấp trên production được xử lý ra sao?
Nhánh dài hạn (long-lived) và nhánh ngắn hạn (short-lived)
Đây là phân biệt quan trọng nhất. Nhánh dài hạn tồn tại suốt vòng đời dự án — ví dụ main (hay master), develop. Nhánh ngắn hạn sinh ra để làm một việc cụ thể rồi bị xóa — ví dụ feature/login-otp, bugfix/cart-total, hotfix/payment-timeout.
Nguyên tắc vàng: nhánh ngắn hạn càng sống ngắn càng tốt. Một feature branch tồn tại 2 ngày dễ merge hơn gấp bội một feature branch sống 3 tuần, vì main càng thay đổi nhiều thì con nhánh của bạn càng lệch xa (drift), và conflict càng lớn.
Git Flow — mô hình kinh điển
Git Flow do Vincent Driessen đề xuất năm 2010, và trong nhiều năm là "chuẩn mực" của ngành. Nó có tới 5 loại nhánh:
main ─────●──────────────────●────────────●────── (production, gắn tag v1.0, v1.1...)
\ / \ /
develop ──●───●───●───────●───●───●────●───●─────── (tích hợp, nơi feature gộp về)
\ \ / \ /
feature A feature-B C (feature/*)
release release/1.1 ──●──● (release/*, chỉ sửa lỗi cuối)
hotfix hotfix/1.0.1 (hotfix/*, vá thẳng production)
Cơ chế hoạt động:
main: luôn phản ánh code đang chạy production. Mỗi commit trênmainlà một bản release, thường được gắn tag phiên bản (v1.2.0).develop: nhánh tích hợp, nơi tất cả feature hoàn thành được gộp về. Đây là "bản nháp của phiên bản tiếp theo".feature/*: tách ra từdevelop, làm xong thì merge ngược vềdevelop.release/*: khidevelopđủ tính năng cho một phiên bản, tách rarelease/1.1để ổn định (sửa bug nhỏ, cập nhật số phiên bản), rồi merge vào cảmainlẫndevelop.hotfix/*: khi production cháy, tách thẳng từmain, vá xong merge vàomainvàdevelop.
develop và main lệch nhau.GitHub Flow — mô hình tối giản
Đối lập với Git Flow là GitHub Flow: chỉ có một nhánh dài hạn là main. Mọi thay đổi đều đi qua feature branch ngắn hạn, mở Pull Request, được review và chạy CI, rồi merge thẳng vào main. main luôn ở trạng thái deploy được, và thường được deploy tự động ngay sau merge.
main ──●────────●──────────●────────●────── (luôn deploy được)
\ / \ /
feature login checkout-fix
Đơn giản, nhanh, hợp với web/SaaS deploy liên tục. Nhược điểm: không có "vùng đệm" như develop, nên team cần CI mạnh và test tự động tốt để main không vỡ.
Trunk-Based Development — cực đoan của sự tối giản
Xa hơn nữa là Trunk-Based Development (TBD): mọi người commit trực tiếp (hoặc qua feature branch cực ngắn, sống vài giờ) vào một nhánh chung gọi là "trunk" (main). Code chưa hoàn thiện được ẩn sau feature flag thay vì giữ trong nhánh riêng. Đây là mô hình mà Google, Facebook dùng ở quy mô lớn, và là nền tảng của continuous delivery thực thụ.
> Lưu ý: bài này chỉ giới thiệu bức tranh tổng thể để bạn định vị. Chi tiết so sánh Trunk-based vs GitFlow, cùng cách chọn cho từng tình huống, sẽ được đào sâu ở một bài chuyên biệt sau trong khóa. Ở đây, hãy tập trung nắm vững cơ chế và triết lý của từng mô hình.
Tình huống thực tế
Ví dụ 1 — Startup fintech Việt "ôm" Git Flow rồi mắc kẹt
Một công ty ví điện tử tại Hà Nội (gọi là VíNhanh, ~15 kỹ sư) áp dụng Git Flow vì nghe nói "chuẩn ngân hàng phải chặt chẽ". Họ có main, develop, feature branch, release branch đầy đủ.
Vấn đề nảy sinh: sản phẩm là app web + mobile, họ muốn ship tính năng gần như hàng tuần. Nhưng quy trình Git Flow buộc mỗi release phải: gom feature vào develop → tạo release/x.y → QA test trên nhánh release → merge vào main → lại merge ngược về develop. Mỗi chu kỳ mất 4–5 ngày chỉ riêng phần "múa nhánh". Tệ hơn, có tuần một feature lớn (KYC bằng eKYC) sống trên feature branch 19 ngày. Khi merge về develop, conflict trải khắp 40 file, mất 1.5 ngày giải, và làm vỡ 2 feature khác đã merge trước đó.
Diễn giải: Git Flow không sai, nhưng nó bị áp dụng lệch bối cảnh. VíNhanh là sản phẩm web deploy liên tục, không phải phần mềm đóng gói phát hành theo quý. Sự "chặt chẽ" mà họ tưởng có thực chất chỉ là nhiều bước thủ công hơn, không đồng nghĩa với an toàn hơn.
Bài học: Cấu trúc nhiều nhánh không tạo ra chất lượng. Chất lượng đến từ test tự động và review. Với nhịp ship hàng tuần, họ nên chuyển sang GitHub Flow (bỏ develop, deploy từ main) — sau này VíNhanh làm đúng vậy và giảm thời gian release từ 5 ngày xuống nửa ngày.
Ví dụ 2 — Team làm mobile app cho hãng bán lẻ, Git Flow lại là lựa chọn đúng
Một agency ở Đà Nẵng nhận làm app iOS/Android cho một chuỗi siêu thị. App phải qua App Store review (2–5 ngày duyệt), nên không thể "deploy liên tục" như web. Mỗi phiên bản là một artifact được nộp lên store và tồn tại độc lập.
Ở đây Git Flow tỏa sáng: khi develop gom đủ tính năng cho version 3.2, team tách release/3.2, đội QA test kỹ trên nhánh này trong lúc các dev khác đã bắt đầu code tính năng của 3.3 trên develop — hai luồng không dẫm chân nhau. Khi 3.2 được store duyệt và phát hành, họ merge release/3.2 vào main, gắn tag v3.2.0. Ba tuần sau, một khách hàng báo lỗi crash khi thanh toán trên iOS 17; team tách hotfix/3.2.1 thẳng từ main, vá, phát hành bản vá nhanh, rồi merge ngược cả hai nhánh.
Diễn giải: Vì chu kỳ phát hành bị ràng buộc bởi bên ngoài (store review) và các phiên bản tồn tại song song (người dùng đang xài 3.1, 3.2 cùng lúc), việc có nhánh release/ và hotfix/ riêng biệt là hợp lý.
Bài học: Chọn branching strategy phải theo cách sản phẩm được phát hành, không theo trào lưu. Mobile/desktop/phần mềm đóng gói → Git Flow hoặc biến thể của nó thường phù hợp.
Ví dụ 3 — Team Đông Nam Á chuyển sang trunk-based để deploy 20 lần/ngày
Một công ty giao đồ ăn hoạt động ở Việt Nam, Thái Lan, Indonesia có ~120 kỹ sư trên một monorepo. Ban đầu họ dùng GitHub Flow, nhưng khi số dev tăng, các Pull Request xếp hàng chờ nhau, feature branch sống 3–4 ngày và liên tục conflict.
Họ chuyển sang trunk-based: feature branch chỉ được sống tối đa 1 ngày, code chưa xong ẩn sau feature flag (ví dụ if (flags.newCheckout) {...}). CI chạy trên mỗi PR trong dưới 10 phút, merge vào main là deploy ngay. Kết quả: từ ~4 lần deploy/tuần lên hơn 20 lần deploy/ngày, và tỉ lệ conflict giảm mạnh vì không nhánh nào kịp lệch xa.
Diễn giải: Ở quy mô lớn với tần suất merge cao, nhánh dài hạn trở thành nút cổ chai. Trunk-based đẩy sự tách biệt "code chưa xong / đã xong" từ cấp nhánh xuống cấp runtime (feature flag).
Bài học: Khi team lớn và muốn tốc độ, hãy rút ngắn tuổi thọ nhánh xuống mức tối thiểu. Nhưng điều đó đòi hỏi kỷ luật test và feature flag — không phải team nào cũng sẵn sàng ngay.
Hướng dẫn từng bước
Dưới đây là quy trình thiết lập Git Flow cơ bản để bạn thực hành. Sau đó tôi sẽ chỉ cách rút gọn khi cần.
Bước 1 — Khởi tạo hai nhánh dài hạn.
git checkout -b main # nhánh production
git checkout -b develop # nhánh tích hợp, tách từ main
git push -u origin main develop
Bước 2 — Bắt đầu một feature. Luôn tách từ develop, đặt tên rõ ràng theo quy ước feature/<mô-tả-ngắn>:
git checkout develop
git pull
git checkout -b feature/otp-login
Bước 3 — Làm việc và commit thường xuyên. Commit nhỏ, message rõ ràng. Nếu feature sống hơn 1–2 ngày, hãy rebase/merge develop vào nhánh của bạn định kỳ để tránh lệch xa:
git checkout feature/otp-login
git merge develop # hoặc: git rebase develop
Bước 4 — Hoàn thành feature qua Pull Request. Đừng merge thẳng bằng dòng lệnh trên team thật. Hãy push và mở PR để CI chạy (lint, test, build) và đồng nghiệp review:
git push -u origin feature/otp-login
# Mở Pull Request: feature/otp-login -> develop
Bước 5 — Chuẩn bị release. Khi develop đủ tính năng cho phiên bản mới:
git checkout develop
git checkout -b release/1.3.0
# chỉ sửa bug nhỏ, bump version, cập nhật changelog trên nhánh này
Bước 6 — Phát hành. Merge release vào main, gắn tag, rồi merge ngược về develop:
git checkout main
git merge --no-ff release/1.3.0
git tag -a v1.3.0 -m "Release 1.3.0"
git push origin main --tags
git checkout develop
git merge --no-ff release/1.3.0
git branch -d release/1.3.0
Bước 7 — Xử lý hotfix. Khi production cháy, tách thẳng từ main:
git checkout main
git checkout -b hotfix/1.3.1
# vá lỗi
git checkout main && git merge --no-ff hotfix/1.3.1 && git tag v1.3.1
git checkout develop && git merge --no-ff hotfix/1.3.1
Cách rút gọn về GitHub Flow: bỏ hoàn toàn develop, release/, hotfix/. Chỉ giữ main + feature branch. Tách feature từ main, mở PR về main, merge xong deploy. Đơn giản hơn nhiều — và đủ cho phần lớn dự án web.
Lỗi thường gặp & mẹo
Feature branch sống quá lâu. Đây là lỗi số một. Nhánh sống càng lâu, conflict càng khủng và review càng khó. Mẹo: chia feature lớn thành nhiều PR nhỏ, hoặc merge phần chưa hoàn thiện vào main nhưng ẩn sau feature flag. Đặt mục tiêu mỗi PR sống dưới 2–3 ngày.
Commit thẳng lên main/develop. Bỏ qua PR nghĩa là bỏ qua review và đôi khi cả CI. Mẹo: bật branch protection rule trên GitHub/GitLab — bắt buộc PR, bắt buộc CI xanh, bắt buộc ít nhất 1 approver mới được merge vào nhánh được bảo vệ.
Đặt tên nhánh lộn xộn. test, fix2, khang-branch... khiến không ai biết nhánh làm gì. Mẹo: thống nhất tiền tố — feature/, bugfix/, hotfix/, release/ — và kèm mã ticket nếu có: feature/JIRA-123-otp-login.
Dùng Git Flow cho web deploy liên tục. Như ví dụ VíNhanh: quá nặng nề. Mẹo: nếu bạn deploy nhiều lần một tuần và không có ràng buộc phát hành bên ngoài, dùng GitHub Flow hoặc trunk-based.
Quên merge hotfix ngược về develop. Trong Git Flow, nếu vá main mà quên gộp về develop, bản release sau sẽ tái xuất hiện đúng cái bug đã vá. Mẹo: luôn coi "merge vào cả hai nhánh" là một bước bắt buộc, không phải tùy chọn.
Merge conflict dồn cục. Do nhánh lệch xa. Mẹo: đồng bộ với nhánh gốc thường xuyên (git merge develop mỗi ngày), và giữ PR nhỏ.
Một mẹo tổng quát: hãy để branching strategy của bạn khớp với CI/CD. Ví dụ, cấu hình pipeline chạy full test trên mọi PR về develop/main, và tự động deploy main lên staging. Chiến lược nhánh và pipeline phải là một thể thống nhất, không phải hai thứ rời rạc.
Bài tập thực hành
- Dựng Git Flow thu nhỏ. Tạo một repo mới, khởi tạo
mainvàdevelop. Tạo hai feature branch (feature/homepage,feature/footer), mỗi nhánh commit một file, rồi merge cả hai vềdevelopqua PR (dùng GitHub cá nhân). Cuối cùng tạorelease/0.1.0, merge vàomain, gắn tagv0.1.0.
- Tự gây và giải conflict. Trên hai feature branch khác nhau, cùng sửa một dòng trong cùng một file. Merge nhánh thứ nhất về
develop, rồi thử merge nhánh thứ hai. Quan sát conflict và giải nó bằng tay. Ghi lại: điều gì khiến conflict xảy ra, và làm sao để tránh?
- So sánh mô hình. Viết một đoạn ngắn (nửa trang) phân tích: nếu bạn đang xây một SaaS quản lý bán hàng cho các shop online ở Việt Nam, deploy 2–3 lần/tuần, team 6 người — bạn chọn Git Flow, GitHub Flow hay trunk-based? Nêu 3 lý do cụ thể.
- Bảo vệ nhánh. Trên một repo GitHub, bật branch protection cho
main: yêu cầu PR, yêu cầu ít nhất 1 review, yêu cầu status check pass. Thử push thẳng lênmainvà quan sát nó bị chặn.
Tóm tắt
Branching strategy là luật chơi nền tảng của mọi pipeline CI/CD — nó quyết định code đi từ máy developer đến production như thế nào. Ba mô hình chính:
- Git Flow: 5 loại nhánh (
main,develop,feature/,release/,hotfix/*), có cấu trúc, hợp với sản phẩm phát hành theo phiên bản (mobile, desktop, phần mềm đóng gói). Nhược điểm: nặng nề với web deploy liên tục. - GitHub Flow: chỉ
main+ feature branch, đơn giản, hợp với web/SaaS deploy thường xuyên. Đòi hỏi CI và test tốt. - Trunk-Based: nhánh sống cực ngắn, dùng feature flag, cho phép deploy nhiều lần/ngày ở quy mô lớn.