用 ADR 记录架构决策:把“为什么”写进仓库

架构决策记录封面

很多技术债不是因为当初的选择一定错了,而是后来的人只看见了“现在这样”,却找不到“当时为什么这样”。ADR(Architecture Decision Record,架构决策记录)要解决的不是写更多文档,而是把关键选择、约束和取舍放进代码仓库,让团队在修改系统前能先理解背景。

ADR 适合记录什么

ADR 不应该变成会议纪要,也不需要覆盖每个实现细节。它适合记录会影响多人协作、部署方式、数据模型、依赖边界或运维成本的选择。判断一个决策要不要写 ADR,可以看它是否满足下面任意一条:

场景 是否建议写 ADR 原因
改用新的消息队列或缓存组件 会影响部署、监控和故障处理
统一接口鉴权方式 涉及多个服务和安全边界
某个页面按钮颜色调整 属于局部 UI 实现
为单个函数增加参数校验 代码本身就能说明意图
放弃一次重构计划 “不做什么”同样需要被记住

如果三个月后有人可能重新争论同一件事,就值得写一份 ADR。

最小模板:短,但要有取舍

ADR 的价值不在篇幅,而在结构。建议每条记录控制在一到两屏内,写清楚背景、决策、备选方案和后果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ADR-0007: 使用事件驱动同步订单状态

## 状态

已采纳

## 背景

订单服务和履约服务之间存在同步调用。高峰期履约服务波动会拖慢下单链路,
并且重试逻辑分散在多个调用方。

## 决策

下单成功后发布订单状态事件,由履约服务异步消费。下单接口只保证事件写入成功,
不等待履约处理完成。

## 备选方案

- 保持同步调用:实现简单,但会继续放大下游故障。
- 定时扫描订单表:解耦程度高,但延迟更大,且容易引入重复处理。

## 后果

- 需要为事件增加幂等键和消费重试。
- 用户看到的履约状态可能短时间滞后。
- 监控要覆盖事件积压、消费失败和死信数量。

注意,“后果”不是只写优点。好的 ADR 会主动暴露代价,这正是后来维护者最需要知道的部分。

决策流程示意

写 ADR 的流程

ADR 可以很轻量,但不能没有流程,否则很容易变成没人执行的静态文件。

建议使用下面这条路径:

  1. 提出者先写草稿,明确问题边界和约束条件。
  2. 在 Pull Request 中提交 ADR,像代码一样接受评审。
  3. 评审者重点看备选方案是否公平、后果是否写全、状态是否明确。
  4. 合并后将状态标为“已采纳”,并在相关代码、配置或运维手册中引用它。
  5. 当现实条件变化时,新建 ADR 替代旧记录,而不是静默修改历史。

第四步最容易被忽略。ADR 应该和实际实现互相指向:代码注释可以引用 ADR-0012,部署文档也可以链接同一条记录。新人读代码时,就能顺着入口找到背景。

状态要表达生命周期

每条 ADR 都应该有明确状态,避免读者猜测它现在还算不算数。常见状态可以保持简单:

状态 含义
提议中 还在讨论,不能作为实现依据
已采纳 团队决定按此执行
已替代 后续 ADR 改变了原决策
已废弃 决策不再适用,但历史保留

不要在旧 ADR 上反复覆盖结论。架构演进本来就是时间序列。如果某个决策被推翻,新建一条 ADR,并在旧记录里写清楚“被 ADR-00xx 替代”。

评审与维护循环

放在仓库里,而不是散在聊天记录里

ADR 最推荐和代码放在同一个仓库中,例如:

1
2
3
4
5
docs/
adr/
0001-use-postgresql-for-core-data.md
0002-split-background-jobs.md
0003-replace-session-storage.md

这样做的好处很直接:评审流程一致,变更可以追踪,实验性决策不会提前污染主线。多仓库系统可以在平台仓库维护跨服务 ADR,再由具体服务引用编号。

文件名建议使用递增编号加英文短标题。编号让引用稳定,英文标题让路径可读。

常见误区

第一,ADR 不是用来证明自己正确,它应该记录当时可见的信息和约束。第二,ADR 不是越多越好;每个小改动都写,团队很快就会停止阅读。第三,线上故障可以先处理,再补写记录说明临时选择和清理计划。

还要避免抽象口号,比如“提升系统可用性”。更好的写法是:“将导出任务从同步 HTTP 请求迁移到后台队列,并把单次导出限制为 100 万行以内。”

落地清单

如果团队从零开始,先在仓库创建 docs/adr/,提交一个最小模板,再要求跨模块或跨团队的技术选择都通过 PR 合并。开始阶段不用追求格式完美,只要能回答四个问题:为什么决策,选择了什么,放弃了什么,代价是什么。

ADR 的目标不是制造文档负担,而是减少重复争论。把“为什么”写进仓库,系统的演进就不再只依赖少数人的记忆。