架構決策記錄 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 能說「為什麼」。