Menu
ESC

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

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

Đang tải...

Bài 5 — Khi nào viết ADR — Threshold quyết định

Architecture Decision-Making Bài 5/60

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

Ở Bài 4, bạn đã học cách viết một ADR: cấu trúc gồm Context, Decision, Consequences, Status. Nhưng nếu bạn áp dụng máy móc và viết ADR cho mọi quyết định, đội của bạn sẽ nhanh chóng chết ngạt trong giấy tờ. Ngược lại, nếu bạn viết quá ít, những quyết định quan trọng nhất — những thứ định hình hệ thống trong nhiều năm — sẽ trôi tuột đi mà không ai nhớ nổi tại sao nó được quyết như vậy.

Đây chính là bài toán cân bằng mà mọi Tech Lead, Staff Engineer hay Architect đều phải giải: khi nào thì một quyết định xứng đáng được ghi lại thành ADR, và khi nào thì không?

Câu hỏi này nghe có vẻ nhỏ, nhưng nó là ranh giới phân biệt một đội kỹ thuật trưởng thành với một đội hỗn loạn. Một đội viết ADR đúng lúc sẽ có một "bộ nhớ tổ chức" (organizational memory) — người mới vào đọc là hiểu tại sao hệ thống được thiết kế như hiện tại. Một đội không có threshold rõ ràng thì hoặc là bureaucracy (quan liêu giấy tờ), hoặc là amnesia (mất trí nhớ tập thể) — cứ vài tháng lại có người hỏi "tại sao hồi đó mình chọn Kafka nhỉ?" mà không ai trả lời được.

Bài này cho bạn một heuristic threshold — một ngưỡng quyết định đơn giản, dễ nhớ, áp dụng được ngay trong cuộc họp — để trả lời dứt khoát câu hỏi "viết hay không viết". Đây không phải lý thuyết trên trời, mà là công cụ bạn dùng hàng tuần.

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

Heuristic threshold: viết ADR khi thỏa ÍT NHẤT MỘT trong hai điều kiện

Quy tắc gọn đến mức bạn có thể dán lên tường phòng họp:

> Viết ADR khi quyết định thỏa ít nhất MỘT trong hai: > 1. Khó hoặc đắt để đảo ngược (Type 1 decision). > 2. Ảnh hưởng đến nhiều hơn một service hoặc nhiều hơn một team.

Chỉ cần một điều kiện đúng là đủ để cầm bút. Không cần cả hai. Đây là điểm nhiều người hiểu sai — họ nghĩ phải vừa khó đảo ngược vừa ảnh hưởng nhiều team mới viết, thành ra bỏ sót rất nhiều quyết định quan trọng.

Hãy mổ xẻ từng điều kiện.

Điều kiện 1 — Type 1 vs Type 2 decision (khó đảo ngược)

Khái niệm này Jeff Bezos phổ biến trong thư gửi cổ đông Amazon năm 2015, và nó là xương sống của cách suy nghĩ về threshold ADR.

Type 1 (cửa một chiều — one-way door): Quyết định gần như không thể quay đầu, hoặc quay đầu thì cực kỳ tốn kém. Bước qua rồi thì cánh cửa đóng lại sau lưng. Ví dụ:

  • Chọn ngôn ngữ chính cho một microservice mới (viết bằng Go rồi thì đổi sang Java tốn hàng tháng công).
  • Chọn mô hình dữ liệu cốt lõi (schema của bảng orders mà 12 service đang đọc).
  • Quyết định public API contract cho đối tác bên ngoài (một khi khách hàng đã tích hợp, bạn không thể đổi format tùy tiện).
  • Chọn nhà cung cấp cloud primary, hoặc quyết định đưa dữ liệu nhạy cảm ra ngoài biên giới.
Với Type 1, sai lầm rất đắt. Vì thế bạn cần suy nghĩ chậm, cân nhắc kỹ, và ghi lại — chính là ADR.

Type 2 (cửa hai chiều — two-way door): Quyết định dễ đảo ngược. Nếu sai, bạn quay lại và thử cách khác với chi phí thấp. Ví dụ:

  • Đặt tên một biến, một hàm.
  • Chọn thư viện format ngày tháng (đổi lúc nào cũng được).
  • Điều chỉnh mức retry từ 3 lần lên 5 lần.
  • Chọn màu nút bấm, layout của một trang nội bộ.
Với Type 2, viết ADR là lãng phí. Cứ quyết nhanh, thử, sai thì sửa. Bezos nói rất hay: hầu hết tổ chức lớn xử lý mọi quyết định như thể chúng đều là Type 1 — chậm chạp, sợ sai, họp hành liên miên — và điều đó giết chết tốc độ.

Mẹo nhận biết nhanh: hỏi "Nếu 6 tháng nữa quyết định này sai, đảo ngược nó tốn bao nhiêu?" Nếu câu trả lời là "vài giờ" thì Type 2. Nếu là "vài tuần đến vài tháng, và phải điều phối nhiều người" thì Type 1 — viết ADR.

Điều kiện 2 — Blast radius: ảnh hưởng nhiều hơn một service/team

Điều kiện thứ hai không nói về khả năng đảo ngược, mà nói về phạm vi lan tỏa (blast radius) của quyết định.

Một quyết định có thể dễ đảo ngược về mặt kỹ thuật, nhưng nếu nó ảnh hưởng đến nhiều đội, thì bản thân việc phối hợp thay đổi đã là một cơn ác mộng. Ví dụ: bạn định nghĩa một convention chung về format của trace_id trong header để cả hệ thống dùng chung cho observability. Về kỹ thuật, đổi format không khó. Nhưng nếu 8 team đã code theo format cũ, việc đổi đòi hỏi họp hành, thông báo, migration đồng bộ — chi phí phối hợp (coordination cost) khổng lồ.

Khi một quyết định vượt ra ngoài ranh giới của một team hoặc một service, nó trở thành kiến thức chung cần được chia sẻ và bảo tồn. ADR chính là phương tiện làm điều đó. Nó trả lời cho team B, team C — những người không có mặt lúc quyết định — câu hỏi "tại sao lại thế này?".

Dấu hiệu nhận biết: nếu bạn thấy mình sắp phải đi thông báo cho một team khác về một quyết định, hoặc quyết định này sẽ xuất hiện trong tài liệu tích hợp của service khác — đó là tín hiệu viết ADR.

Tại sao chỉ cần MỘT điều kiện?

Bởi vì mỗi điều kiện bắt một loại rủi ro khác nhau:

  • Điều kiện 1 bắt rủi ro thời gian (một mình bạn cũng có thể tạo ra quyết định khó đảo ngược).
  • Điều kiện 2 bắt rủi ro phối hợp và truyền đạt (một quyết định dễ đảo ngược nhưng đông người liên quan vẫn cần được ghi lại).
Nếu bắt buộc cả hai, bạn sẽ bỏ sót quyết định "Type 1 nhưng chỉ ảnh hưởng một service" (ví dụ chọn ngôn ngữ cho một service độc lập) — vốn cực kỳ quan trọng. Vì vậy: một là đủ.

Tình huống thực tế

Ví dụ 1 — Tiki và quyết định chọn database cho service khuyến mãi

Bối cảnh: Một đội 6 kỹ sư ở một sàn thương mại điện tử lớn tại Việt Nam (giả định dựa trên bối cảnh Tiki) xây dựng service "Flash Sale" cho các đợt sale 12/12. Tech Lead phải quyết định: dùng PostgreSQL (đang có sẵn, team quen) hay chuyển sang Redis + một event store để chịu tải ghi cực cao trong 5 phút mở bán.

Áp threshold:

  • Khó đảo ngược? Có. Một khi service đi vào production và 3 team khác (order, payment, notification) đã tích hợp qua API, đổi database nghĩa là viết lại tầng lưu trữ và migration dữ liệu — ước tính 4–6 tuần. → Type 1.
  • Ảnh hưởng nhiều team? Có. Order và Payment phụ thuộc vào tính nhất quán của tồn kho flash sale.
Cả hai điều kiện đều đúng (chỉ cần một là đủ, đây thừa tiêu chuẩn). Đội viết ADR-014 "Chọn kiến trúc lưu trữ cho Flash Sale service", ghi rõ họ chọn Redis với optimistic locking và lý do loại bỏ PostgreSQL thuần (không chịu nổi 30.000 request/giây trong peak).

Bài học: Sáu tháng sau, một kỹ sư mới đề xuất "sao không dùng Postgres cho đơn giản?". Thay vì tranh luận lại từ đầu tốn cả buổi họp, Tech Lead chỉ gửi link ADR-014. Cuộc tranh luận kết thúc trong 5 phút. ADR đã tiết kiệm hàng giờ và bảo vệ quyết định khỏi bị "xét lại" vô ích.

Ví dụ 2 — Startup fintech Singapore và quyết định KHÔNG viết ADR

Bối cảnh: Một fintech giai đoạn seed ở Singapore, đội 4 người. Một kỹ sư băn khoăn có nên viết ADR cho việc chọn thư viện validate dữ liệu form (chọn giữa Zod và Yup cho frontend).

Áp threshold:

  • Khó đảo ngược? Không. Cả hai thư viện đều bọc quanh logic validate. Đổi từ Yup sang Zod trong một codebase nhỏ mất khoảng nửa ngày. → Type 2.
  • Ảnh hưởng nhiều team? Không. Chỉ đội frontend dùng, và đội này có... đúng 2 người.
Không điều kiện nào đúng → không viết ADR. Họ chỉ để lại một comment ngắn trong PR và một dòng trong file README của repo frontend.

Bài học: Đây là mặt còn lại của threshold — biết khi nào không viết cũng quan trọng như biết khi nào viết. Nếu đội non trẻ này viết ADR cho từng lựa chọn thư viện, họ sẽ có 40 ADR trong 2 tháng, không ai đọc, và mất niềm tin vào chính hệ thống ADR. Threshold giúp giữ cho tập ADR tinh gọn và đáng tin — mỗi ADR đều thực sự quan trọng.

Ví dụ 3 — Grab và quyết định "dễ đảo ngược nhưng đông người"

Bối cảnh: Một nền tảng gọi xe khu vực Đông Nam Á (giả định dựa trên Grab) muốn chuẩn hóa cách các service phát ra log lỗi: format JSON chung, các trường bắt buộc (service_name, request_id, severity). Về kỹ thuật, đây chỉ là một convention — mỗi service tự đổi vài dòng code là xong, đảo ngược dễ.

Áp threshold:

  • Khó đảo ngược? Không hẳn. → Nghiêng về Type 2.
  • Ảnh hưởng nhiều team? Cực kỳ. Hơn 20 service thuộc 6 team khác nhau phải tuân theo để hệ thống observability tập trung hoạt động.
Điều kiện 2 đúng → viết ADR, dù điều kiện 1 sai.

Họ viết ADR-231 "Chuẩn structured logging cho toàn bộ backend services", trong đó nêu rõ format bắt buộc và lý do (để công cụ Elasticsearch truy vấn được đồng nhất qua mọi service).

Bài học: Nếu chỉ nhìn tiêu chí "khó đảo ngược", họ đã bỏ qua quyết định này — và hậu quả là mỗi team log một kiểu, dashboard observability vô dụng. Đây chính là lý do threshold có hai điều kiện độc lập, không phải một. Blast radius lớn tự nó là lý do đủ để ghi lại.

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

Khi đứng trước một quyết định kiến trúc và phân vân, hãy chạy qua quy trình 5 bước sau:

Bước 1 — Phát biểu quyết định thành một câu. Ép bản thân viết ra: "Chúng ta đang quyết định [X] thay vì [Y]." Nếu không viết được thành câu rõ ràng, bạn chưa hiểu đủ để đánh giá — quay lại làm rõ vấn đề trước.

Bước 2 — Đặt câu hỏi đảo ngược. "Nếu 6 tháng nữa quyết định này sai, chi phí quay đầu là bao nhiêu — tính bằng giờ, ngày, hay tuần?" Nếu tính bằng tuần trở lên, hoặc cần điều phối nhiều người để quay đầu → đánh dấu Type 1.

Bước 3 — Đo blast radius. "Ngoài đội mình, còn service nào hay team nào bị ảnh hưởng, hoặc phải thay đổi code, hoặc cần được thông báo?" Nếu câu trả lời vượt quá một team/một service → đánh dấu cross-boundary.

Bước 4 — Áp threshold. Nếu Type 1 HOẶC cross-boundary đúng (chỉ cần một) → viết ADR. Nếu cả hai đều sai → không viết; ghi chú ngắn trong PR/README là đủ.

Bước 5 — Nếu ở ranh giới, nghiêng về viết. Có những quyết định lấp lửng. Nguyên tắc: chi phí viết một ADR ngắn (30–45 phút) gần như luôn nhỏ hơn chi phí của việc mất trí nhớ tổ chức sau này. Khi phân vân 50-50, hãy viết. Nhưng viết ngắn — đừng biến nó thành luận văn.

Một mẹo tổ chức: đưa hẳn câu hỏi "Quyết định này có cần ADR không?" vào checklist của Definition of Done cho các task thiết kế, và vào template của architecture review (bạn sẽ học sâu hơn ở Bài 57).

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

Lỗi 1 — Viết ADR cho mọi thứ (bureaucracy). Đội mới biết đến ADR thường phấn khích và viết cho cả những quyết định Type 2 tầm thường. Kết quả: tập ADR phình to, không ai đọc, và người ta bắt đầu coi ADR là gánh nặng hành chính. Mẹo: Bám chặt threshold. Nếu một ADR không thỏa điều kiện nào, xóa nó đi.

Lỗi 2 — Không viết gì cả (amnesia). Đối cực ngược lại: "Tụi mình là startup, chạy nhanh, không có thời gian viết tài liệu." Sáu tháng sau không ai nhớ tại sao chọn kiến trúc hiện tại, mỗi quyết định cũ bị đem ra tranh luận lại. Mẹo: Chỉ cần bắt đúng nhóm quyết định Type 1 hoặc cross-boundary là bạn đã bảo vệ được 90% giá trị. Không cần nhiều — cần đúng.

Lỗi 3 — Nhầm "quyết định lớn" với "quyết định nhiều dòng code". Một quyết định có thể sửa rất ít code nhưng vẫn là Type 1 (ví dụ: đổi một dòng cấu hình để bật chế độ eventual consistency cho cả cụm). Ngược lại, một task viết 2000 dòng code có thể hoàn toàn là Type 2. Mẹo: Đánh giá theo khả năng đảo ngượcblast radius, không phải theo khối lượng code.

Lỗi 4 — Đợi quyết định "chín muồi" rồi mới viết. Nhiều người trì hoãn viết ADR đến khi mọi thứ đã xong, và rồi... không bao giờ viết. Mẹo: Viết ADR ở trạng thái "Proposed" trước khi thực thi, để nó trở thành công cụ ra quyết định chứ không phải biên bản khảo cổ.

Lỗi 5 — Quên rằng "không viết" cũng là một quyết định hợp lệ. Threshold cho phép bạn tự tin nói "không cần ADR". Mẹo: Khi ai đó hỏi "cái này có cần ADR không?", hãy trả lời bằng threshold, không bằng cảm tính. Điều đó tạo ra văn hóa nhất quán trong đội.

Mẹo vàng — Timebox ADR. Đặt giới hạn: một ADR chuẩn nên viết xong trong 30–60 phút và dài không quá 1–2 trang. Nếu bạn cần cả ngày, có thể bạn đang cố nhồi phần Trade-off Analysis phức tạp vào ADR — hãy tách nghiên cứu đó ra thành một Tech Spike (Bài 58) và để ADR chỉ ghi kết luận.

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

Bài tập 1 — Phân loại (15 phút). Với mỗi quyết định dưới đây, ghi: (a) Type 1 hay Type 2, (b) blast radius một team hay nhiều team, (c) kết luận VIẾT hay KHÔNG VIẾT ADR, và một câu lý do.

  • Chọn giữa dùng snake_case hay camelCase cho field trong public REST API mà 3 đối tác đã tích hợp.
  • Đổi thư viện logging nội bộ trong một CLI tool chỉ đội DevOps dùng.
  • Quyết định tách module payment khỏi monolith thành service riêng.
  • Chọn font chữ cho trang admin nội bộ.
  • Áp dụng chuẩn health-check endpoint /healthz cho toàn bộ 15 microservice.
(Gợi ý đáp án: 1-VIẾT, 2-KHÔNG, 3-VIẾT, 4-KHÔNG, 5-VIẾT. Hãy tự lý giải theo threshold trước khi so.)

Bài tập 2 — Audit thực tế (30 phút). Lấy 5 quyết định kỹ thuật gần nhất trong dự án của bạn (nhìn lại git log, các PR lớn, hoặc ghi chú họp). Với mỗi cái, chạy quy trình 5 bước. Bao nhiêu cái đáng lẽ phải có ADR mà không có? Bao nhiêu cái nếu áp threshold thì không cần viết? Viết retro-ADR (ADR viết hồi cứu) cho một quyết định Type 1 quan trọng đang thiếu.

Bài tập 3 — Thiết kế checklist cho đội (20 phút). Soạn một checklist 3 dòng để dán vào template review của đội bạn, giúp bất kỳ ai cũng tự trả lời được "quyết định này có cần ADR không?". Chia sẻ với ít nhất một đồng nghiệp và xin phản hồi.

Tóm tắt

  • Không phải mọi quyết định đều xứng đáng có ADR. Threshold giúp bạn tránh cả hai cực: quan liêu giấy tờ và mất trí nhớ tập thể.
  • Quy tắc vàng: Viết ADR khi quyết định thỏa ít nhất MỘT trong hai — (1) khó/đắt để đảo ngược (Type 1 decision), hoặc (2) ảnh hưởng nhiều hơn một service/team.
  • Chỉ cần một điều kiện là đủ, vì mỗi điều kiện bắt một loại rủi ro khác nhau: rủi ro thời gian và rủi ro phối hợp.
  • Type 1 (cửa một chiều) cần suy nghĩ chậm và ghi lại; Type 2 (cửa hai chiều) cứ quyết nhanh, sai thì sửa.
  • Blast radius lớn tự nó là lý do đủ để viết ADR, ngay cả khi quyết định dễ đảo ngược về mặt kỹ thuật (ví dụ chuẩn logging của Grab).
  • Khi phân vân ở ranh giới, nghiêng về viết — nhưng viết ngắn, timebox trong 30–60 phút.
  • "Không viết ADR" cũng là một quyết định hợp lệ và nên được đưa ra bằng threshold, không bằng cảm tính.
Ở Bài 6, chúng ta sẽ đi sâu vào phần bên trong của một quyết định lớn: khung tư duy Trade-off Analysis — cách so sánh các phương án một cách có hệ thống trước khi bạn chốt và ghi vào ADR.