Software Architecture

Documentation là một phần của kiến trúc: Cách viết Architecture Decision Records (ADRs)

7/19/2026 · 11p đọc


title: "Documentation là một phần của kiến trúc: Cách viết Architecture Decision Records (ADRs)"
series: "Software Architecture Masterclass"
part: "Phần 1 — Nguyên lý & Triết lý Kiến trúc"
order: 6
audience: "Solutions Architect & Technical Lead"
reading_time: "13 phút"
tags: ["ADR", "documentation", "architecture-decision-record", "knowledge-management", "technical-leadership"]

Documentation là một phần của kiến trúc: Cách viết Architecture Decision Records (ADRs)

Tháng 3, team quyết định dùng RabbitMQ thay vì Kafka cho hệ thống event nội bộ. Cuộc họp kéo dài 40 phút, ba kỹ sư senior tranh luận, cuối cùng chốt trong một thread Slack: "OK dùng RabbitMQ, đơn giản hơn, team không cần vận hành thêm một cluster phức tạp." Thread đó có 12 reply, rải rác cả lý do kỹ thuật lẫn vài câu đùa không liên quan.

Tháng 10, một kỹ sư mới join, đọc code, thấy RabbitMQ và tự hỏi "sao không dùng Kafka, xử lý throughput tốt hơn nhiều?" Anh ta không tìm ra thread Slack cũ — nó đã trôi qua hàng nghìn tin nhắn khác, kênh cũng đã bị archive sau một lần dọn workspace. Anh ta đề xuất migrate sang Kafka trong một RFC, được duyệt vì không ai trong phòng review nhớ rõ lý do ban đầu là gì. Sáu tháng sau, team phát hiện lại đúng lý do cũ: overhead vận hành Kafka không tương xứng với quy mô traffic thực tế. Chi phí migrate hai chiều, cộng thời gian học lại bài học đã biết, lớn hơn nhiều so với chi phí ghi lại quyết định ban đầu.

Đây không phải là câu chuyện hiếm. Nó là quy luật: kiến trúc là thứ duy nhất trong hệ thống mà quyết định quan trọng hơn code, nhưng lại là thứ ít được ghi chép nhất một cách có hệ thống. Code có Git history. Bug có ticket. Nhưng "tại sao chúng ta chọn thế này" — thứ đắt giá nhất để tái tạo lại khi mất đi — lại thường chỉ tồn tại trong trí nhớ của vài người, và trí nhớ đó rời công ty cùng với họ.

The Problem

Vấn đề cụ thể có ba lớp, không chỉ là "thiếu tài liệu":

Lớp 1 — Mất ngữ cảnh (context loss). Một quyết định kiến trúc luôn được đưa ra trong một bối cảnh cụ thể: ràng buộc về đội ngũ, deadline, ngân sách, mức độ trưởng thành của công nghệ tại thời điểm đó. Khi thời gian trôi qua, bối cảnh biến mất khỏi bộ nhớ tập thể nhưng quyết định (dưới dạng code, cấu hình, kiến trúc) vẫn còn đó. Người đọc sau này chỉ thấy hệ quả, không thấy lý do.

Lớp 2 — Đảo ngược quyết định mù (blind reversal). Kỹ sư mới, hoặc thậm chí kỹ sư cũ đã quên, nhìn thấy một quyết định "trông có vẻ không tối ưu" và đề xuất đổi lại theo hướng đã từng bị bác bỏ — với đúng lý do đã từng được cân nhắc và loại trừ. Đây là vòng lặp lãng phí kinh điển: tổ chức trả tiền hai lần cho cùng một bài học.

Lớp 3 — Tranh cãi lặp lại (re-litigation). Không có bản ghi chính thức, mọi cuộc thảo luận về "tại sao lại thế này" biến thành tranh cãi lại từ đầu, dựa trên trí nhớ không đầy đủ và thiên lệch của từng người tham gia. Người nhớ nhầm, người quên mất ràng buộc gốc, cuộc họp cũ lặp lại gần như nguyên vẹn — tốn thời gian của những người giỏi nhất trong phòng cho việc họ đã làm rồi.

Cả ba lớp này đều là hậu quả của cùng một nguyên nhân gốc: quyết định kiến trúc được coi là sự kiện (event) tức thời, trong khi thực chất nó là tài sản (asset) cần được bảo trì và tra cứu lâu dài. Slack, cuộc họp, email — đều là kênh giao tiếp tối ưu cho tốc độ, không tối ưu cho khả năng tra cứu (retrievability) và tính bền vững (durability) theo thời gian.

The Theory

Giải pháp không phải là "viết tài liệu nhiều hơn" — documentation truyền thống (wiki khổng lồ, design doc 40 trang) thường chết yểu vì chi phí bảo trì cao và không ai đọc. Giải pháp đúng đắn hơn là Architecture Decision Record (ADR) — một pattern documentation nhẹ, có cấu trúc, được Michael Nygard giới thiệu và trở thành chuẩn thực hành phổ biến trong ngành.

Nguyên lý cốt lõi của ADR gồm bốn đặc điểm:

1. Đơn vị nhỏ, tập trung vào MỘT quyết định. Không phải một tài liệu kiến trúc tổng quát, mà mỗi ADR chỉ ghi lại một quyết định cụ thể ("Dùng RabbitMQ cho message queue nội bộ", không phải "Kiến trúc hệ thống messaging"). Điều này giúp ADR ngắn (thường 1-2 trang), dễ viết, dễ đọc, dễ review.

2. Bất biến sau khi chốt (immutable once accepted). Một ADR đã được accept không bị sửa lại để "cập nhật" khi bối cảnh thay đổi. Nếu quyết định cũ không còn phù hợp, ta viết một ADR MỚI đánh dấu supersede ADR cũ — giữ nguyên lịch sử, không xóa dấu vết. Đây là điểm khác biệt lớn nhất so với wiki thông thường: ADR hoạt động giống Git commit hơn là một trang tài liệu sống.

3. Cấu trúc tối thiểu bắt buộc: Context — Decision — Consequences.

  • Context: bối cảnh nào dẫn tới việc phải quyết định — ràng buộc kỹ thuật, tổ chức, thời gian.
  • Decision: quyết định là gì, phát biểu ở thể chủ động, rõ ràng.
  • Consequences: hệ quả — cả tích cực lẫn tiêu cực, không né tránh cái giá phải trả.

4. Đặt cạnh code, không đặt trên wiki tách biệt. ADR nên sống trong repository (docs/adr/ hoặc tương tự), version cùng code, review qua cùng quy trình PR như code. Điều này đảm bảo ADR không "mồ côi" — nó luôn ở gần nơi quyết định được thực thi, và được review bởi đúng người sẽ bị ảnh hưởng.

Về bản chất, ADR biến quyết định kiến trúc từ một sự kiện giao tiếp (communication event, dễ bay hơi) thành một artifact có version, có trạng thái vòng đời, có thể tra cứu — cùng loại tài sản như code, migration file, hay Terraform state.

The Trade-off

Đây là phần phải nói thẳng: viết ADR không miễn phí, và nó không phải "best practice nên làm nếu rảnh".

Cái giá phải trả — cụ thể:

  • Kỷ luật ngay tại thời điểm nóng nhất. Đúng lúc team đang hào hứng chốt quyết định để đi code tiếp, phải dừng lại 20-30 phút viết ADR là ma sát thực sự. Nhiều team bỏ qua bước này chính vì áp lực deadline — và đây là lý do ADR "chết" phổ biến nhất trong thực tế: viết được vài cái đầu rồi bỏ.
  • Overhead cho quyết định nhỏ. Không phải quyết định nào cũng đáng một ADR. Nếu áp dụng quy tắc "mọi thay đổi đều cần ADR" một cách máy móc, team sẽ tạo ra hàng trăm ADR cho những chọn lựa tầm thường (đặt tên biến, format response API nhỏ) — làm loãng tín hiệu, khiến ADR quan trọng bị chôn vùi.
  • Rủi ro ADR lỗi thời nhưng không ai đánh dấu supersede. Nếu kỷ luật "khi đổi quyết định phải viết ADR mới supersede ADR cũ" không được tuân thủ nghiêm, ADR cũ nằm đó với trạng thái "Accepted" trong khi thực tế đã bị thay thế từ lâu — gây hiểu lầm còn tệ hơn không có ADR nào.
  • Không thay thế được trao đổi trực tiếp. ADR ghi lại kết quả và lý do ở mức đủ để tra cứu sau này, nhưng không thể tái hiện toàn bộ sắc thái của một cuộc thảo luận trực tiếp (ai lo ngại điều gì, ai đề xuất phương án nào bị loại). Với quyết định cực kỳ phức tạp, ADR là bản tóm tắt — không phải biên bản đầy đủ.

Cái được — cụ thể, không mơ hồ:

  • Loại bỏ chi phí tranh cãi lại. Khi có kỹ sư mới đặt câu hỏi "sao không dùng X", câu trả lời là một đường link, không phải một cuộc họp 40 phút để dựng lại trí nhớ tập thể. Chi phí tra cứu giảm từ "giờ" xuống "phút".
  • Bảo hiểm chống mất trí nhớ tổ chức khi nhân sự thay đổi. Đây là giá trị lớn nhất về mặt dài hạn: người ra quyết định ban đầu có thể rời công ty, nhưng lý do quyết định thì ở lại. Với tốc độ turnover kỹ sư senior trong ngành, đây không phải rủi ro lý thuyết.
  • Giảm khả năng đảo ngược quyết định mù. Kỹ sư mới đọc ADR trước khi đề xuất thay đổi lớn — nếu đã có ADR ghi rõ lý do loại bỏ phương án X, họ tiết kiệm được thời gian đề xuất lại đúng thứ đã bị bác bỏ (trừ khi bối cảnh đã thực sự đổi, lúc đó ADR mới lại chính là công cụ hợp lệ để lập luận cho sự thay đổi).
  • Tạo văn hóa ra quyết định có trách nhiệm. Biết rằng quyết định sẽ được ghi lại và có thể bị soi lại sau này khiến người ra quyết định cân nhắc kỹ hơn, trình bày lý do rõ ràng hơn — hiệu ứng phụ tích cực không nằm trong mục tiêu ban đầu nhưng có thật.

Tóm gọn phép tính đánh đổi: chi phí là thời gian rời rạc, nhỏ, trả ngay lúc quyết định (20-30 phút/ADR quan trọng); lợi ích là thời gian lớn, không rời rạc, trả dần về sau (tránh vòng lặp tranh cãi, tránh đảo ngược sai lầm, tránh mất kiến thức khi người ra đi). Đây là một khoản đầu tư có lãi suất kép rõ ràng — nhưng chỉ khi kỷ luật viết được duy trì; nếu không, ADR chỉ còn là chi phí không sinh lời.

The Architecture Diagram

Vòng đời của một ADR không tuyến tính đơn giản — nó có nhiều trạng thái và nhánh rẽ phản ánh đúng cách quyết định kiến trúc tiến hóa theo thời gian:

stateDiagram-v2
    [*] --> Proposed: Kỹ sư/Architect soạn ADR nháp

    Proposed --> Accepted: Team review & đồng thuận
    Proposed --> Rejected: Team review & từ chối
    Proposed --> Proposed: Cần thêm thông tin/thảo luận thêm

    Accepted --> Superseded: Bối cảnh thay đổi,\nADR mới thay thế
    Accepted --> Deprecated: Quyết định không còn\nphù hợp nhưng chưa có thay thế

    Rejected --> [*]: Lưu trữ tham khảo\n(tránh đề xuất lại mù quáng)
    Deprecated --> Superseded: Khi có ADR thay thế chính thức

    Superseded --> [*]: Giữ nguyên trong lịch sử,\nlink tới ADR mới

    note right of Accepted
        Trạng thái "đang hiệu lực"
        — team tuân theo quyết định này
    end note

    note right of Superseded
        KHÔNG xóa ADR cũ.
        ADR mới ghi rõ:
        "Supersedes ADR-0012"
    end note

Điểm mấu chốt trong sơ đồ: không có mũi tên nào quay lại từ Superseded hay Rejected về Accepted. Một khi một ADR đã kết thúc vòng đời hiệu lực, nó trở thành hồ sơ lịch sử bất biến. Nếu team muốn quay lại hướng cũ, họ phải viết một ADR hoàn toàn mới, tự giải thích tại sao bối cảnh đã đổi đủ để đảo ngược lần nữa — chứ không phải "sửa" lại ADR cũ.

Implementation Guardrails

1. Dùng đúng template tối thiểu — không thêm mục thừa.

# ADR-0012: Sử dụng RabbitMQ cho message queue nội bộ

## Status
Accepted (2026-03-14)

## Context
- Cần cơ chế async messaging giữa module Order và module Notification.
- Team hiện có 2 kỹ sư, chưa có kinh nghiệm vận hành Kafka cluster.
- Throughput dự kiến: ~500 msg/s ở giai đoạn hiện tại, không có yêu cầu
  stream processing hay retention log dài hạn.

## Decision
Dùng RabbitMQ (qua amqplib) làm message broker cho toàn bộ giao tiếp
async nội bộ giữa các module BE.

## Consequences
Được:
- Vận hành đơn giản hơn, team hiện tại đủ năng lực tự quản trị.
- Độ trễ thấp, phù hợp use-case command/task queue.
Mất:
- Không phù hợp nếu về sau cần replay message hoặc xử lý stream
  throughput cao — sẽ cần đánh giá lại (xem điều kiện supersede).
- Không có tính năng partition/consumer group mạnh như Kafka.

Không thêm "Alternatives Considered" dài dòng, không thêm biểu đồ kiến trúc phức tạp vào chính ADR — nếu cần, link ra tài liệu riêng. ADR phải đọc xong trong dưới 5 phút.

2. Quy tắc cứng: không merge thay đổi kiến trúc lớn nếu chưa có ADR đi kèm trong cùng PR. "Thay đổi kiến trúc lớn" được định nghĩa rõ ràng trước (ví dụ: đổi database engine, đổi message broker, đổi pattern giao tiếp giữa module, đổi framework chính) — không để mơ hồ, nếu không rule này sẽ bị lách. Reviewer có quyền block PR chỉ vì thiếu ADR, kể cả khi code đúng.

3. Đánh số tuần tự, không đánh số theo ngày hay theo module. ADR-0001, ADR-0002... giúp tránh trùng lặp khi nhiều người tạo ADR song song, và giữ được thứ tự thời gian rõ ràng khi liệt kê.

4. Mỗi khi supersede, ADR mới PHẢI trỏ ngược lại ADR cũ và ADR cũ PHẢI được cập nhật trạng thái (không nội dung) thành Superseded by ADR-00XX. Đây là bước hay bị bỏ quên nhất — team viết ADR mới nhưng quên quay lại đánh dấu ADR cũ, khiến hai bản ghi mâu thuẫn nhau tồn tại song song.

Architect's Verdict

ADR là bảo hiểm rẻ nhất bạn có thể mua cho việc mất trí nhớ tổ chức khi nhân sự thay đổi — chi phí một buổi chiều, lợi ích là tránh lặp lại sai lầm đã biết trong nhiều năm tới. Đừng biến nó thành thủ tục hành chính nặng nề: chỉ viết cho quyết định thực sự khó đảo ngược, giữ mỗi ADR dưới một trang, và quan trọng nhất — coi việc viết ADR là một phần không thể tách rời của việc "hoàn thành" quyết định, không phải việc làm thêm sau đó. Một tổ chức không có ADR nào không phải vì họ chưa cần, mà vì họ chưa từng trả giá đủ đau cho việc thiếu nó.

🔗 Bài viết liên quan: Cửa sổ thời gian của kiến trúc · Kiến trúc sư trong môi trường Agile


Bài trước: Cửa sổ thời gian của kiến trúc · Bài tiếp theo: Kiến trúc sư trong môi trường Agile

Documentation là một phần của kiến trúc: Cách viết Architecture Decision Records (ADRs)