Đội nào cũng phải ra quyết định. Nhưng phần lớn các đội lại quên mất vì sao mình đã quyết như vậy. Sáu tháng sau, có người hỏi “sao hồi đó mình chọn PostgreSQL mà không chọn MongoDB?”, chẳng ai còn nhớ những ràng buộc lúc ấy, thế là cả cuộc tranh luận lại diễn ra từ đầu, mà thường còn ít người tham gia hơn lần trước.
Tài liệu ra quyết định sinh ra để giải quyết đúng chuyện đó. Gọi là ADR, RFC hay decision log cũng được, cái tên không quan trọng. Quan trọng là ghi lại bối cảnh, các phương án và lập luận ngay tại thời điểm lựa chọn. Năm nay, khi quản lý một đội lập trình viên, tôi càng thấm giá trị của loại tài liệu này hơn bao giờ hết: có nó thì tổ chức có trí nhớ thật, không có thì chỉ còn lại những lời truyền miệng.
Vì sao đáng bỏ công#
Cái lợi dễ thấy nhất là không phải cãi nhau về cùng một chuyện hai lần. “Ơ, chiến lược versioning cho API mình chốt rồi mà nhỉ?” Khi có một bản ghi rõ ràng về điều đã quyết và lý do, chuyện gì đã xong sẽ nằm yên, và cả đội dồn sức vào làm thay vì quay lại đi con đường cũ.
Cái lợi âm thầm hơn thì phải một thời gian mới lộ ra. Dự án kéo dài hàng tháng, hàng năm, người đến rồi người đi, thứ còn lại chỉ là mã nguồn. Mã nguồn cho bạn biết điều gì đã được quyết, nhưng không bao giờ cho biết vì sao. Tài liệu ra quyết định thì giữ lại được lập luận: lúc đó có những ràng buộc gì, phương án nào đã bị loại, ai là người chốt. Người mới vào có bối cảnh để hiểu, thay vì chỉ nghe kể lại. Những quyết định liên quan tới nhiều đội cũng nhận được sự đồng thuận thật, vì ai cũng thấy các yếu tố đã được cân nhắc thế nào, chứ không chỉ thấy kết luận cuối cùng. Và khi tài liệu ghi rõ ai ra quyết định, ai đóng góp ý kiến, thì phần việc tiếp theo sẽ có người thật sự chịu trách nhiệm.
Khi nào nên viết#
Không phải quyết định nào cũng đáng một tài liệu. Tôi chỉ viết khi quyết định đó:
- Tốn kém nếu muốn quay đầu: chọn tech stack (framework, cơ sở dữ liệu, nhà cung cấp cloud), chọn pattern kiến trúc (microservices hay monolith, event-driven hay request-response), chọn nhà cung cấp bên thứ ba, hay cách tiếp cận về bảo mật và tuân thủ
- Có tác động lâu dài: hợp đồng API mà các hệ thống bên ngoài phụ thuộc vào, data model trải qua nhiều service, hoặc những thay đổi phá vỡ các pattern đã thành nếp
- Liên quan tới nhiều đội: bất cứ thứ gì cần nhiều đội hoặc các stakeholder không làm kỹ thuật cùng đồng ý, các quyết định về phân bổ nguồn lực và thứ tự ưu tiên
- Là kết quả của một đợt spike: khi một đợt tìm hiểu có giới hạn thời gian vừa kết thúc, hãy ghi lại những gì tìm được và quyết định rút ra trước khi mọi người quên sạch
Với những quyết định dễ đảo ngược, những chi tiết triển khai nội bộ trong đội, hay những thứ gần như không ảnh hưởng ra ngoài phạm vi trực tiếp của bạn, thì bỏ qua thủ tục. Một tài liệu không ai cần tới thì chỉ là bài tập về nhà.
Vòng đời#
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Draft │ ──► │ Review │ ──► │ Approved │ ──► │ Implemented │
│ Proposed │ │ Feedback │ │ Accepted │ │ Completed │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Rejected/ │
│ Superseded │
└─────────────┘Người viết phác thảo vấn đề và các giải pháp có thể chọn. Sau đó các stakeholder review. Đây là giai đoạn làm nên giá trị của cả quá trình, vì đây là lúc các giả định bị chất vấn và những điểm mù lộ ra. Người ra quyết định (thường là tech lead, kiến trúc sư hoặc product owner) chốt lần cuối, đội bắt tay triển khai, và nếu thực tế buộc phải thay đổi giữa chừng thì tài liệu được cập nhật theo. Có tài liệu chết ngay ở vòng review, có tài liệu về sau bị thay thế khi hoàn cảnh đổi khác. Trường hợp nào cũng phải đánh dấu cho rõ, vì một tài liệu “accepted” đã lỗi thời chính là cái bẫy cho người tiếp theo tìm thấy nó.
Tài liệu thế nào thì đáng đọc#
Bắt đầu từ vấn đề, đừng bắt đầu từ giải pháp bạn thích nhất. Mình đang giải quyết chuyện gì, vì sao lúc này chuyện đó quan trọng, và nếu không làm gì thì sẽ ra sao? Khi mọi người còn chưa thống nhất vấn đề là gì, thì tranh luận về giải pháp chỉ là diễn kịch.
Tiếp theo, đưa ra các phương án thật sự, ít nhất hai hoặc ba, kể cả khi bạn đã nghiêng hẳn về một cái. Mỗi phương án cần có: mô tả ngắn, ưu và nhược điểm, ước lượng công sức sơ bộ (ước theo cỡ áo S/M/L/XL là đủ), cùng các rủi ro và cách bạn sẽ giảm thiểu chúng. Có ghi lại các phương án thay thế thì người đọc sau này mới hiểu vì sao phương án được chọn lại thắng.
Nói thẳng ra những đánh đổi. “Chúng tôi chọn ra thị trường nhanh hơn, chấp nhận kiến trúc không được đẹp.” “Chúng tôi chấp nhận vận hành phức tạp hơn để đổi lấy khả năng scale tốt hơn.” “Chúng tôi ưu tiên trải nghiệm của lập trình viên hơn hiệu năng thuần.” Chỉ vài câu như vậy thôi đã giúp người đọc sau này nhiều hơn cả chục trang phân tích.
Neo cuộc thảo luận vào những thứ cụ thể: đoạn mã minh hoạ giải pháp sẽ trông ra sao, sơ đồ tương tác giữa các hệ thống, link tới các bản proof-of-concept từ những đợt spike. Thảo luận chung chung thì chỉ sinh ra quyết định chung chung.
Cuối cùng, đặt hạn chót: khi nào phải ra quyết định, ai phải góp ý trước ngày nào, và những việc gì đang bị kẹt lại trong lúc chờ. Quyết định mà không có hạn chót thì sẽ bị treo mãi.
Ví dụ: Architecture Decision Record (ADR)#
Dưới đây là một ví dụ thực tế về tài liệu ra quyết định:
# ADR-001: API Authentication Strategy
**Status:** Accepted
**Date:** 2024-11-15
**Decision Maker:** Sarah Chen (Platform Architect)
**Contributors:** Backend Team, Security Team, Mobile Team
## Context
Our public API currently uses API keys for authentication. As we expand to
support third-party integrations and mobile apps, we need a more robust
authentication mechanism that supports:
- Token expiration and refresh
- Scoped permissions
- User-level authentication (not just service-level)
## Decision
We will implement OAuth 2.0 with JWT tokens for API authentication.
## Options Considered
### Option 1: OAuth 2.0 with JWT (Recommended)
**Description:** Industry-standard protocol with self-contained tokens
| Pros | Cons |
|------|------|
| Industry standard, well-documented | More complex initial implementation |
| Self-contained tokens reduce database lookups | Tokens cannot be revoked instantly |
| Broad library support | Requires refresh token management |
**Effort:** Medium (2-3 sprints)
### Option 2: Session-based Authentication
**Description:** Traditional server-side sessions with cookies
| Pros | Cons |
|------|------|
| Simple to implement | Not suitable for mobile apps |
| Easy to revoke sessions | Requires sticky sessions or shared session store |
| Familiar to most developers | Doesn't scale as well |
**Effort:** Small (1 sprint)
### Option 3: Custom Token System
**Description:** Build our own token-based authentication
| Pros | Cons |
|------|------|
| Fully customizable | Reinventing the wheel |
| No external dependencies | Security risks from custom implementation |
**Effort:** Large (4+ sprints)
## Consequences
- Mobile team can implement standard OAuth flows
- We'll need to set up a token refresh mechanism
- API documentation will need updates for OAuth flows
- Existing API key users will need a migration path (6-month deprecation)
## References
- [OAuth 2.0 RFC 6749](https://tools.ietf.org/html/rfc6749)
- Internal spike document: [Authentication Options Spike](/spikes/auth-spike-2024)
- Security team review: SEC-2024-042Template, cứ lấy về mà dùng#
Lấy cái này làm điểm xuất phát, rồi cắt gọt cho vừa với đội của bạn:
| Mục | Nội dung |
|---|---|
| Tên quyết định/vấn đề | [Tiêu đề ngắn gọn, nói rõ nội dung] |
| Trạng thái | [Draft / In Review / Accepted / Rejected / Superseded] |
| Mức độ ảnh hưởng | [Cao / Trung bình / Thấp] |
| Người phụ trách | [Người dẫn dắt quá trình ra quyết định] |
| Người ra quyết định | [Người có quyền quyết cuối cùng] |
| Hạn chót | [Ngày phải ra quyết định] |
Mô tả vấn đề#
Mình đang giải quyết vấn đề gì? Vì sao nó quan trọng? Không làm gì thì phải trả giá thế nào?
Bối cảnh#
Nêu bối cảnh, lịch sử và các chi tiết liên quan. Dẫn link tới những tài liệu liên quan, các quyết định trước đó, hoặc kết quả spike.
Ràng buộc#
Giải pháp nào cũng phải tuân theo những giới hạn hay yêu cầu gì?
- Ràng buộc về ngân sách
- Yêu cầu về tiến độ
- Ràng buộc kỹ thuật (hệ thống hiện có, kỹ năng của đội)
- Yêu cầu về tuân thủ hoặc bảo mật
Các giải pháp đã cân nhắc#
| Phương án | Mô tả | Ưu điểm | Nhược điểm | Công sức | Rủi ro |
|---|---|---|---|---|---|
| Phương án 1 | Mô tả | Ưu A, Ưu B | Nhược A, Nhược B | S/M/L/XL | Thấp/TB/Cao |
| Phương án 2 | Mô tả | Ưu A, Ưu B | Nhược A, Nhược B | S/M/L/XL | Thấp/TB/Cao |
| Phương án 3 | Mô tả | Ưu A, Ưu B | Nhược A, Nhược B | S/M/L/XL | Thấp/TB/Cao |
Đề xuất#
Nêu phương án bạn đề xuất, và tóm tắt vì sao với các ràng buộc và đánh đổi hiện có, đó là lựa chọn tốt nhất.
Quyết định#
Chốt xong thì ghi lại quyết định cuối cùng. Nếu khác với đề xuất, giải thích lý do.
Hệ quả#
Quyết định này kéo theo những gì? Cần làm tiếp những việc gì?
Việc cần làm#
| Việc cần làm | Người phụ trách | Hạn chót |
|---|---|---|
| Việc 1 | Tên | Ngày |
| Việc 2 | Tên | Ngày |
Tài liệu tham khảo#
Link tới các tài liệu liên quan, nguồn bên ngoài, kết quả spike, hoặc các quyết định trước đó.
Làm sao để thói quen này bám rễ#
Lưu tài liệu ở một chỗ cố định, chẳng hạn một thư mục riêng trong repo hay một không gian trên wiki, vì một quyết định không ai tìm thấy thì cũng như chưa từng có. Viết thật gọn nhẹ thôi; tài liệu mà mất cả mấy ngày mới viết xong thì sẽ chẳng bao giờ có ai viết. Nhắc tới chúng trong comment trong mã, commit message và pull request, để phần “vì sao” luôn đi liền với phần “cái gì”. Khi một quyết định bị thay thế, đừng xoá tài liệu cũ, mà hãy đánh dấu nó là superseded rồi link sang tài liệu mới; giữ lại lịch sử mới là mục đích. Và hãy mang các quyết định cũ ra xem lại trong những buổi retro: cái nào vẫn còn đứng vững, cái nào bây giờ bạn sẽ quyết khác đi.
Những kiểu thất bại thì cũng dễ đoán y như vậy. Phân tích mãi mà không quyết được: hãy đặt hạn chót, quyết dựa trên thông tin đang có, và nhớ rằng phần lớn quyết định đều có thể xem xét lại. Review cho có, chỉ để đóng dấu mà không bao giờ có phản biện thật, thì chỉ thêm quy trình chứ không thêm giá trị. Tài liệu viết xong rồi bỏ đó, không dẫn tới hành động nào, còn tệ hơn là không có tài liệu. Quá nhiều chi tiết cũng là một lỗi: tài liệu nên nói về vì sao và cái gì, không phải làm thế nào; phần triển khai là việc của technical spec. Và khi các stakeholder bất đồng, hãy ghi lại ý kiến phản đối. Quyết định có thể vẫn giữ nguyên, nhưng chính lời phản đối đó là bối cảnh mà một năm sau sẽ có người cần tới.
Hãy bắt đầu từ quyết định quan trọng tiếp theo của bạn. Cứ viết ra, dù còn thô, rồi vừa làm vừa chỉnh lại quy trình. Mỗi cuộc họp “sao hồi đó mình chọn X?” không cần diễn ra nữa vì ai đó chỉ việc mở ADR ra đọc, là thêm một tiếng đồng hồ đội bạn lấy lại được.
Đọc thêm#
Bài viết liên quan trên blog này:
- Tìm hiểu về spike trong phát triển phần mềm Agile - làm một đợt spike để thu thập thông tin trước khi chốt quyết định lớn
- Retrospective: sức mạnh của vòng lặp feedback - xem lại các quyết định cũ như một phần của quá trình cải tiến liên tục
- Lên kế hoạch cho một API - áp dụng tư duy tài liệu ra quyết định vào thiết kế API
- Dùng GitHub để quản lý dự án - để tài liệu ra quyết định nằm ngay cạnh mã nguồn
Tài liệu bên ngoài:
- Documenting Architecture Decisions - Michael Nygard - bài viết gốc đã giúp ADR trở nên phổ biến
- ADR GitHub Organization - công cụ, template và ví dụ cho Architecture Decision Record
- Design Docs at Google - cách Google viết tài liệu thiết kế
- Quy trình RFC của Rust - quy trình RFC của ngôn ngữ Rust, được ghi chép rất kỹ
- Template Decision Record của Spotify - góc nhìn của Spotify về chuyện khi nào nên viết ADR

