Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 11 — ADR (Architecture Decision Records)

Communication Skills cho Tech Teams Bài 11/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa join một team backend ở một công ty fintech tại TP.HCM. Ngày thứ ba, bạn mở codebase và thấy hệ thống dùng MongoDB cho phần lưu giao dịch — điều mà bất kỳ kỹ sư nào cũng thấy "sai sai", vì giao dịch tài chính cần ACID, cần transaction mạnh, thứ mà một database quan hệ như PostgreSQL làm tốt hơn nhiều. Bạn hỏi anh tech lead: "Sao lại chọn Mongo cho phần này?". Anh gãi đầu: "Ừm, hồi đó anh chưa vào team, để anh hỏi lại". Ba người được hỏi, ba câu trả lời khác nhau. Không ai nhớ chính xác lý do. Và thế là team mất nguyên một buổi họp để tranh luận về một quyết định đã đưa ra từ hai năm trước — một cuộc tranh luận mà lẽ ra không cần xảy ra.

Đây chính là "nỗi đau" mà ADR (Architecture Decision Record) ra đời để giải quyết. Trong một team tech, quyết định kiến trúc được đưa ra liên tục: chọn database nào, dùng REST hay gRPC, tách microservice hay giữ monolith, dùng thư viện auth nào... Mỗi quyết định đều có bối cảnh, có sự đánh đổi, có lý do. Nhưng bối cảnh đó bay hơi rất nhanh — sau vài tháng, sau khi người ra quyết định nghỉ việc, sau khi team scale từ 5 lên 30 người. Khi context mất đi, quyết định trông giống như một sai lầm ngẫu nhiên, và người mới sẽ hoặc là mù quáng làm theo, hoặc là bốc đồng đập đi làm lại — cả hai đều tốn kém.

ADR là một cách viết cực kỳ đơn giản nhưng hiệu quả để "đóng băng" bối cảnh của một quyết định vào thời điểm nó được đưa ra. Trong bài này, chúng ta sẽ học cách viết ADR đúng chuẩn, khi nào nên viết, và làm sao để nó thực sự được đọc chứ không nằm chết trong repo.

Khái niệm cốt lõi

ADR là gì?

Architecture Decision Record là một tài liệu ngắn (thường 1–2 trang) ghi lại một quyết định kiến trúc quan trọng: bối cảnh dẫn đến quyết định, các phương án đã cân nhắc, quyết định cuối cùng, và hệ quả của nó. Khái niệm này được Michael Nygard phổ biến năm 2011 và giờ đã trở thành thực hành phổ biến ở các công ty như GitHub, Spotify, và cả các engineering team ở Grab, Tiki, VNG.

Ba đặc điểm định nghĩa nên một ADR:

Một record cho một quyết định. Bạn không gom mười quyết định vào một tài liệu. Mỗi ADR là một file riêng, đánh số tuần tự: 0001-use-postgresql-for-transactions.md, 0002-adopt-grpc-for-internal-services.md... Cách này giúp mỗi quyết định có thể được tham chiếu, tranh luận và thay thế độc lập.

Ngắn và tập trung vào "tại sao". ADR không phải là tài liệu kỹ thuật chi tiết. Nó không cần sơ đồ 20 trang. Nó trả lời câu hỏi quan trọng nhất: "Vào thời điểm đó, chúng ta biết gì và tại sao chúng ta chọn thế này?". Phần giá trị nhất của ADR là contextconsequences — không phải bản thân quyết định.

Immutable (bất biến) sau khi được chấp nhận. Đây là điểm nhiều người hiểu sai. Một khi ADR đã được "Accepted", bạn không sửa nó nữa. Nếu sau này quyết định thay đổi, bạn không quay lại xoá ADR cũ — bạn viết một ADR mới đánh dấu ADR cũ là "Superseded" (bị thay thế). Lý do: ADR là bản ghi lịch sử. Nó phản ánh những gì bạn biết tại thời điểm đó. Sửa ADR cũ đồng nghĩa với xoá lịch sử, và lịch sử chính là thứ giá trị nhất.

Cấu trúc chuẩn của một ADR

Một ADR theo template Nygard gồm các phần sau:

  • Title (Tiêu đề): ngắn gọn, dạng câu quyết định. Ví dụ: "Sử dụng PostgreSQL cho dịch vụ thanh toán".
  • Status (Trạng thái): Proposed → Accepted → Superseded/Deprecated. Đây là vòng đời của quyết định.
  • Context (Bối cảnh): Điều gì đang xảy ra? Vấn đề gì cần giải quyết? Ràng buộc nào đang tồn tại (deadline, ngân sách, kỹ năng team, quy mô traffic)? Đây là phần dài nhất và quan trọng nhất.
  • Decision (Quyết định): Chúng ta quyết định làm gì. Viết ở thể chủ động, dứt khoát: "Chúng ta sẽ dùng...".
  • Consequences (Hệ quả): Sau quyết định này, điều gì trở nên dễ hơn, khó hơn, hoặc rủi ro hơn? Cả tích cực lẫn tiêu cực — trung thực về cái giá phải trả.
Nhiều team hiện đại bổ sung thêm phần Alternatives considered (Các phương án đã cân nhắc) — liệt kê những lựa chọn khác và lý do bị loại. Phần này cực kỳ giá trị vì nó ngăn team tương lai "phát minh lại" một phương án đã bị bác bỏ.

ADR khác gì với RFC và tài liệu kiến trúc thông thường?

Ở bài trước bạn đã học về RFC — tài liệu để đề xuất và thảo luận một thay đổi trước khi đồng thuận. ADR khác ở chỗ nó tập trung vào việc ghi lại một quyết định đã (hoặc sắp) được chốt. RFC thường dài hơn, có tính khám phá, thảo luận nhiều phương án; ADR là kết tinh cô đọng của quyết định cuối. Trên thực tế, nhiều team dùng RFC để bàn bạc, rồi khi chốt thì viết một ADR ngắn tham chiếu về RFC đó. RFC là cuộc thảo luận, ADR là biên bản kết luận.

Tình huống thực tế

Tình huống 1: Startup e-commerce và cuộc "thánh chiến" REST vs gRPC

Một startup thương mại điện tử ở Hà Nội, team 12 engineer, đang tách monolith thành các service nhỏ. Hai phe hình thành: một phe muốn giao tiếp giữa các service bằng REST/JSON (quen thuộc, dễ debug), phe kia muốn gRPC (nhanh hơn, có contract rõ ràng). Cuộc tranh luận trên Slack kéo dài ba ngày, không ai chịu ai.

Tech lead quyết định dừng lại và viết một ADR: 0007-use-grpc-for-internal-service-communication.md. Trong phần Context, anh ghi rõ: team có 4 service nội bộ gọi nhau với tần suất cao (trung bình 800 request/giây vào giờ cao điểm), độ trễ đang là vấn đề, và team đã có kinh nghiệm với Protobuf. Phần Alternatives, anh liệt kê REST/JSON và lý do loại (overhead serialize lớn hơn, không có contract enforce ở compile-time). Phần Decision: dùng gRPC cho giao tiếp nội bộ, giữ REST cho API public mà mobile/web gọi vào. Phần Consequences: thừa nhận rõ ràng nhược điểm — gRPC khó debug bằng curl hơn, cần training cho junior, và tooling ở VN chưa nhiều tài liệu tiếng Việt.

Bài học: ADR biến một cuộc cãi vã cảm tính thành một quyết định có tài liệu. Điểm mấu chốt là câu cuối anh viết: "Chúng ta chọn gRPC cho nội bộ, không phải mọi nơi". Sáu tháng sau, khi một engineer mới đề xuất "chuyển hết sang gRPC kể cả public API", team chỉ cần chỉ vào ADR 0007 — quyết định đã có ranh giới rõ ràng, không cần tranh luận lại từ đầu.

Tình huống 2: Fintech và ADR "Superseded" — khi quyết định thay đổi đúng cách

Quay lại công ty fintech ở đầu bài. Sau sự cố "không ai nhớ tại sao dùng Mongo", CTO yêu cầu team áp dụng ADR. Họ viết hồi tố (backfill) một ADR: 0003-use-mongodb-for-transaction-storage.md, status "Accepted". Trong Context, họ ghi trung thực những gì tái dựng được: hồi đó team mới 3 người, chọn Mongo vì đội cũ quen NoSQL và cần ra MVP trong 6 tuần để kịp gọi vốn.

Một năm sau, khi hệ thống xử lý hàng trăm nghìn giao dịch/ngày và bài toán consistency trở nên nghiêm trọng, team quyết định migrate sang PostgreSQL. Họ không sửa ADR 0003. Thay vào đó họ viết 0015-migrate-transaction-storage-to-postgresql.md, status "Accepted", và cập nhật status của 0003 thành "Superseded by 0015". ADR mới ghi rõ trong Context: "Quyết định 0003 hoàn toàn hợp lý ở thời điểm MVP; giờ ràng buộc đã đổi — chúng ta có transaction volume lớn, yêu cầu tuân thủ ngân hàng nhà nước về audit trail, và team đã có DBA".

Bài học: Đây là ví dụ mẫu mực về tính immutable. ADR 0003 không phải "sai" — nó đúng ở thời điểm của nó. Việc giữ nguyên nó và thêm ADR mới cho phép người đọc thấy được cả hành trình tư duy của team. Người mới join sẽ hiểu: "À, quyết định ban đầu là đánh đổi có chủ đích vì tốc độ, không phải sự cẩu thả". Đó là sự khác biệt giữa một team trưởng thành và một team hỗn loạn.

Tình huống 3: Team remote và ADR như công cụ đồng bộ bất đồng bộ

Một team engineering phân tán, có người ở Đà Nẵng, người ở Singapore, người làm remote ở Bangkok. Chênh lệch múi giờ khiến họp đồng bộ rất khó. Họ quyết định chọn thư viện quản lý state cho frontend: Redux Toolkit hay Zustand.

Thay vì đợi một cuộc họp mà cả ba múi giờ đều thức, một engineer viết ADR ở trạng thái "Proposed", đẩy lên repo dưới dạng Pull Request. Mọi người review ADR như review code — comment thẳng vào từng dòng: "Zustand nhẹ hơn nhưng ecosystem middleware nghèo hơn", "Team đã có 2 dự án Redux, chi phí học lại là 0". Sau ba ngày, đủ ý kiến, họ merge PR và đổi status sang "Accepted".

Bài học: ADR không chỉ là tài liệu lưu trữ — nó là một cơ chế ra quyết định bất đồng bộ. Với team remote xuyên múi giờ (chủ đề bạn sẽ học sâu hơn ở các bài sau), việc để ADR ở trạng thái "Proposed" và review qua PR cho phép mọi người đóng góp mà không cần cùng online. Quyết định trở nên minh bạch, có dấu vết ai đồng ý điều gì, và không ai bị bỏ lại khỏi cuộc thảo luận chỉ vì ngủ khi cả team họp.

Hướng dẫn từng bước

Đây là quy trình cụ thể để bắt đầu dùng ADR trong team của bạn:

Bước 1 — Tạo thư mục và chọn template. Tạo thư mục docs/adr/ (hoặc docs/decisions/) ngay trong repo, để ADR sống cạnh code, không nằm ở Confluence hay Notion tách biệt. Chọn một template — template Nygard là điểm khởi đầu tốt nhất cho người mới. Bạn có thể dùng công cụ adr-tools (CLI) để tự động tạo file đánh số, nhưng ban đầu chỉ cần một file template.md copy tay là đủ.

Bước 2 — Nhận diện thời điểm cần viết ADR. Không phải quyết định nào cũng cần ADR. Hãy hỏi: "Nếu người mới join sau 6 tháng thấy điều này, họ có thắc mắc 'tại sao lại làm thế' không?". Nếu có, hãy viết ADR. Các dấu hiệu điển hình: chọn/đổi database, chọn giao thức giao tiếp, tách/gộp service, chọn framework hoặc thư viện cốt lõi, quyết định về xác thực/bảo mật, hay bất kỳ đánh đổi nào khó đảo ngược.

Bước 3 — Viết ở trạng thái "Proposed". Đừng chờ đến khi mọi thứ hoàn hảo. Viết nháp với status "Proposed", điền Context trước — đây là phần bạn hiểu rõ nhất ngay lúc này. Viết Context như thể giải thích cho một đồng nghiệp thông minh nhưng chưa biết gì về dự án.

Bước 4 — Đưa ra review qua Pull Request. Đẩy ADR lên như một PR để cả team comment. Đây là nơi các phương án được thách thức, các hệ quả bị bỏ sót được chỉ ra. Mục tiêu không phải là đồng thuận tuyệt đối, mà là mọi tiếng nói được lắng nghe và người ra quyết định (thường là tech lead hoặc người đề xuất) chốt cuối cùng.

Bước 5 — Chốt status "Accepted" và merge. Khi đã đủ ý kiến, đổi status sang "Accepted", ghi ngày tháng và tên người quyết định, rồi merge. Từ giây phút này, ADR trở thành bất biến.

Bước 6 — Khi quyết định thay đổi, viết ADR mới. Không bao giờ sửa ADR đã accepted để "cập nhật". Viết ADR mới, tham chiếu ngược, và đánh dấu cái cũ là "Superseded by 00XX".

Lỗi thường gặp & mẹo

Lỗi 1: Viết ADR quá dài như một luận văn. ADR mà dài 15 trang thì không ai đọc, và bạn cũng không bao giờ viết cái thứ hai. Giữ nó dưới 2 trang. Nếu cần chi tiết kỹ thuật sâu, link tới tài liệu riêng. Nhớ: giá trị nằm ở "tại sao", không phải "như thế nào".

Lỗi 2: Chỉ ghi quyết định, quên phần Context và Consequences. Một ADR chỉ nói "Chúng ta dùng Kafka" là vô dụng — đọc code cũng biết. Điều code không nói cho bạn là tại saođánh đổi gì. Nếu bạn chỉ có thời gian viết một phần, hãy viết Context thật kỹ.

Lỗi 3: Sửa ADR cũ khi quyết định thay đổi. Đây là lỗi phá vỡ toàn bộ giá trị của ADR. Immutable nghĩa là bất biến. Luôn viết mới và đánh dấu Superseded.

Lỗi 4: Chỉ có tích cực trong phần Consequences. Nếu ADR nào cũng chỉ toàn ưu điểm, đó là dấu hiệu bạn đang bán hàng chứ không ghi chép trung thực. Mọi quyết định kiến trúc đều có cái giá. Ghi rõ nhược điểm giúp team tương lai biết trước rủi ro và không ngạc nhiên.

Mẹo 1 — Backfill có chọn lọc. Nếu team chưa từng dùng ADR, đừng cố viết lại lịch sử toàn bộ. Chỉ backfill vài quyết định lớn nhất, gây tranh cãi nhất mà mọi người vẫn còn nhớ context.

Mẹo 2 — Để ADR sống trong repo, review như code. ADR đặt trong Confluence thường bị lãng quên. Đặt cạnh code, review qua PR, nó trở thành một phần văn hoá kỹ thuật tự nhiên.

Mẹo 3 — Viết Context ở thì hiện tại của thời điểm đó. Đừng viết "hồi đó chúng ta nghĩ". Viết như thể bạn đang ở chính thời điểm ra quyết định: "Hệ thống hiện xử lý 500 req/s và đang chạm giới hạn". Điều này giữ cho bản ghi trung thực với bối cảnh gốc.

Bài tập thực hành

Bài 1 — Viết một ADR hồi tố. Chọn một quyết định kiến trúc trong dự án hiện tại của bạn (hoặc một dự án cá nhân) mà bạn thấy "có lý do đằng sau". Viết một ADR đầy đủ theo template Nygard: Title, Status, Context, Decision, Consequences, Alternatives. Giới hạn 2 trang. Đặc biệt tập trung viết phần Context thật rõ, như thể giải thích cho người mới.

Bài 2 — Thực hành vòng đời Superseded. Lấy ADR bạn vừa viết ở Bài 1. Giả định một tình huống khiến quyết định đó cần thay đổi sau 1 năm (traffic tăng, team scale, ràng buộc pháp lý mới). Viết một ADR thứ hai thay thế nó, tham chiếu ngược, và cập nhật status của ADR đầu thành "Superseded". Chú ý: không sửa nội dung ADR đầu, chỉ đổi status.

Bài 3 — Review nhóm. Nếu bạn đang làm trong một team, đề xuất viết một ADR cho quyết định kỹ thuật sắp tới, đặt status "Proposed", và mời hai đồng nghiệp review qua PR hoặc comment. Quan sát xem cuộc thảo luận có rõ ràng và ít cảm tính hơn so với tranh luận miệng không.

Tóm tắt

ADR là công cụ đơn giản đến bất ngờ nhưng có sức mạnh lớn: nó "đóng băng" bối cảnh của một quyết định kiến trúc trước khi bối cảnh đó bay hơi. Ba nguyên tắc cần nhớ: một record cho một quyết định, ngắn và tập trung vào tại sao, và bất biến sau khi accepted — khi quyết định đổi, viết mới và đánh dấu Superseded chứ không sửa cũ.

Cấu trúc cốt lõi gồm Status, Context, Decision, Consequences (và Alternatives nếu có), trong đó Context và Consequences là hai phần giá trị nhất — vì code đã kể cho bạn nghe quyết định là gì, nhưng chỉ ADR mới kể được tại saođánh đổi gì. Hãy để ADR sống trong repo, review như code, và bạn sẽ tiết kiệm cho team tương lai vô số giờ tranh luận về những quyết định đã có lời giải từ lâu. Một team ghi lại lý do của mình là một team học được từ chính lịch sử của nó — thay vì lặp lại nó.