10 KiB
12 quy tắc viết tài liệu kiến trúc
Mọi skill sa-* phải tuân thủ. Đây là thứ phân biệt một tài liệu kiến trúc dev đọc xong thi
công được với một bộ sơ đồ hộp và mũi tên.
Bộ này song song với W1–W12 của bộ BA (../../ba-lifecycle/references/writing-rules.md).
Khi làm tài liệu chạm cả hai (ví dụ ICD đối chiếu API của BA), tuân thủ cả hai bộ.
D1 — Một ADR, một quyết định
❌ ADR-004: Chọn stack backend — bên trong quyết cả ngôn ngữ, framework, ORM, message broker.
✅ Bốn ADR. Vì sáu tháng sau khi đổi message broker, bạn cần supersede đúng một quyết định,
không phải viết lại một tài liệu còn đúng ba phần tư.
Ngưỡng "quyết định nào cần ADR": xem decision-radar.md.
D2 — Cấm NFR định tính
Danh sách cấm, kèm cách thay:
| Cấm | Thay bằng |
|---|---|
| nhanh, phản hồi tốt | p95 ≤ 300ms cho GET /orders tại 2.000 rps |
| chịu tải cao, mở rộng được | 20.000 đơn/giờ giờ cao điểm, tăng 3×/năm trong 2 năm |
| ổn định, sẵn sàng cao | 99.9%/tháng ⇒ ngân sách lỗi 43 phút/tháng |
| bảo mật | tên mối đe doạ + biện pháp + cách kiểm chứng |
| dễ bảo trì | một dev mới onboard và sửa được một bug trong ≤ 3 ngày |
| dữ liệu lớn | con số + đơn vị + tốc độ tăng |
Mỗi QAS phải trả lời đủ bốn câu: kịch bản gì · con số bao nhiêu · đo bằng cách nào · ai đo.
Thiếu câu thứ ba là NFR không verify được ⇒ theo D8 nó chỉ là mong muốn.
Quét trước khi nộp: grep -niE "nhanh|ổn định|dễ (bảo trì|dùng)|chịu tải cao|bảo mật$" <file>
D3 — Không có phương án bị loại thì không phải quyết định
Mọi ADR và mọi OPT phải nêu ≥ 1 phương án đã cân nhắc và loại, kèm lý do loại nói được
bằng ràng buộc (CON-nn) hoặc thuộc tính chất lượng (QAS-nnn), không phải bằng sở thích.
❌ "Chọn PostgreSQL vì team quen."
✅ "Chọn PostgreSQL. Loại MongoDB vì QAS-007 yêu cầu giao dịch nhiều bảng nguyên tử; loại
MySQL vì CON-03 (đội vận hành chỉ có kinh nghiệm Postgres) và chênh lệch hiệu năng ở
QAS-004 không đáng kể theo POC-02."
"Team quen" là một lý do hợp lệ — nhưng phải viết ra như một ràng buộc có tên, để sau này biết quyết định này gắn với con người chứ không phải với kỹ thuật.
D4 — Sơ đồ phải khai báo mức, và không trộn mức
Mỗi sơ đồ ghi rõ ở đầu: C4 mức nào (Context / Container / Component / Code) hoặc loại (deployment, sequence, state, data flow). Một sơ đồ có cả "trình duyệt người dùng" lẫn "class OrderValidator" là sơ đồ không ai đọc được.
Kèm mỗi sơ đồ: một legend giải thích hình khối và loại mũi tên (sync/async, ai gọi ai, giao thức). Mũi tên không nhãn không mang thông tin.
Vẽ bằng mermaid, không ASCII art — thống nhất với quy tắc W13 của bộ BA
(../../ba-lifecycle/references/diagram-rules.md). GitHub/Confluence và Artifact của Claude
Code render thẳng, và git diff đọc được từng cạnh.
| Sơ đồ | Loại mermaid | Sinh ở |
|---|---|---|
| C4 Context / Container / Component | flowchart + subgraph |
SAD §3–§5 |
| Deployment view | flowchart TB + subgraph theo vùng/AZ |
SAD §7, INF §3 |
| Luồng chính, có nhánh lỗi | sequenceDiagram |
SAD §6 |
| Ranh giới tin cậy | flowchart LR |
SEC §1 |
| Mô hình dữ liệu khái niệm | erDiagram |
DAT §2 |
| Phụ thuộc giữa mục roadmap | flowchart LR |
TRM §4 |
| Phương án ở mức khối | flowchart LR |
OPT §3 |
🔴 Mỗi sơ đồ phải có bảng đi kèm (W13). Sơ đồ để nhìn; bảng để truy vết, để đặt con số,
và để kiểm chứng. Sơ đồ không diễn đạt được timeout, quyền, mã lỗi, hay ai sở hữu cái gì.
D5 — Mọi interface có chủ, có contract, có chính sách phiên bản
Mỗi dòng trong ICD phải có: IF-nnn · hai đầu · giao thức · sync hay async · ai sở hữu
contract · nơi contract sống (đường dẫn file OpenAPI/AsyncAPI/proto) · chính sách đổi phiên
bản · hành vi khi đầu kia hỏng.
🔴 Interface không biết ai sở hữu là interface sẽ bị đổi mà không ai báo.
D6 — Đường lỗi là bắt buộc cho mọi thứ vượt ranh giới process
Mọi lời gọi ra khỏi process (HTTP, DB, cache, queue, file, hệ thống ngoài) phải ghi: timeout · retry (số lần + backoff + có idempotent không) · hành vi khi hết retry · ảnh hưởng tới người dùng.
Bốn câu hỏi cho mỗi phụ thuộc, không được bỏ câu nào:
- Nó chậm thì sao? (không phải "hỏng" — chậm nguy hiểm hơn hỏng)
- Nó hỏng thì sao? Có degrade được không hay chết cả luồng?
- Nó trả sai dữ liệu thì sao? Có phát hiện được không?
- Nó hồi phục thì sao? Có retry storm không? Có cần backpressure không?
D7 — Mỗi mảnh dữ liệu có đúng một chủ sở hữu
Trong DAT, mỗi thực thể ghi rõ hệ thống nào là single source of truth. Mọi bản sao khác
là read model, phải ghi: cập nhật bằng cơ chế gì, độ trễ tối đa bao nhiêu, và được phép lệch
bao lâu trước khi coi là sự cố.
❌ "Cả hai hệ thống đều lưu thông tin khách hàng và đồng bộ hai chiều." ✅ Chọn một bên làm chủ. Đồng bộ hai chiều không có chủ là cách sinh ra dữ liệu mâu thuẫn không ai gỡ được.
D8 — Ràng buộc không verify tự động được là khuyến nghị, không phải ràng buộc
Mỗi ràng buộc kiến trúc trong AGD phải có một trong hai:
- một fitness function trong
FIT(ArchUnit, dependency-cruiser, lint rule, kiểm thử tải, kiểm tra hạ tầng) chạy trên CI, hoặc - ghi thẳng nhãn
⚠️ Khuyến nghị — không tự kiểm được.
Không có nhãn và không có bài kiểm ⇒ trong sáu tháng nó sẽ bị vi phạm và không ai biết.
D9 — Mọi con số hạ tầng phải quy được ra tiền và ra đơn vị
"Cần 8 node" ⇒ node loại gì, ở vùng nào, bao nhiêu tiền/tháng, ở mức tải nào. TCO và INF
phải khớp nhau; lệch thì TCO sai hoặc INF sai, không có khả năng thứ ba.
Mọi con số ghi kèm nguồn: bài đo nào, POC nào, báo giá nào, ngày nào. Con số không nguồn
là 🔴 giả định, phải hạ Confidence của tài liệu.
D10 — Không quyết định thay người có thẩm quyền
SA trình phương án kèm khuyến nghị. Ba loại quyết định không thuộc SA:
| Loại | Chủ | SA làm gì |
|---|---|---|
| Trade-off nghiệp vụ (chậm hơn nhưng rẻ hơn) | PO | Trình bảng đánh đổi kèm con số |
| Chấp nhận rủi ro bảo mật | Security | Trình threat model + biện pháp + chi phí |
| Ngân sách, tiến độ, nhân sự | PO / PM | Trình TCO và effort |
Gặp chỗ chưa rõ, viết:
> **OQ-021** — Chấp nhận eventual consistency ≤ 5 giây cho số dư ví không?
> **Hỏi:** PO (chị Lan) + Security · **Từ:** 2026-08-29 · **Chặn:** ADR-009, QAS-011
> **Phương án SA đề xuất:** Chấp nhận, vì … *(đề xuất, chưa phải quyết định)*
> **Nếu không chấp nhận:** phải dùng phân tán 2 pha ⇒ +6 tuần, +30% chi phí hạ tầng
Luôn ghi hệ quả của phương án ngược lại bằng con số — đó là thứ giúp người có thẩm quyền quyết được trong một lần đọc.
D11 — Ghi cả cái kiến trúc KHÔNG làm
Cuối mỗi tài liệu, hai mục bắt buộc:
- Ngoài phạm vi — những thứ người đọc có thể tưởng là có: "Không hỗ trợ đa vùng (multi-region) trong phiên bản này; DR dựa trên khôi phục từ backup, RTO 4 giờ."
- Giả định —
ASM-nn, mỗi cái có cách xác minh và hệ quả nếu sai.
Kiến trúc là tập hợp những thứ đã loại nhiều hơn là những thứ đã chọn. Không ghi ra thì người sau tưởng bạn đã cân nhắc.
D12 — Sơ đồ và văn bản mâu thuẫn: chia thẩm quyền rõ ràng
Ghi câu này trong mọi tài liệu có sơ đồ:
Sơ đồ thắng về quan hệ và luồng (ai gọi ai, theo thứ tự nào). Bảng/văn bản thắng về ràng buộc và con số (timeout, quyền, định dạng, giới hạn). Mâu thuẫn ngoài hai loại trên ⇒ là lỗi tài liệu, phải sửa chứ không phải chọn bên.
Checklist tự chấm trước khi nộp bất kỳ tài liệu kiến trúc nào
[ ] D1 Mỗi ADR chỉ chứa một quyết định
[ ] D2 grep NFR định tính trả về rỗng; mọi QAS đủ 4 câu (kịch bản/số/cách đo/ai đo)
[ ] D3 Mọi quyết định nêu ≥1 phương án đã loại + lý do gắn với CON hoặc QAS
[ ] D4 Mỗi sơ đồ khai báo mức C4 + có legend, không trộn mức
[ ] D5 Mọi interface có chủ, contract, versioning policy
[ ] D6 Mọi phụ thuộc ngoài process có timeout/retry/fallback + trả lời 4 câu hỏi
[ ] D7 Mỗi thực thể dữ liệu có đúng một single source of truth
[ ] D8 Mọi ràng buộc có FIT hoặc nhãn "⚠️ Khuyến nghị"
[ ] D9 Mọi con số hạ tầng có đơn vị, nguồn, và quy ra tiền; INF khớp TCO
[ ] D10 Mọi chỗ chưa rõ là OQ kèm hệ quả phương án ngược, không phải quyết định ngầm
[ ] D11 Có mục "Ngoài phạm vi" và mục "Giả định ASM-nn"
[ ] D12 Có câu quy định thẩm quyền sơ đồ vs văn bản
In checklist này dạng bảng ☐/✅ ở cuối mỗi lần chạy skill. Mục chưa đạt ⇒ nói rõ thiếu gì, không được đánh ✅ cho có.