架構決策記錄 ADR#
前面各章反覆強調一件事:架構的本質是取捨——架構特性彼此衝突、架構風格各有強弱、DDD 邊界是權衡的結果。但取捨若沒被記錄下來,半年後沒人記得「當初為什麼這樣決定」,於是有人想推翻、有人不敢動。**架構決策記錄(Architecture Decision Record, ADR)**就是把這些取捨變成輕量、可追溯文件的實踐。
ADR 是一份簡短的文件,記錄「一個有架構意義的決策、它的脈絡、最終選擇,以及由此承擔的後果」。一個決策一份,按時間編號累積,形成系統的「決策歷史」。
三個反模式:ADR 要解決的痛#
ADR 的價值,從它對治的三個常見病症最容易看清。
| 反模式 | 症狀 | ADR 如何解決 |
|---|---|---|
| 拖延決策(Procrastination) | 因怕做錯而遲遲不拍板,系統停在「待定」狀態 | 用 Proposed 狀態把決策攤開逼討論 |
| 忘記理由(Forgetting Why) | 決策做了,但理由沒留,後人不敢動也不知能不能動 | Context + Consequences 永久留存 |
| Email 分散(Scattered) | 理由散落在郵件、聊天、會議記錄,無法檢索 | 集中進版本庫,與程式碼同生命週期 |
最危險的是「忘記理由」。一段看似可以刪掉的奇怪程式碼,背後可能是當年某個慘痛教訓的補丁。沒有 ADR,後人只能在「不敢動」與「動了出事」之間賭運氣。
何謂「有架構意義」(Architecturally Significant)#
不是每個技術決定都值得寫 ADR——否則文件會淹沒在瑣事裡。判準是這個決策是否有架構意義:
- 影響系統的結構(模組劃分、依賴方向、部署單元)。
- 影響關鍵的架構特性(效能、可用性、安全……見第 7 章)。
- 涉及難以逆轉、成本高昂的取捨(選資料庫、選通訊協定、選架構風格)。
- 牽動多個團隊或跨上下文的契約。
一個簡單的篩選問題:「如果以後想反悔,代價大不大?」代價大、影響廣的,就值得一份 ADR;改個按鈕顏色、調個變數名,不必。
ADR 的五個要素#
一份標準 ADR 由五個欄位組成,刻意保持精簡——目標是「五分鐘能寫、兩分鐘能讀」。
| 要素 | 內容 |
|---|---|
| Title(標題) | 編號 + 簡短主旨,例如「ADR-007: 採用事件驅動處理訂單通知」 |
| Status(狀態) | 決策當前的生命週期狀態(見下節) |
| Context(脈絡) | 為什麼現在要做這決策?面臨什麼問題、限制、取捨? |
| Decision(決策) | 我們決定做什麼?用主動語氣明確陳述 |
| Consequences(後果) | 這個決策帶來什麼結果——好的、壞的、需承擔的代價都要寫 |
Consequences最能體現本章與架構特性取捨的呼應:誠實寫下「我們為了 X 犧牲了 Y」。只寫好處的 ADR 是行銷文,不是決策記錄。
Status 生命週期#
決策不是寫完就定案,它有狀態流轉。ADR 採「永不刪除、只標記取代」原則——舊決策即使被推翻也保留,因為它記錄了歷史脈絡。
flowchart LR
Proposed[Proposed<br/>提案中] -->|團隊同意| Accepted[Accepted<br/>已採納]
Proposed -->|否決| Rejected[Rejected<br/>已否決]
Accepted -->|被新決策取代| Superseded[Superseded<br/>已被取代]
Accepted -->|不再適用| Deprecated[Deprecated<br/>已棄用]
style Proposed fill:#fff3e0
style Accepted fill:#e8f5e9
style Superseded fill:#e3f2fd
style Rejected fill:#ffebee
style Deprecated fill:#f3e5f5| 狀態 | 意義 |
|---|---|
| Proposed | 提出待討論,尚未生效 |
| Accepted | 已採納,正式生效 |
| Rejected | 經討論後否決(仍保留,記錄「為何不選」) |
| Superseded | 被某個新的 ADR 取代,註明取代它的編號 |
| Deprecated | 不再適用,但未被特定新決策直接取代 |
當 ADR-012 取代了 ADR-007,做兩件事:把 ADR-007 狀態改為
Superseded by ADR-012,並在 ADR-012 的Context註明它取代了 ADR-007。如此「決策的演化」就有完整鏈路可追,呼應演化原則。
ADR 模板#
放進版本庫(例如 docs/adr/0007-event-driven-order-notification.md),與程式碼同 PR 審查。
# ADR-007: 採用事件驅動處理訂單通知
## Status
Accepted(2026-06-23)
## Context
訂單成立後需通知庫存、物流、簡訊三個下游。
目前同步呼叫導致下單 API 的 P99 延遲達 1.2s,且任一下游故障
即連帶下單失敗。我們優先重視「下單可用性」與「吞吐」,
可接受通知「最終送達」而非「即時送達」。
## Decision
訂單服務在交易成功後發布 OrderPlaced 事件至訊息佇列,
下游各自訂閱消費。下單流程不再同步等待下游回應。
## Consequences
- 正面:下單 P99 降至約 300ms;下游故障不再阻斷下單。
- 負面:引入訊息佇列,增加運維與可觀測性成本。
- 取捨:通知改為最終一致,需處理重複消費與補償(犧牲即時性換可用性)。
- 後續:需另立 ADR 決定佇列選型與冪等策略。為什麼用 Markdown + 版本庫,而非 Wiki?
- 與程式碼同壽命:ADR 隨對應變更進同一個 PR,review 決策與 review 程式碼一起發生。
- 可追溯:
git blame/git log直接看到誰、何時、為何改了決策。 - 不會失聯:Wiki 容易與程式碼脫節、權限走失;版本庫裡的文件跟著倉庫一起搬遷。
- 輕量:純文字、可 diff、可搜尋,沒有工具綁定。
本章小結#
| 主題 | 核心觀點 |
|---|---|
| 三反模式 | 拖延決策、忘記理由、Email 分散——ADR 一次對治 |
| 何時寫 | 只記「有架構意義」的決策:影響結構/關鍵特性/難逆轉 |
| 五要素 | Title、Status、Context、Decision、Consequences |
| 誠實後果 | 必寫犧牲了什麼,呼應特性取捨;只寫好處的不算 ADR |
| 生命週期 | Proposed➡️Accepted➡️Superseded;永不刪除,只標記取代 |
把 ADR 當成寫給「半年後的自己與接手的同事」的信。決策當下的脈絡與取捨理由,是這封信最寶貴的內容——程式碼會說「做了什麼」,只有 ADR 能說「為什麼」。