Chuỗi: docs-as-code
Năng suất & công cụ dev
Chiến lược Documentation-as-Code cho Super-App — capstone đầu-cuối
Gói cả khóa thành một chiến lược chạy được: cấu trúc repo super-app, contract-first là trái tim, git strategy xuyên đội, CI/CD nhiều tầng, traceability, governance, và kế hoạch rollout 90 ngày.
2026-06-2420 phút đọcVI
- 1.Git & Pull Request cho tài liệu — để docs đi cùng nhịp với code
- 2.Tự động hoá Docs-as-Code — để CI bắt lỗi tài liệu trước con người
- 3.Viết tài liệu bằng Markdown — từ file đẹp mắt đến tài liệu máy đọc được
- 4.Chiến lược Documentation-as-Code cho Super-App — capstone đầu-cuối
- 5.Docs-as-Code cheat sheet — một trang tóm tắt toàn bộ
- 6.Tại sao Documentation-as-Code — bài học từ một lỗi không ai thấy suốt cuối tuần
Bài này gói cả khóa thành một chiến lược chạy được: lấy năm mảnh rời của Lesson 1–5 và ghép chúng thành một hệ thống tài liệu hoàn chỉnh cho cả một super-app (siêu ứng dụng chứa nhiều mini-app bên trong) — có cấu trúc repo, contract (hợp đồng API giữa các đội), git strategy, CI/CD, traceability, governance, một case study đầu-cuối, và kế hoạch rollout 90 ngày.

Mục tiêu học tập
Sau bài học này, bạn sẽ:
- Ghép được năm thói quen của Lesson 1–5 thành một chiến lược thống nhất cho cả super-app, thay vì năm kỹ thuật rời.
- Thiết kế được cấu trúc repo super-app đầy đủ:
shell/,bff/,mini-apps/,contracts/,docs/product-docs/— và quyết định được monorepo vs multi-repo bằng bảng trade-off. - Giải thích được vì sao contract-first (định nghĩa hợp đồng trước, code và tài liệu bám theo) là trái tim của docs cho super-app, và cách contract chặn "sự cố Minh" của Lesson 1.
- Áp dụng được git strategy xuyên đội: PR xuyên đội (cross-team),
CODEOWNERStheo mini-app, quy tắc "docs + code trong cùng PR", và quy trình xử lý thay đổi contract. - Mở rộng được CI/CD từ
validate-skills.sh+validate.ymlthật của repo lên một pipeline quy mô super-app. - Lập được ma trận traceability xuyên nhiều mini-app, một mô hình governance federated (mỗi đội tự quản tài liệu của mình theo chuẩn chung), và một kế hoạch rollout 90 ngày cho đội 20–40 người.
1. Mở đầu: từ 5 mảnh rời → một chiến lược
Bạn đã đi hết Track A. Hãy dừng lại một nhịp và nhìn lại những gì đã có trong tay. Không phải để ôn bài, mà để thấy một điều quan trọng: năm bài đó không phải năm kỹ thuật rời rạc. Chúng là năm mảnh của một hệ thống.
| Lesson | Mảnh ghép | Câu một dòng |
|---|---|---|
| 1 | Lý do | Tài liệu tách rời code sẽ bị documentation rot (cũ dần, lệch khỏi code). Coi tài liệu như code thì hết. |
| 2 | Cú pháp | Viết bằng Markdown + frontmatter (khối metadata YAML đầu file) → text thuần, so sánh được từng dòng, máy đọc được. |
| 3 | Kiến trúc | Tổ chức tài liệu thành chuỗi BRD → PRD → FR/NFR → Epic → Story → Test + ma trận traceability (truy vết liên kết). |
| 4 | Quy trình | Quản lý thay đổi qua branch → PR → review → merge; CODEOWNERS + branch protection; docs + code trong cùng PR. |
| 5 | Tự động + quy mô | CI bắt lỗi máy-bắt-được; docs site khi cần; rollout theo giai đoạn cho đội lớn. |
Mỗi mảnh tự nó đã hữu ích. Nhưng câu hỏi của bài Capstone này khác hẳn:
Khi bạn không quản một tài liệu, một dự án, mà cả một super-app với bốn mini-app, bốn đội, vòng đời lệch nhau — làm sao ghép năm mảnh này thành một chiến lược chạy được cho tất cả?
Đây không còn là chuyện "viết tài liệu cho đẹp". Như Lesson 1 đã chỉ ra, trong super-app tài liệu là hợp đồng giao tiếp giữa các đội — một câu sai phá vỡ niềm tin và gây lỗi dây chuyền. Bài này dạy bạn dựng cái hệ thống khiến lỗi đó gần như không thể xảy ra.
Chúng ta tiếp tục dùng super-app hư cấu NovaApp, gồm bốn mini-app generic: Payments (thanh toán/QR), Booking (đặt chỗ), Wallet (ví), Rewards (điểm thưởng). Và xuyên suốt bài, ta sẽ quay lại câu chuyện Minh của Lesson 1. Minh — lập trình viên đội Booking — gọi API hoàn tiền thiếu trường bắt buộc reason vì tài liệu lỗi thời. Với chiến lược đầy đủ trong bài này, sự cố đó bị chặn ngay tại review và CI, không bao giờ ra tới production.
2. Cấu trúc repo super-app đầy đủ
Mọi chiến lược tài liệu bắt đầu từ một câu hỏi vật lý: các file đặt ở đâu? Với một dự án đơn lẻ (Lesson 3), câu trả lời gọn: copy templates/product-docs/ vào docs/. Với super-app, bạn cần một cây thư mục lớn hơn, nơi code, contract và tài liệu của nhiều đội sống cạnh nhau một cách có kỷ luật.
2.1 Cây thư mục monorepo NovaApp
monorepo (một kho mã duy nhất chứa toàn bộ super-app) của NovaApp trông như sau. Đây là khung tham chiếu — bạn sẽ điều chỉnh theo đội mình, nhưng giữ nguyên các tầng:
novaapp/ # monorepo gốc
├── shell/ # lớp vỏ dùng chung — host các mini-app
│ ├── src/ # code của shell (điều hướng, vòng đời mini-app)
│ └── docs/ # tài liệu shell (product-docs riêng của đội shell)
│ └── product-docs/ # 00-discovery → 04-delivery cho shell
│
├── bff/ # cổng API back-end dùng chung (đội super-app sở hữu)
│ ├── src/ # code BFF (POST /refunds và các API dùng-chung)
│ └── docs/ # tài liệu BFF
│ └── product-docs/ # FR/NFR/test cho các API BFF
│
├── mini-apps/
│ ├── payments/
│ │ ├── src/ # code mini-app Payments (thanh toán/QR)
│ │ └── docs/product-docs/ # tài liệu Payments (chuỗi BRD→Test riêng)
│ ├── booking/
│ │ ├── src/
│ │ └── docs/product-docs/
│ ├── wallet/
│ │ ├── src/
│ │ └── docs/product-docs/
│ └── rewards/
│ ├── src/
│ └── docs/product-docs/
│
├── contracts/ # ★ hợp đồng API giữa các đội (shell↔mini-app, mini-app↔BFF)
│ ├── README.md # quy ước contract + vòng đời
│ ├── shell-host/ # API shell cung cấp cho mini-app
│ │ └── navigation-v1.md
│ └── bff/ # API BFF cung cấp cho mini-app (hoàn tiền + API dùng chung)
│ ├── refund-v1.md
│ └── refund-v2.md # phiên bản mới (thêm trường reason)
│
├── docs/ # tài liệu cấp super-app (xuyên mini-app)
│ ├── product-docs/ # kiến trúc tài liệu nền tảng dùng chung
│ │ └── platform/ # auth, design system, quy ước chung
│ └── traceability/ # ma trận tổng hợp toàn ecosystem
│
├── .github/
│ └── workflows/ # CI: validate per-mini-app, contract, docs
├── CODEOWNERS # gán reviewer theo mini-app
└── CONTRIBUTING.md # checklist PR, chuẩn "review docs như code"
Ba điều cần nắm về cây này:
- Mỗi mini-app là một "thế giới con" hoàn chỉnh: có
src/(code) vàdocs/product-docs/(chuỗi tài liệu BRD→Test của riêng nó, đúng kiến trúc Lesson 3). Đội Payments sở hữu trọnmini-apps/payments/. bff/là back-end dùng chung của đội super-app: nó chứa các API mà nhiều mini-app cùng gọi — nhưPOST /refunds. Đội super-app (đội nền tảng) sở hữubff/.contracts/là tầng đặc biệt, đứng riêng: nó không thuộc về một đội mini-app nào — nó là ranh giới giữa các đội. Đây là phần mới so với một dự án đơn lẻ, và là trọng tâm của phần 3.docs/ở gốc chỉ chứa cái xuyên mini-app: tài liệu nền tảng (platform/) và ma trận traceability tổng hợp. Cái gì riêng của một mini-app thì không để ở đây — nó sống trong mini-app đó.
💡 Quy tắc đặt file một dòng: Riêng của một đội mini-app → trong thư mục mini-app đó. API dùng chung nhiều mini-app →
bff/. Là ranh giới giữa các đội →contracts/. Dùng chung mọi đội →docs/platform/. Bốn câu này quyết định 95% chuyện "file này để đâu".
📂 Có ví dụ chạy được:
examples/nova-superapp/là bản NovaApp đã dựng thật để bạn nghiên cứu. Lưu ý: ví dụ đó cố ý dùng layout đơn giản hoá để golden thread dễ theo trên lần đọc đầu — product-docs đặt ởdocs/product-docs/cấp super-app (thay vì lồng trong từng mini-app) và contract để phẳng dạngcontracts/bff-refund.contract.md(thay vìcontracts/bff/refund-v1.md+refund-v2.md). Cây ở trên là layout đầy đủ-quy mô cho đội lớn nhiều mini-app. Cả hai đều hợp lệ — chọn layout phẳng khi mới bắt đầu, tách subfolder khi số mini-app và số phiên bản contract tăng lên.
2.2 Sơ đồ luồng: ai đọc tài liệu của ai
Cấu trúc trên không phải để trang trí — nó mã hóa các đường đọc giữa các đội. Sơ đồ dưới cho thấy đội Booking đọc contract của BFF thế nào (chính là đường mà Minh đã đi sai ở Lesson 1):
Điểm mấu chốt: Booking không đọc code của BFF — nó đọc contract của BFF. Code BFF có thể đổi tự do miễn vẫn khớp contract. Contract là ngôn ngữ chung. Đó cũng là chỗ duy nhất cần được canh giữ cực kỳ chặt — vì sai một chỗ thì cả đội khác làm sai theo.
2.3 Monorepo vs multi-repo: chọn cái nào?
Cây trên là một monorepo — tất cả trong một kho. Lựa chọn còn lại là multi-repo (nhiều kho tách rời): một repo cho shell, một repo cho mỗi mini-app, một repo riêng cho contracts, có khi một repo riêng cho docs nền tảng. Không có lựa chọn "đúng tuyệt đối" — có trade-off.
| Tiêu chí | Monorepo (một kho) | Multi-repo (nhiều kho) |
|---|---|---|
| Docs + code + contract trong cùng PR | ✅ Tự nhiên — mọi thứ cùng kho, một PR đụng cả ba | ⚠️ Khó — đổi contract ở repo A, đổi code ở repo B = nhiều PR, dễ lệch |
| Traceability xuyên mini-app | ✅ Một ma trận tổng hợp, đọc thẳng cây thư mục | ⚠️ Phải tổng hợp link xuyên repo, công cụ phức tạp hơn |
| Quyền sở hữu / cô lập đội | ⚠️ Cần CODEOWNERS chặt để đội không giẫm chân nhau | ✅ Mỗi đội một kho, ranh giới vật lý rõ |
| CI | ⚠️ Cần build có chọn lọc (chỉ chạy mini-app bị đổi) | ✅ Mỗi repo CI riêng, đơn giản |
| Onboard người mới | ✅ Clone một kho thấy cả ecosystem | ⚠️ Phải clone nhiều kho, hiểu cách chúng nối |
| Quy mô tổ chức hợp | Đội vừa, nhiều liên kết chéo, muốn nhất quán | Đội rất lớn, mini-app độc lập cao, cần tách bạch tuyệt đối |
Khuyến nghị cho hầu hết super-app tài liệu-trung tâm: bắt đầu monorepo. Lý do nằm ở chính trái tim của khóa học này. Quy tắc "docs + code trong cùng PR" (Lesson 4) là tuyến phòng thủ chính chống doc drift, và monorepo làm nó tự nhiên. Trong multi-repo, đổi contract ở một kho và đổi code ở kho khác là hai PR ở hai nơi — và "việc thứ hai bị quên" chính là nguyên nhân gốc của sự cố Minh.
Có một mẫu lai phổ biến đáng biết: monorepo cho docs + contracts, multi-repo cho code mini-app nặng. Tức là contract và toàn bộ tài liệu sống chung một kho — để traceability và cùng-PR dễ. Còn code của những mini-app rất lớn thì tách ra repo riêng. Đây là điểm cân bằng hợp lý khi code phình to nhưng bạn vẫn muốn tài liệu thống nhất.
📌 Quyết định này đảo ngược được: bắt đầu monorepo, tách ra multi-repo sau khi đội/đặc thù buộc phải, dễ hơn nhiều so với gom nhiều repo về một. Nguyên tắc chung của khóa: chọn cái rẻ để đổi hướng.
3. Contract-first — trái tim của docs cho super-app
Đây là phần quan trọng nhất của bài, và là phần mới so với mọi thứ trước đó. Nếu Lesson 3 dạy bạn tổ chức tài liệu bên trong một mini-app, thì contract dạy bạn quản lý tài liệu giữa các mini-app — đúng chỗ mà sự cố Minh xảy ra.
3.1 Ba loại ranh giới — đừng lẫn
Người mới hay nghĩ "contract = hai mini-app nói chuyện với nhau". Thực tế super-app có ba loại ranh giới, và contract ở mỗi loại trông khác nhau:
| # | Ranh giới | Bản chất | Ai gọi ai | Phổ biến? |
|---|---|---|---|---|
| ① | Shell ↔ mini-app | host contract: vòng đời (onMount...), điều hướng, cấp token | Shell điều phối mọi mini-app | Bắt buộc với mọi mini-app |
| ② | Mini-app ↔ BFF | API nghiệp vụ (REST/gRPC): endpoint, field, mã lỗi HTTP | Mini-app (front-end) gọi BFF (back-end) | Phổ biến nhất — nơi drift hay xảy ra nhất |
| ③ | Mini-app ↔ mini-app (trực tiếp) | một mini-app gọi thẳng capability của mini-app khác | Hiếm — thường nên tránh | Hiếm; gây coupling chặt |
Golden thread của khóa — sự cố Minh — là loại ②, KHÔNG phải ③. Booking là mini-app front-end (người dùng bấm "hủy & hoàn tiền"); nó gọi BFF (back-end xử lý POST /refunds). Hai mini-app front-end không tự phơi REST endpoint cho nhau gọi — chỉ back-end mới làm thế. Đó là lý do contract refund có Authorization: Bearer <token> và mã 400/401/409 — dấu hiệu của một API server.
💡 Vì sao refund ở BFF chứ không ở mini-app Payments? Vì refund là API mà nhiều mini-app cùng cần (Booking hủy chỗ, Wallet hoàn vào ví). API dùng-chung-nhiều-mini-app sống ở BFF — cổng back-end của đội super-app — thay vì nhét vào một mini-app. Nếu nhét vào Payments, thì Booking phải phụ thuộc chéo vào Payments (coupling loại ③). BFF cắt nút đó: mini-app chỉ gọi một cổng chung.
📌 Lưu ý đặt tên: BFF (Backend-for-Frontend — cổng API back-end làm điểm vào chung cho front-end) do đội super-app sở hữu (cùng đội với shell). Mini-app là consumer; BFF là provider. Đây là ranh giới giữa hai đội thật — đúng tinh thần "hai đội không đọc code của nhau, họ đọc contract của nhau".
3.2 Contract là gì, và vì sao nó là docs-as-code
Nhớ lại sự cố Minh ở Lesson 1: đội super-app thêm trường bắt buộc reason vào API hoàn tiền trong BFF, sửa code, sửa test, ship. Nhưng tài liệu trên wiki thì không ai đụng tới. Minh đọc tài liệu cũ, gọi API thiếu reason, và hàng trăm khách không nhận được tiền hoàn suốt cuối tuần.
Câu hỏi gốc: vì sao tài liệu API lại sống tách rời code API? Câu trả lời của super-app là một artifact tên contract.
Một contract là tài liệu mô tả giao kèo của một API giữa bên cung cấp (provider) và bên tiêu dùng (consumer). Nó nói rõ: endpoint nào, nhận trường gì, trường nào bắt buộc, trả về gì, lỗi ra sao. Nó chính là "tài liệu API" — nhưng được nâng lên thành một artifact docs-as-code đúng nghĩa:
- Versioned (có phiên bản, lưu trong Git) —
refund-v1.md,refund-v2.md, không phải một trang wiki bị ghi đè im lặng. - Reviewed — mọi thay đổi contract đi qua PR; người tiêu dùng (consumer) được mời review.
- CI-checked — máy kiểm code provider có thực sự khớp contract không.
- Sống cạnh code —
contracts/bff/refund-v2.mdnằm cùng monorepo vớibff/src/.
Khác biệt sống còn so với wiki của Minh: contract không thể đổi âm thầm. Đổi nó là một commit, một PR, một lượt review của các đội tiêu dùng. Cái lỗ hổng "không gì buộc tài liệu đi cùng code" (chẩn đoán của Lesson 1) bị bịt kín tại tầng contract.
💡 Một câu để nhớ: Trong một dự án đơn lẻ, contract ẩn — code tự nói chuyện với nhau. Trong super-app nhiều đội, contract phải hiện ra thành tài liệu — vì hai đội không đọc code của nhau, họ đọc contract của nhau. Contract là tài liệu bắt buộc duy nhất trong cả khóa.
3.3 Một contract mẫu trông như thế nào
Đây là contracts/bff/refund-v2.md — chính là contract mà giá như đã tồn tại thì Minh đã không sai. Để ý frontmatter khai báo phiên bản, consumer, và liên kết traceability ngược về yêu cầu:
---
id: CONTRACT-bff-refund
version: 2.0.0
status: active # draft | active | deprecated
provider: bff
consumers: [booking, wallet]
supersedes: refund-v1.md
traceability:
upstream:
- FR-014-bff-refund-reason
- BRD-007-bff-refund-compliance
updated: "2026-06-02"
---
# Contract — BFF Refund API (v2)
## Endpoint
`POST /refunds`
## Request — các trường
| Trường | Kiểu | Bắt buộc | Ghi chú |
|-------------|--------|----------|---------|
| `order_id` | string | ✅ có | Mã đơn cần hoàn tiền |
| `amount` | number | ✅ có | Số tiền hoàn (≤ số tiền gốc) |
| `reason` | string | ✅ **CÓ** | **MỚI ở v2.** Lý do hoàn tiền (tuân thủ quy định). |
> ⚠️ **Breaking change so với v1:** `reason` chuyển từ *không tồn tại* sang *bắt buộc*.
> Consumer chưa gửi `reason` sẽ nhận `400 Bad Request`.
## Response — thành công (200)
```json
{ "refund_id": "rf_123", "status": "processing" }Response — lỗi
| Mã | Khi nào | Consumer phải làm gì |
|---|---|---|
400 | Thiếu reason hoặc trường sai | Hiển thị lỗi, không nuốt im lặng |
409 | Đơn đã hoàn trước đó | Báo "đã hoàn", không thử lại |
Migration từ v1
Consumer đang dùng v1 phải thêm trường reason trước 2026-07-01.
Sau ngày đó, v1 bị gỡ. Xem mục "Vòng đời" trong contracts/README.md.
Hãy đối chiếu với sự cố Minh. Nếu contract này tồn tại:
1. Minh mở `contracts/payments/refund-v2.md`, **thấy ngay** `reason` được đánh dấu `✅ CÓ` và cảnh báo "Breaking change".
2. Bảng lỗi nói rõ `400` khi thiếu `reason`, và dặn "**không** nuốt im lặng" — đúng cái bẫy đã hại Booking.
3. Code Booking thêm `reason`. Không có cuối tuần cháy máy.
Tài liệu *đúng*, vì nó **không thể trôi khỏi code** — phần 3.4 và 5 sẽ cho thấy cơ chế buộc nó đúng.
### 3.4 Vòng đời một contract
Contract không bất biến — nó tiến hóa. Nhưng nó tiến hóa *có kỷ luật*, theo một vòng đời rõ ràng:
```mermaid
flowchart LR
DRAFT["draft<br/>(provider đề xuất)"] --> REVIEW["review<br/>(consumer góp ý qua PR)"]
REVIEW --> ACTIVE["active<br/>(đã merge, code phải khớp)"]
ACTIVE --> CHANGE{"cần đổi?"}
CHANGE -- "thay đổi không phá vỡ<br/>(thêm trường optional)" --> MINOR["bump minor<br/>(v2.0 → v2.1)"]
CHANGE -- "thay đổi phá vỡ<br/>(thêm trường bắt buộc)" --> MAJOR["contract version mới<br/>(v1 → v2), v1 deprecated"]
MINOR --> ACTIVE
MAJOR --> DEPRECATED["v cũ: deprecated<br/>(có hạn gỡ)"]
DEPRECATED --> REMOVED["removed<br/>(sau hạn migration)"]
Hai loại thay đổi, hai cách xử lý khác nhau hẳn:
- Thay đổi không phá vỡ (non-breaking): thêm một trường optional, thêm một mã lỗi mới. Consumer cũ vẫn chạy. → bump minor (v2.0 → v2.1), cùng file, ghi changelog.
- Thay đổi phá vỡ (breaking): thêm trường bắt buộc (đúng case
reason), đổi kiểu dữ liệu, gỡ endpoint. Consumer cũ sẽ vỡ. → tạo phiên bản mới (refund-v2.md), đánh dấu bản cũdeprecatedkèm hạn migration, và — quan trọng nhất — thông báo mọi consumer (phần 4.4).
📌 Quy tắc vàng của contract: thêm trường bắt buộc luôn là breaking change. Đây chính xác là cái mà đội super-app của Lesson 1 đã làm mà không coi là breaking — họ xử lý
reasonnhư một sửa đổi nội bộ BFF, không phải một thay đổi giao kèo. Contract-first buộc bạn gọi đúng tên nó và kích hoạt quy trình thông báo.
4. Git strategy
Có cấu trúc repo (phần 2) và contract (phần 3) rồi, giờ cần quy trình thay đổi vận hành chúng. Đây là Lesson 4 được nâng lên quy mô nhiều đội — cùng nguyên tắc, thêm các tình huống xuyên đội.
4.1 Branching cho docs + code trong monorepo
Trong monorepo, mọi đội chia sẻ một nhánh main. Quy ước đặt tên nhánh (mở rộng Lesson 4) thêm tên mini-app để biết ngay thay đổi thuộc đội nào:
<loại>/<mini-app hoặc bff>/<mô-tả-ngắn-kebab-case>
docs/booking/cancel-window-2h # chỉ tài liệu Booking
feat/bff/refund-reason-field # code BFF (kèm contract + docs)
contract/bff/refund-v2 # đổi contract (đụng nhiều đội)
Tiền tố contract/ đáng có riêng: nó báo hiệu PR này chạm vào ranh giới giữa các đội, nên cần nhiều mắt hơn bình thường. Một người review PR contract/bff/refund-v2 biết ngay: "đây không phải sửa nội bộ, đây là thay đổi giao kèo — phải kéo consumer vào".
4.2 Pull request xuyên đội (cross-team)
PR thông thường (Lesson 4) chỉ cần đội của mình review. PR xuyên đội thì khác: nó đụng tài liệu/contract mà đội khác phụ thuộc. Quy trình:
- Provider (đội super-app, chủ sở hữu BFF) mở PR đổi contract.
CODEOWNERStự động gán các consumer (Booking, Wallet) làm reviewer (phần 4.3).- PR không merge được cho tới khi mỗi consumer bị ảnh hưởng approve — họ xác nhận "đội tôi đã biết và sẽ migrate".
- Mô tả PR phải nêu rõ: ai bị ảnh hưởng, breaking hay không, hạn migration.
Đây là nâng cấp lớn so với sự cố Minh: thay vì đội super-app đổi API BFF trong im lặng, giờ Booking bắt buộc được mời vào bàn trước khi thay đổi thành sự thật. Review xuyên đội biến "thông báo sau khi đã vỡ" thành "đồng thuận trước khi đổi".
4.3 CODEOWNERS gán reviewer theo mini-app
CODEOWNERS của repo này hiện ở dạng tối giản BDFL-hybrid (một lead sở hữu tất cả). Ở quy mô super-app, bạn mở rộng nó để mỗi đường dẫn tự gán đúng đội — và, then chốt, để contract của một provider tự gán cho cả consumer của nó:
# Mặc định: lead maintainer (giữ nguyên tinh thần repo gốc)
* @leduykhuong-daniel
# Mỗi mini-app: đội của nó sở hữu code + docs
/mini-apps/payments/ @team-payments
/mini-apps/booking/ @team-booking
/mini-apps/wallet/ @team-wallet
/mini-apps/rewards/ @team-rewards
# Shell + BFF: đội super-app (đội nền tảng)
/shell/ @team-super-app
/bff/ @team-super-app
/docs/product-docs/platform/ @team-super-app
# ⭐ Contract: provider SỞ HỮU, nhưng consumer được gán review
# (nhiều owner trên một dòng = tất cả được mời review)
/contracts/bff/ @team-super-app @team-booking @team-wallet
/contracts/shell-host/ @team-super-app @team-payments @team-booking @team-wallet @team-rewards
Dòng /contracts/bff/ là tinh túy của cả chiến lược: đổi contract hoàn tiền của BFF → Booking và Wallet tự động bị kéo vào review. Không ai phải nhớ "Booking đang dùng API này" — Git nhớ giúp. Đúng như Lesson 4 đã hứa, nhưng giờ ở quy mô liên đội.
4.4 Docs + code + contract trong CÙNG một PR
Đây là quy tắc trung tâm của Lesson 4, nâng lên ba thành phần. Một PR đổi hành vi API của super-app phải mang theo cả ba:
Vì sao điều này triệt tiêu sự cố Minh:
- Cùng một review: reviewer thấy code thêm
reason, contract đánh dấureasonbắt buộc, và FR/test cập nhật — cạnh nhau. Nếu thiếu một trong ba, lệch lộ ra ngay tại cổng, không phải ba tháng sau. - Cùng một lịch sử: Git ghi "hành vi
reason+ contract của nó + tài liệu của nó" trong một PR. Truy vết về sau luôn thấy ba thứ đi đôi. - Cùng một đường lui: phải revert thì revert đúng PR đó — code, contract, docs lùi cùng nhau. Không còn cảnh "code đã đổi nhưng contract vẫn quảng cáo API cũ".
Checklist PR của repo (CONTRIBUTING.md) đã có ô "Docs and code changed together (no doc drift)". Ở quy mô super-app, ta mở rộng nó thành ba ô:
- Code đổi hành vi → contract liên quan đã cập nhật (cùng PR).
- Contract đổi → tài liệu (FR/test) liên quan đã cập nhật (cùng PR).
- Contract đổi breaking → consumer đã được gán review + hạn migration đã ghi.
4.5 Xử lý thay đổi contract: provider sửa → consumer được notify
Gói lại toàn bộ phần 4 bằng quy trình đầu-cuối khi đội super-app đổi contract hoàn tiền trong BFF:
| Bước | Ai | Làm gì | Cơ chế |
|---|---|---|---|
| 1 | Đội super-app | Mở PR contract/bff/refund-v2, đánh dấu breaking | Branch + PR |
| 2 | (tự động) | Booking + Wallet được gán reviewer | CODEOWNERS |
| 3 | Booking + Wallet | Đọc contract, xác nhận ảnh hưởng, approve | Review xuyên đội |
| 4 | (tự động) | CI kiểm code BFF khớp contract v2 | Contract test (phần 5) |
| 5 | Đội super-app | Merge; v1 thành deprecated kèm hạn 2026-07-01 | Branch protection |
| 6 | Booking + Wallet | Mở PR riêng thêm reason, trước hạn | Vòng đời migration |
Hãy so với Lesson 1, nơi mọi thứ này không tồn tại — và hậu quả là cuối tuần cháy máy. Đây là khác biệt giữa "tài liệu là ghi chú tùy hứng" và "tài liệu là hợp đồng có hiệu lực".
5. CI/CD ở quy mô super-app
Phần 4 dùng con người (reviewer) chặn lỗi. Phần này dùng máy. Như Lesson 5 đã dạy: đẩy mọi lỗi máy-bắt-được sang CI để con người chỉ còn phán đoán. Ở quy mô super-app, CI có thêm việc — nhưng nó mở rộng từ chính validate-skills.sh + validate.yml thật của repo này, không phải xây lại từ đầu.
5.1 Điểm khởi đầu: CI thật của repo
Nhắc lại từ Lesson 5, CI hiện tại của repo gồm hai file:
scripts/validate-skills.sh— kiểm.claude-plugin/*.jsonlà JSON hợp lệ + mỗi skill cóSKILL.mdvới frontmattername:/description:, gom lỗi vào một cờfail,exit 1khi đỏ..github/workflows/validate.yml— chạy script đó ởpull_requestvàpushlênmain, chỉ gọi một script.
Hai lựa chọn thiết kế của nó là nền móng để mở rộng: (a) một cờ gom mọi lỗi (báo tất cả một lần), và (b) workflow chỉ gọi script (chạy lại được tại máy). Ta giữ nguyên hai nguyên tắc đó khi lên quy mô super-app.
5.2 Pipeline super-app — sáu tầng kiểm
Pipeline đầy đủ cho NovaApp thêm các tầng xung quanh lõi sẵn có:
Đi qua từng tầng:
① Phát hiện thay đổi có chọn lọc. Monorepo lớn không nên build mọi thứ mỗi PR. CI dò xem PR đụng mini-apps/booking/ hay bff/ rồi chỉ chạy phần đó (cộng các consumer của contract bị đổi). Đây là điểm khác lớn nhất so với repo đơn lẻ.
② Validate per-mini-app. Đây chính là validate-skills.sh được tổng quát hóa. Thay vì chỉ kiểm skills/, nó kiểm mọi file trong mini-apps/*/docs/ và bff/docs/ có frontmatter hợp lệ: status ∈ {draft, review, published}, có owner, và có khối traceability. Cùng cờ fail, cùng exit 1.
③ Contract check — tầng chống-Minh. Đây là tầng mới và quan trọng nhất của super-app. Nó kiểm: code của provider có thực sự khớp contract không?
Ví dụ contract refund-v2.md khai reason bắt buộc. Một test đối chiếu sẽ đảm bảo handler /refunds trong BFF thật sự từ chối request thiếu reason. Nếu code và contract lệch, CI đỏ. Đây là phiên bản tự động của câu hỏi mà đáng lẽ đội super-app phải tự hỏi ở Lesson 1.
④ Lint + link docs. Đúng Lesson 5: markdownlint-cli2 rà định dạng, lychee dò link hỏng. Nó gồm cả link traceability xuyên mini-app — ví dụ một FR Booking trỏ tới NFR-PLATFORM-003 mà file đó bị xóa thì link sẽ đỏ.
⑤ Build docs site tổng hợp. Generator (Starlight/VitePress, Lesson 5) gộp docs/product-docs/ của mọi mini-app + bff/docs/ + docs/platform/ thành một site duy nhất, có search Pagefind xuyên toàn ecosystem. Người đọc gõ "hoàn tiền" và tới thẳng contract BFF, bất kể đội nào viết.
⑥ Preview deploy per-PR. Mỗi PR sinh một URL xem thử (Cloudflare Pages/Netlify/Vercel) để reviewer thấy trang đã render, không phải Markdown thô — bắt được bảng vỡ, ảnh thiếu trước khi merge.
5.3 Một workflow mở rộng (giữ nguyên triết lý "gọi script")
Dưới đây là validate.yml mở rộng cho super-app. Để ý nó vẫn theo mẫu Lesson 5: workflow mỏng, mỗi tầng là một script gọi được tại máy.
name: validate
on:
pull_request:
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
# ② Lõi sẵn có — giữ nguyên từ repo gốc
- name: Validate manifest + skills + frontmatter
run: bash scripts/validate-skills.sh
# ③ Contract check — tầng mới của super-app
- name: Verify code matches contracts
run: bash scripts/check-contracts.sh
# ④ Lint + link
- name: Markdown lint
run: npx markdownlint-cli2 "mini-apps/**/docs/**/*.md" "bff/docs/**/*.md" "docs/**/*.md"
- name: Link check
run: npx lychee --no-progress "mini-apps/**/docs/**/*.md" "bff/docs/**/*.md" "docs/**/*.md"💡 Mẹo giữ pipeline lành mạnh (Lesson 5): mỗi check phải chạy được tại máy bằng một lệnh
bash scripts/.... Người đóng góp chạybash scripts/check-contracts.shở terminal, thấy đỏ/xanh trong vài giây, thay vì chờ Actions vài phút. Đó là dấu hiệu pipeline trưởng thành.
5.4 Một mẩu CI output mẫu (để cảm nhận)
Khi đội super-app mở PR thêm reason vào BFF nhưng quên cập nhật handler từ chối request thiếu reason, tầng ③ bắt được:
==> Validating manifest + skills + frontmatter
OK bff/docs/product-docs/02-requirements/functional/FR-014-bff-refund-reason.md
==> Verifying code matches contracts
OK contracts/shell-host/navigation-v1.md ↔ shell/src/nav.ts
ERR contracts/bff/refund-v2.md declares `reason` as REQUIRED,
but bff/src/refund.ts accepts requests without it
→ handler must return 400 when `reason` is missing
==> FAILED
Đây là khoảnh khắc then chốt của cả khóa: lỗi của Minh giờ là một dòng ERR đỏ trong CI, trước khi merge. Không còn một cuối tuần cháy máy ở production. Docs-as-code không "nhắc nhở" bạn cập nhật tài liệu — nó chặn bạn ship khi tài liệu (contract) và code lệch nhau.
6. Traceability xuyên super-app
Lesson 3 dạy traceability trong một mini-app: BRD → PRD → FR/NFR → Epic → Story → Test, mỗi hàng một sợi chỉ. Ở super-app, ta thêm một mắt xích: contract, và tổng hợp ma trận xuyên nhiều đội.
6.1 Chuỗi traceability mở rộng với contract
Khi một yêu cầu chạm ranh giới giữa các đội, sợi chỉ truy vết đi qua một contract:
Contract đứng giữa FR của provider (đội super-app/BFF) và việc tích hợp của consumer. Booking khai informed_by: CONTRACT-bff-refund trong FR của nó — đúng cơ chế informed_by mà Lesson 3 đã giới thiệu cho các tầng cắt ngang.
6.2 Ma trận traceability tổng hợp đa mini-app
Ở docs/traceability/ cấp super-app, ma trận thêm hai cột so với Lesson 3: Mini-app (đội nào sở hữu sợi chỉ) và Contract (nếu sợi chỉ chạm ranh giới):
| Mini-app / đội | BRD | PRD | FR | Contract | Story | Test |
|---|---|---|---|---|---|---|
| BFF (super-app) | BRD-007-refund-compliance | PRD-007 | FR-014-refund-reason | refund-v2 (provider) | STORY-030-add-reason | TEST-014 |
| Booking | BRD-021-cancel-refund | PRD-021 | FR-040-booking-refund | refund-v2 (consumer) | STORY-061-call-refund | TEST-040 |
| Wallet | BRD-003-topup | PRD-003 | FR-001-topup-bank | — | STORY-001-select-bank | TEST-001 |
Sức mạnh ở quy mô super-app:
- Ô Test trống = yêu cầu chưa kiểm chứng (gap), đúng như Lesson 3.
- Cùng một
Contractxuất hiện ở provider và consumer = bạn thấy ngay ai phụ thuộc vào cái gì. Lọc cộtContract = refund-v2→ ra đúng danh sách đội phải migrate khi v1 bị gỡ. - Tổng hợp toàn ecosystem: lãnh đạo sản phẩm gộp ma trận mọi mini-app để thấy bức tranh phủ sóng — yêu cầu nào chưa test, contract nào còn consumer chưa migrate — bất kể đội nào viết. Một kiến trúc, nhiều đội, một ngôn ngữ chung.
📌 Ma trận này tiêu thụ link chứ không nuôi tầng nào — nó là điểm cuối phục vụ audit và sign-off khi release một mini-app hay cả super-app.
7. Governance & ownership
Công cụ và cấu trúc là phần dễ. Phần khó — như Lesson 5 đã cảnh báo — là đưa con người vào nếp. Ở quy mô super-app nhiều đội, câu hỏi cốt lõi là: ai sở hữu cái gì, và làm sao mỗi đội tự quản tài liệu của mình mà vẫn nhất quán?
7.1 Ai sở hữu cái gì
| Thành phần | Owner | Chịu trách nhiệm |
|---|---|---|
mini-apps/<x>/ (code + docs) | Đội của mini-app đó | Chuỗi BRD→Test của mini-app, giữ docs khớp code |
bff/ (code + docs) | Đội super-app (đội nền tảng) | API dùng chung như /refunds; giữ code BFF khớp contract |
contracts/bff/ | Đội super-app (provider BFF) | Định nghĩa + tiến hóa contract; thông báo consumer khi đổi |
contracts/<provider>/ (các provider khác) | Đội provider tương ứng | Định nghĩa + tiến hóa contract; thông báo consumer khi đổi |
| Review một contract | Đội consumer | Xác nhận ảnh hưởng, approve trước khi contract đổi |
shell/ + docs/product-docs/platform/ | Đội super-app / nền tảng | Quy ước chung, design system, auth, contract của shell |
docs/traceability/ (tổng hợp) | Lãnh đạo sản phẩm / BA trưởng | Bức tranh phủ sóng toàn ecosystem |
| Chuẩn chung (template, CI, style guide) | Core team (BDFL-hybrid) | Một chuẩn cho mọi đội theo |
Nguyên tắc một dòng (Lesson 5): mọi tài liệu phải có một chủ rõ ràng. Tài liệu không chủ là tài liệu sẽ mục. CODEOWNERS (phần 4.3) mã hóa chính bảng này thành luật kỹ thuật.
7.2 Mô hình federated — tự quản theo chuẩn chung
Có hai thái cực sai khi quản tài liệu super-app:
- Tập trung cực đoan: một "đội tài liệu trung tâm" viết hết cho mọi mini-app. → Họ không đủ hiểu sâu từng sản phẩm; tài liệu thành nút cổ chai; lặp lại "việc của người khác" (bẫy 3 của Lesson 1).
- Phân tán cực đoan: mỗi đội làm theo cách riêng, không chuẩn chung. → Booking và Payments dùng cấu trúc khác nhau; một người chuyển đội phải học lại; không thể tổng hợp ma trận.
Lời giải là federated (liên bang) — điểm cân bằng:
- Core team sở hữu chuẩn —
templates/product-docs/, CI, style guide, quy ước frontmatter + traceability. Một chuẩn, không bàn cãi per-đội. - Mỗi đội mini-app sở hữu nội dung — họ viết, review, bảo trì
docs/của mình theo chuẩn đó. Người gần code nhất sở hữu tài liệu — đúng lời giải bẫy 3 của Lesson 1. - Đóng góp ngược: đội nào tìm ra cách cải tiến template gửi PR về core; cải tiến tốt thành chuẩn cho mọi người. Đây là vòng học hỏi của cả ecosystem.
7.3 Review cadence + style guide
- Review cadence (nhịp rà soát): mỗi quý, mỗi đội chạy một lượt "freshness sweep" — đọc metric
freshness(Lesson 5), tìm trang >6 tháng không đụng, quyết định cập nhật hay archive. Contract có nhịp riêng, chặt hơn: rà mỗi khi provider release. - Style guide chung: một trang
docs/platform/STYLE.md— quy ước Markdown, cách đặt tên, chuẩn song ngữ (Việt chính / Anh phụ, đừng để bản dịch chặn xuất bản — Lesson 5), và cách viết theo khung Diátaxis (tutorial/how-to/reference/explanation). Core team sở hữu, mọi đội theo.
8. Case study end-to-end
Đây là phần demonstrate quan trọng nhất của bài. Ta đi MỘT tính năng hoàn chỉnh của NovaApp từ đầu đến cuối, và cho thấy chính sự cố Minh của Lesson 1 — giờ bị chặn ngay tại review và CI. Bối cảnh: một quy định mới buộc mọi giao dịch hoàn tiền phải ghi lý do. Payments phải thêm trường bắt buộc reason vào API hoàn tiền — đúng thay đổi đã gây ra cuối tuần cháy máy.
📂 Đối chiếu với ví dụ thật: case study dưới đây dùng đường dẫn theo layout đầy đủ-quy mô (product-docs lồng trong bff/, contract
contracts/bff/refund-v2.md). Bản dựng thật ởexamples/nova-superapp/dùng layout đơn giản hoá (product-docs ở gốc, contract phẳng) — cùng một golden thread, chỉ khác cách sắp xếp thư mục (xem note ở §2.1).
Bước 1 — BRD: nêu vấn đề nghiệp vụ
BA của đội super-app tạo bff/docs/product-docs/00-discovery/brd/BRD-007-bff-refund-compliance.md:
Vấn đề kinh doanh: Quy định mới yêu cầu mọi giao dịch hoàn tiền phải lưu lý do để phục vụ kiểm toán. API hoàn tiền hiện tại trong BFF không thu thập lý do → NovaApp đối mặt rủi ro tuân thủ.
Mục tiêu: 100% giao dịch hoàn tiền có lý do hợp lệ, trước hạn quy định. Owner: Business Analyst (đội super-app).
BRD nói bằng ngôn ngữ nghiệp vụ, không nhắc API. Nó link downstream tới PRD.
Bước 2 — PRD → FR: biến thành yêu cầu
PRD (PRD-007) mô tả sản phẩm: thêm bước "chọn lý do hoàn tiền" vào luồng. Từ đó dẫn xuất một FR atomic và testable:
FR-014-bff-refund-reason.md (frontmatter theo đúng template repo):
---
id: FR-014
title: BFF Refund API yêu cầu trường reason bắt buộc
status: approved
owner: BA đội super-app
priority: must
traceability:
upstream:
- PRD-007-bff-refund-compliance
downstream:
- CONTRACT-bff-refund
- STORY-030-add-reason
- TEST-014-refund-reason
---Acceptance criteria: Given một yêu cầu hoàn tiền, when thiếu trường
reason, then hệ thống từ chối với400 Bad Requestvà không xử lý hoàn tiền.
Để ý dòng downstream: CONTRACT-bff-refund — FR này chảy qua một contract, vì nó chạm ranh giới đội.
Bước 3 — Đổi contract: từ v1 sang v2
Đây là mắt xích mà Lesson 1 thiếu. Đội super-app tạo contracts/bff/refund-v2.md (đúng mẫu phần 3.3): reason chuyển từ không tồn tại → bắt buộc, đánh dấu breaking change, ghi consumer [booking, wallet], hạn migration 2026-07-01. Bản refund-v1.md được đánh dấu status: deprecated.
Bước 4 — MỘT pull request chứa cả ba
Đội super-app mở PR trên nhánh feat/bff/refund-reason-field. Diff của PR chứa đồng thời:
bff/src/refund.ts | code: validate reason, trả 400 nếu thiếu
contracts/bff/refund-v2.md | contract: reason = bắt buộc (breaking)
contracts/bff/refund-v1.md | đánh dấu deprecated + hạn 2026-07-01
bff/docs/product-docs/.../FR-014-bff-refund-reason.md | FR cập nhật
bff/docs/product-docs/.../TEST-014-refund-reason.md | test plan + contract test
Mô tả PR (theo chuẩn Lesson 4 — thay đổi gì / vì sao / cần để ý gì):
Tiêu đề: feat(bff): thêm trường bắt buộc
reasonvào API hoàn tiền (refund v2)Thay đổi gì:
reasonthành trường bắt buộc trong BFF. Contract bump v1 → v2 (breaking). Thiếureason→400.Vì sao: Tuân thủ quy định mới —
BRD-007,FR-014.Cần để ý — ⚠️ BREAKING cho consumer: Booking và Wallet đang gọi
/refunds. Hai đội phải thêmreasontrước2026-07-01. Đã gán hai đội review. Sau hạn, v1 bị gỡ.
Bước 5 — CODEOWNERS kéo Booking vào review
PR đụng contracts/bff/ → CODEOWNERS tự động gán @team-booking và @team-wallet làm reviewer (phần 4.3). Đây chính là khoảnh khắc cứu Minh. Thay vì đội super-app đổi API BFF trong im lặng (Lesson 1), Booking bắt buộc xuất hiện trên PR trước khi thay đổi thành sự thật.
Một reviewer Booking để lại nhận xét:
[blocking]Đội Booking xác nhận đang gọi/refundsở luồng hủy đặt chỗ. Chúng tôi sẽ mở PR thêmreasontrong sprint này, trước hạn 07-01. Approve với điều kiện hạn migration giữ nguyên.
So với Lesson 1: cuộc trao đổi này xảy ra trên PR, trước merge — không phải trong cuộc họp căng thẳng sau khi production đã vỡ.
Bước 6 — CI kiểm, và bắt lỗi nếu có
CI chạy pipeline phần 5. Giả sử đội super-app quên sửa handler BFF từ chối request thiếu reason — tầng ③ contract check bắt ngay:
==> Verifying code matches contracts
ERR contracts/payments/refund-v2.md declares `reason` as REQUIRED,
but mini-apps/payments/src/refund.ts accepts requests without it
==> FAILED
PR bị chặn merge. Payments sửa handler, đẩy lại, CI xanh. Đây là điều Lesson 1 mơ tới: tài liệu (contract) và code không thể lệch nhau ra tới production — máy chặn ở cổng.
Bước 7 — Ship, và sợi chỉ traceability khép kín
PR xanh + Booking/Wallet approve + branch protection thỏa → merge. Ma trận docs/traceability/ ghi sợi chỉ hoàn chỉnh:
| Mini-app | BRD | PRD | FR | Contract | Story | Test |
|---|---|---|---|---|---|---|
| Payments | BRD-007 | PRD-007 | FR-014 | refund-v2 | STORY-030 | TEST-014 ✅ |
Vài ngày sau, Booking mở PR riêng (feat/booking/refund-add-reason) thêm reason vào lời gọi của họ, trước hạn. Ma trận thêm một hàng consumer trỏ tới cùng refund-v2. Khi 2026-07-01 tới, lọc cột Contract = refund-v1 → 0 consumer còn lại → an toàn gỡ v1.
So sánh hai thế giới
| Lesson 1 (không docs-as-code) | Lesson 6 (chiến lược đầy đủ) | |
|---|---|---|
| Tài liệu API | Trang wiki, đổi âm thầm | Contract versioned, đổi qua PR |
| Booking biết thay đổi | Sau khi production vỡ | Trước merge, qua CODEOWNERS |
| Code ↔ tài liệu lệch | Không ai phát hiện cả cuối tuần | CI chặn tại cổng (tầng ③) |
| Hậu quả | Cháy máy, mất niềm tin, họp căng | Một PR sạch, ship bình thường |
Đây là toàn bộ giá trị của khóa học, gói trong một dòng: docs-as-code biến "tài liệu lỗi thời gây sự cố" thành một lỗi CI đỏ trước khi merge.
9. Rollout 90 ngày cho đội 20–40 người
Có chiến lược rồi, làm sao đưa vào một tổ chức thật mà không vỡ trận? Như Lesson 5 đã cảnh báo: ép cả đội 30 người đổi cách làm cùng lúc gần như luôn thất bại. Thay vào đó, dùng phased adoption (triển khai theo giai đoạn). Đây là bản 90 ngày, mở rộng kế hoạch 30 ngày của Lesson 5 lên quy mô super-app.
| Phase | Cửa sổ | Mục tiêu | Sản phẩm bàn giao | Milestone |
|---|---|---|---|---|
| 1 — Nền tảng | Ngày 1–30 | Chứng minh quy trình chạy được trên một mini-app | Repo monorepo dựng; templates/product-docs/ copy vào pilot (Payments); CI tối thiểu (validate-skills.sh mở rộng); 3 tài liệu thật theo Diátaxis | Pilot có 1 sợi chỉ BRD→Test đầy đủ; 2–3 champion |
| 2 — Contracts | Ngày 31–55 | Dựng tầng contract giữa 2 đội | contracts/ + README vòng đời; contract đầu tiên (refund v2) liên kết Payments↔Booking; CODEOWNERS gán consumer | 1 contract đi qua review xuyên đội thành công |
| 3 — CI/Automation | Ngày 56–75 | Để máy gác cổng | Contract check (tầng ③); markdownlint + lychee; preview deploy per-PR; baseline 4 metric | CI chặn được 1 lỗi code↔contract lệch thật |
| 4 — Scale | Ngày 76–90 | Mở mọi mini-app + chuẩn hóa | Wallet + Rewards vào; docs site tổng hợp + Pagefind; mô hình federated; "docs+code+contract cùng PR" thành bắt buộc | Ma trận traceability tổng hợp 4 mini-app; quy tắc bắt buộc bật |
Giảm kháng cự — bài học từ Lesson 5
- Champion model, không email "từ nay tất cả phải...". Người pilot Phase 1 thành champion kèm cặp đội tiếp theo. Lan tỏa bằng người, không bằng mệnh lệnh.
- Bắt buộc sau cùng, không phải đầu tiên. Phase 1–3 là tự nguyện, có ví dụ sống để khoe. Chỉ Phase 4 mới bắt buộc — sau khi đa số đã thấy giá trị.
- CI bắt đầu lỏng, siết dần. Phase 1 CI chỉ
warnnhiều,blockít (tránh "kêu sói"). Phase 3–4 mới siết contract check thành blocking. - Rào cản đóng góp thật thấp. Mọi check chạy được tại máy (
bash scripts/...); template rõ; README-first cho tới khi đau mới thêm site. Người mới sửa lỗi cú pháp trong riêng tư trước khi reviewer thấy.
📌 90 ngày không phải để "xong" — nó để đạt điểm tự duy trì: champion đủ nhiều, CI đủ tin, một-hai mini-app làm gương đủ thuyết phục để phần còn lại tự muốn theo.
10. Metrics, maintenance + tổng kết toàn khóa
10.1 Metrics
Bạn không quản được cái bạn không đo. Bốn metric đủ để theo sức khỏe tài liệu một super-app — ba cái đầu từ Lesson 5, cái thứ tư là mới cho super-app:
| Metric | Đo gì | Cách đo | Tín hiệu xấu |
|---|---|---|---|
| Coverage (độ phủ) | % tính năng có tài liệu | Đếm file có frontmatter hợp lệ / tổng tính năng | Coverage giảm khi sản phẩm lớn lên |
| Freshness (độ tươi) | Tài liệu cập nhật gần đây | Phân bố date / commit cuối mỗi file | Nhiều trang >6 tháng không đụng |
| Contribution rate | Bao nhiêu người thật sự viết | Số tác giả docs khác nhau / tháng | 1–2 người gánh hết → rủi ro xe buýt |
| Contract-sync ⭐ | Code có khớp contract không | % contract pass tầng ③ CI | Bất kỳ contract nào lệch code = nguy cơ "sự cố Minh" |
Ba metric đầu tự động hóa được nhờ kỷ luật frontmatter (Lesson 2) + CI (Lesson 5). Contract-sync thì phải tự động — nó chính là tầng ③ CI báo cáo ra một con số. Một dashboard nhỏ đọc kết quả CI đã đủ cảnh báo trước khi lệch contract thành sự cố.
10.2 Maintenance
- Freshness sweep mỗi quý: mỗi đội rà trang cũ, cập nhật hoặc archive (phần 7.3).
- Contract migration tracking: mỗi contract
deprecatedcó hạn gỡ; ma trận traceability cho biết consumer nào chưa migrate. Đừng gỡ contract khi cột consumer chưa trống. - Template tiến hóa qua đóng góp ngược (federated): cải tiến tốt từ một đội thành chuẩn cho mọi đội.
- Tránh bốn failure mode (Lesson 5): doc drift (→ docs+code+contract cùng PR), CI kêu sói (→ lỏng rồi siết), một người gánh (→ champion + rotation), site rỗng (→ nội dung trước hình thức).
10.3 Tổng kết toàn khóa — sáu bài, một hệ thống
Sáu bài là một vòng khép kín. Lesson 1 đặt ra một câu hỏi đau — "tại sao tài liệu nói một đằng, code làm một nẻo?" Năm bài sau xây từng tầng câu trả lời.
Và Lesson 6 khép vòng. Với contract-first + cùng-PR + CI contract check + traceability xuyên đội, sự cố Minh không còn là chuyện "có thể xảy ra rồi rút kinh nghiệm". Nó là một lỗi CI đỏ trước khi merge.
Năm nguyên tắc gói cả khóa:
- Tài liệu là code — text, Git, PR, CI. (L1–L2)
- Tài liệu có kiến trúc — chuỗi truy vết được, không sót yêu cầu. (L3)
- Thay đổi đi qua review — docs + code (+ contract) trong cùng PR. (L4)
- Máy gác lỗi máy-bắt-được — con người dành sức cho phán đoán. (L5)
- Quy mô bằng contract + federated — mỗi đội tự quản theo chuẩn chung, ranh giới là hợp đồng versioned. (L6)
💡 Một câu duy nhất nếu chỉ được nhớ một điều: "Tài liệu chưa cập nhật thì tính năng chưa xong" (Lesson 1) — và ở super-app, "tài liệu" gồm cả contract, được canh giữ bởi review xuyên đội và CI.
🛠 Bài tập Capstone
Mục tiêu: thiết kế một chiến lược docs-as-code đầy đủ cho một super-app giả định của riêng bạn. Đây là bài tập tổng kết — nó gom mọi thứ sáu bài. Đừng dùng super-app thật của tổ chức bạn trong giáo trình mở này. Hãy nghĩ ra một super-app hư cấu với 3 mini-app tùy chọn — ví dụ một super-app giao đồ ăn với mini-app Order, Delivery, Loyalty.
Sản phẩm bàn giao là một tài liệu chiến lược (3–5 trang) gồm năm phần:
Phần 1 — Cấu trúc repo. Vẽ cây thư mục super-app của bạn (theo mẫu phần 2.1): shell/, mini-apps/{ba-mini-app}/, contracts/, docs/. Quyết định monorepo hay multi-repo và viết 2–3 câu giải thích lựa chọn dựa trên bảng trade-off phần 2.3.
Phần 2 — Một contract mẫu. Viết một contract đầy đủ theo mẫu phần 3.2. Nó gồm: frontmatter (version, provider, consumers, traceability), bảng trường request (đánh dấu trường bắt buộc), response thành công + bảng lỗi, và một mục "Migration". Chọn một API thật sự chạm ranh giới hai mini-app của bạn.
Phần 3 — Git strategy. Viết quy ước đặt tên nhánh. Thêm một đoạn CODEOWNERS gán mỗi mini-app, và gán consumer review cho contract (theo phần 4.3). Cuối cùng, viết checklist PR ba ô "docs + code + contract cùng PR" (phần 4.4).
Phần 4 — CI checklist. Liệt kê các tầng CI bạn sẽ bật (theo 6 tầng phần 5.2), đánh dấu tầng nào block và tầng nào chỉ warn. Rồi giải thích tầng contract check (③) sẽ kiểm gì cho contract ở Phần 2.
Phần 5 — Kế hoạch rollout 90 ngày. Một bảng 4 phase (theo phần 9): mỗi phase nêu mục tiêu, sản phẩm bàn giao, milestone. Chỉ rõ ai là champion, 4 metric với cách đo, và 2 failure mode bạn dự phòng cùng cách tránh.
Tiêu chí hoàn thành: Một người khác đọc tài liệu chiến lược của bạn và có thể (a) hình dung cây repo, (b) hiểu một contract của bạn versioned + reviewed thế nào, (c) thấy rõ "sự cố Minh" bị chặn ở đâu trong quy trình của bạn (review? CI? cả hai?), và (d) biết tuần nào làm gì trong 90 ngày. Nếu bạn chỉ rõ được điểm chặn sự cố Minh, bạn đã nắm trọn tinh thần khóa học.
Tóm tắt toàn khóa
- Năm mảnh thành một hệ thống: lý do (L1) → cú pháp (L2) → kiến trúc (L3) → quy trình (L4) → tự động & quy mô (L5) → chiến lược super-app (L6).
- Cấu trúc repo:
shell/+mini-apps/*/(mỗi cái cósrc/+docs/) +contracts/+docs/platform/. Monorepo mặc định vì nó làm "docs+code+contract cùng PR" tự nhiên; multi-repo khi đội rất lớn cần cô lập tuyệt đối. - Contract-first là trái tim: contract = tài liệu API versioned, reviewed, CI-checked, sống cạnh code. Thêm trường bắt buộc luôn là breaking change → phiên bản mới + thông báo consumer. Đây là tầng bịt kín lỗ hổng của sự cố Minh.
- Git strategy xuyên đội: nhánh có tên mini-app;
CODEOWNERSgán consumer review contract của provider; docs + code + contract trong cùng PR. - CI 6 tầng mở rộng từ
validate-skills.sh+validate.ymlthật: phát hiện-có-chọn-lọc → validate frontmatter → contract check (tầng chống-Minh) → lint+link → build site tổng hợp → preview deploy. Lỗi của Minh giờ là một dòngERRđỏ trước merge. - Traceability xuyên super-app: chuỗi BRD→Test thêm mắt xích contract; ma trận tổng hợp đa mini-app thêm cột Mini-app + Contract → thấy ai phụ thuộc cái gì.
- Governance federated: core team sở hữu chuẩn, mỗi đội sở hữu nội dung; mọi tài liệu có chủ rõ ràng.
- Rollout 90 ngày 4 phase (nền tảng → contracts → CI → scale); 4 metric (coverage/freshness/contribution/contract-sync); champion model, bắt buộc sau cùng.
- Khép vòng: sự cố Minh của Lesson 1 — với chiến lược đầy đủ này — bị chặn ngay tại review xuyên đội và CI contract check, không bao giờ ra tới production.
Đọc tiếp (Track B)
Đây là bài cuối của Track A — Fundamentals. Bạn đã có đủ hạ tầng để đưa một super-app thật vào nếp docs-as-code: từ lý do, cú pháp, kiến trúc, quy trình, tự động hóa, tới một chiến lược hoàn chỉnh cho nhiều đội.
Sắp ra mắt — Track B: Agentic Workflow (luồng làm việc cùng trợ lý AI). Nếu Track A dạy bạn hạ tầng, Track B dạy bạn đòn bẩy. Ý tưởng: dùng các trợ lý AI (như Claude Code) để sinh, rà soát, và bảo trì tài liệu ở quy mô.
Chúng sinh nháp BRD/PRD/FR từ template và kiểm tính nhất quán giữa code và contract. Khi phát hiện drift, chúng đề xuất sửa và tự động cập nhật ma trận traceability — tất cả có người giám sát. Chính cây repo, contract, và CI bạn dựng trong bài này sẽ là nền để trợ lý AI làm việc trên đó.
Trong lúc chờ Track B, hãy củng cố nền tảng:
- Mở
templates/product-docs/và áp một mini-app hư cấu của bạn vào chuỗi00-discovery → 04-deliveryđể thấy traceability thành hình. - Đọc lại
scripts/validate-skills.shvà.github/workflows/validate.yml— rồi tự hỏi: "tầng contract check (③) của mình sẽ trông như thế nào?" - Tra cứu thuật ngữ bất kỳ ở
docs/GLOSSARY.md.
Bài viết này là một bài học trong bộ giáo cụ mã nguồn mở docs-as-code-kit (CC BY 4.0) tôi đang xây — dạy docs-as-code qua một super-app hư cấu "NovaApp". Đọc trọn bộ, xem ví dụ chạy được, hoặc cài làm plugin Claude Code: leduykhuong-daniel/OSS-docs-as-code-kit.
