架构决策记录(ADR,Architecture Decision Record)是团队把关键技术选型「写下来、说清楚、留得住」的轻量实践。很多项目不是败在技术选型本身,而是败在「为什么当初选了它」这句话三个月后没人答得上来。本文用一套可直接照抄的模板,带你把散落在聊天记录里的技术决策,沉淀成团队资产。
一、为什么团队需要 ADR
新人和老人对同一个系统往往有完全不同的理解。老人记得「当年为了赶上线,数据库先用了 MySQL」,新人只看到「为什么不用 PostgreSQL」。当老人离职、聊天记录被清理,决策的来龙去脉就彻底丢失,后人只能在猜测中维护一套自己并不理解的架构。
更隐蔽的代价是重复讨论。没有书面记录,同一个「要不要引入消息队列」的话题会在不同会议里被反复提起,每次都从零开始,每次结论都可能不一致。ADR 的价值不在于文档本身,而在于它强制团队在某个时点做出显式决策,并把这个决策冻结下来。
一个真实场景:某团队在创业初期为了快,选了单机 SQLite 扛全部业务。半年后流量上来,大家才意识到该迁移。但没人说得清「当初为什么不用 MySQL」——是评估过觉得没必要,还是单纯图省事?这个信息不对称让迁移争论了整整两周。如果当初有一篇 ADR 写明「当前 QPS 预计 < 50,SQLite 足够,半年后复核」,后续决策就会顺畅得多。ADR 把「上下文」和「决策」绑在一起,后人看到的不是一句孤零零的结论,而是得出结论时的全部前提。
二、什么是架构决策记录
ADR 是一篇短小、不可变、带状态的 Markdown 文档,描述「我们在一个具体上下文中,面对一个具体问题,做出了什么决策,以及为什么」。它的核心约束有三条:只记录有影响的决策(不要写「今天把变量名改了」这种琐事);一旦接受就不可修改(发现错了就写新的 ADR 来取代,而不是偷偷改旧文档);决策可逆但痕迹永久(你能翻回去看清每一步演变)。
它和常规设计文档的区别在于「轻」。一份 ADR 通常 200–500 字就能说清,不要求画图、不要求评审会,一个 Pull Request 就能合并。这种低门槛,正是它能真正活下来的关键。
三、一个标准 ADR 模板
下面是可以直接复制到仓库的模板。把占位符替换掉即可,不需要额外工具:
# ADR-001 引入 PostgreSQL 作为主数据库
## 状态
Accepted(已接受)
## 背景
我们需要支持 JSON 半结构化字段、复杂联表查询,以及未来可能的地理空间查询。
当前团队以关系型经验为主,NoSQL 运维能力不足。
## 决策
选用 PostgreSQL 15 作为核心业务主库,JSONB 存储可变结构,
PostGIS 预留空间查询能力。Redis 仅作缓存层,不承载主数据。
## 后果
正面:事务完整、生态成熟、招聘容易;
负面:分库分表需自行规划,超大规模写需额外评估。
## 备选方案
- MySQL 8:JSON 支持弱于 PG,放弃;
- MongoDB:团队缺运维经验,放弃;
- TiDB:成本与复杂度偏高,暂不引入。
四、ADR 的状态流转
每篇 ADR 必须有一个明确状态,让读者一眼看出它此刻是否有效。状态不是摆设,它决定了后人要不要照着做:
| 状态 | 含义 | 后人该如何对待 |
| Proposed | 提出待评审 | 可以讨论,暂未生效 |
| Accepted | 已接受生效 | 按此执行,新代码须对齐 |
| Deprecated | 已废弃 | 不再推荐,但历史原因保留 |
| Superseded | 被取代 | 注明被哪篇 ADR 取代 |
关键是 Superseded:当你推翻旧决策,永远写新 ADR 去取代它,并在旧文档顶部加一行「本 ADR 已被 ADR-00X 取代」。这样架构演进是一条可追溯的链,而不是一堆互相矛盾的孤本。
五、从 0 到 1 落地:目录与命名
把 ADR 放在仓库根目录的 docs/adr/ 下,用连续编号命名,降低决策顺序的歧义。不要按日期命名,日期看不出先后逻辑:
docs/adr/
├── 0001-record-architecture-decisions.md # 元 ADR:规定我们采用 ADR 实践
├── 0002-use-postgresql-as-primary-db.md
├── 0003-introduce-kafka-for-async-tasks.md
└── 0004-adopt-react-for-admin-console.md
第 0001 篇建议固定为「我们决定采用 ADR 这种实践本身」,相当于给这套方法立个户头。后续每加一篇,编号 +1,文件名用短横线连写的英文摘要,方便grep。
六、三个真实场景示例
光讲模板太空,看三个落地案例。
# ADR-0003 引入 Kafka 处理异步任务
## 状态
Accepted
## 背景
订单创建后需要发短信、更新统计、通知仓储,同步串行使接口 RT 从 80ms 涨到 1.2s。
## 决策
引入 Kafka,将非核心链路改为事件驱动;核心写仍走主库事务。
## 后果
RT 回落到 120ms;代价是引入了消息幂等、消费延迟监控的复杂度。
# ADR-0004 管理后台采用 React
## 状态
Accepted
## 背景
内部管理系统需要快速迭代表单与表格,团队已有 React 经验。
## 决策
管理后台统一用 React + TypeScript,与 C 端小程序技术栈解耦。
## 后果
开发效率高;但需注意与小程序(Vue)团队的知识不互通。
七、ADR 与代码评审、技术债的关系
ADR 不是孤立动作。它和 结对编程 一样,都是把「隐性判断」变成「团队共识」的手段——只不过一个发生在写代码时,一个发生在做选型时。决策不合理时,它就是 技术债 的来源;而把债写清楚,至少让后人知道「这是故意欠的,还是无意踩的」。
反过来,ADR 也能减轻 代码评审文化 的负担:当「为什么用这个方案」已被 ADR 记录,评审时就不必每次重新辩论架构方向,只需聚焦实现细节。
八、常见反模式与避坑
| 反模式 | 后果 | 解法 |
| 把 ADR 写成需求文档 | 过长没人看 | 坚持 500 字内,只写决策与理由 |
| 偷偷修改已 Accepted 的 ADR | 历史失真 | 新写 ADR 取代,旧文标记 Superseded |
| 只有架构师写,团队不读 | 文档与代码脱节 | 合并进 PR 流程,CI 校验 |
| 记录无关紧要的小决定 | 噪音淹没信号 | 只记「可逆成本高」的选型 |
九、把 ADR 接进研发流程
让 ADR 活下来的最好方式,是把它变成流程的一部分,而不是一份「有空再写」的文档。在 GitHub Actions 里加一个轻量校验,确保新增 ADR 带正确状态字段:
# .github/workflows/adr-check.yml
name: ADR Lint
on:
pull_request:
paths: ['docs/adr/**.md']
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 校验状态字段
run: |
grep -lE '^(## )?状态' docs/adr/*.md || (echo "ADR 缺少状态字段"; exit 1)
这条规定和 文档即代码 的思路一致:文档也走版本控制、也进 CI、也能被自动化守护。把 ADR 当成 GitHub Actions 流水线里的必过项,它就不会沦为摆设。同时参考成熟的 代码评审文化,把「为什么这么选」的前置讨论沉淀进文档,评审时只聚焦实现细节。
十、小团队也能用的轻量实践
你不需要一个委员会。三人团队完全可以这样跑:谁发起选型,谁写 ADR 初稿;在周会花五分钟过一遍 Proposed 状态的文档;一致就 Accept,存进仓库。半年后你会拥有一份比任何人口述都可靠的「架构编年史」。
真正稀缺的从来不是技术信息,而是「我们当初为什么这么选」的上下文。ADR 用极低的成本,把这种上下文留了下来——下次有人问「为什么不用 MongoDB」,你不用翻聊天记录,只需要把链接甩过去。
十一、ADR 与 RFC、设计文档有何不同
容易混淆的是,ADR 不是 RFC,也不是传统设计文档。RFC(Request for Comments)通常是大厂用来征集广泛意见的正式提案,流程重、参与人多;设计文档侧重「怎么实现」,会画时序图、写接口契约。ADR 只回答一个最小问题:「我们决定做什么,以及为什么」——它不关心实现细节,也不追求全员评审。可以把三者理解为不同颗粒度的记录:RFC 是提案,设计文档是施工图,ADR 是那张贴在工地门口的「本楼采用框架结构」的决议牌。
也正因如此,ADR 的门槛最低,最适合中小团队。你不需要采购协作工具,一个 Markdown 文件加一个 Pull Request 就够。当团队成长到需要更重的流程时,ADR 也能平滑升级:把 Accepted 的 ADR 自动同步进团队 Wiki,或接入 GitHub Actions 在每次架构相关 PR 里提醒作者「是否该写篇 ADR」。它从一个轻量习惯,慢慢长成团队的架构记忆体。




