架构决策记录(Architecture Decision Record,ADR)是一种把架构决策及其背景写成短文档的方法。uber/ADR是 Uber 在 GitHub 上公开的一套 ADR 模板与配套规范,它把一次技术选型从会议口头结论变成可检索、可追溯、可反思的工程资产。团队在维护中大型系统时,代码只能说明“当前是什么”,无法说明“当初为什么这样做”。ADR 要补上的,正是“为什么”这一段缺失的上下文。
这篇文章面向正在搭建新项目、维护老系统,或者想改善团队架构评审流程的工程师。读完这篇文章,你能理解 ADR 的核心概念,并能以uber/ADR的模板思路为基础,在自己项目里建立一套最小可用的 ADR 管理流程,包括目录组织、字段设计、状态流转、评审绑定,以及常见的落地失败场景和排查方式。
1. 先理解 ADR 是记录架构决策的最小单元
1.1 架构决策为什么需要落成文字
实际项目里,一次技术选型往往发生在会议、IM 群、PR 评论甚至口头讨论中。讨论结束后,结论可能进了会议纪要,也可能只存在于某个负责人的记忆里。三个月后,新同学接手时看到代码库里用了某个中间件,问“为什么选这个”,答案常常是“当时评估过,好像还行”。至于评估过哪些方案、放弃了什么、在什么约束下做的判断,很难再拼凑完整。
代码本身只表达实现结果,不表达决策过程。两个服务之间选择了同步调用而不是消息队列,从代码里能看到 HTTP 调用,却看不到“当时吞吐量预期不高,团队不希望引入额外运维组件”这些约束。如果这些背景没有被记录下来,后续的重构、换型、成本优化就没有判断依据。ADR 就是用来解决这个问题的:它把一次决策的背景、结论、后果写成一段结构化文字,放在版本控制中,和代码一起演进。
1.2 ADR 文件是什么样子的
一个 ADR 通常是一个短文档,几百字到一千多字,核心结构非常稳定。业界常见的结构来自 Michael Nygard 的提议:先写 Context(背景),再写 Decision(决策),最后写 Consequences(后果)。uber/ADR的模板思路在这个基础上做了工程化加工,加入元数据区域、状态字段、关联字段,让文档可以被检索、被校验、被追踪。
下面是一个最简形式的 ADR 内容示意,只说明结构,不包含完整细节:
# ADR-0001: 使用 Redis 作为缓存层 ## Status Accepted ## Context 当前服务存在大量重复查询,数据库压力大。 ## Decision 引入 Redis 作为缓存层,缓存热点数据。 ## Consequences 更快的读取速度,但增加了一个基础组件需要运维。这种文件不需要很长。它足够告诉未来的人:当时的背景是什么,团队做了什么选择,代价是什么。更工程化的模板会在这个基础上增加 Front Matter,这是后续要展开的部分。
1.3 ADR 与常规设计文档的区别
团队里通常已经有架构设计文档、技术方案评审材料等,容易产生疑问:是不是又多了一种文档?这里要明确,ADR 的定位和设计文档不同。
| 维度 | 架构设计文档 | ADR 架构决策记录 |
|---|---|---|
| 目标 | 描述系统整体方案、模块关系、流程 | 记录一次决策的背景、选择和后果 |
| 篇幅 | 通常较长,几十页也常见 | 短小轻量,一般控制在几百字到一千多字 |
| 更新方式 | 随设计演进持续修改 | 决策确定后保持稳定,后续变化用新 ADR 替代 |
| 读者 | 评审者、开发团队、新成员 | 未来的维护者、架构评审者、需要复盘的人 |
| 维护成本 | 高,需要持续同步 | 低,写一次,状态变化时更新元数据 |
| 典型问题 | 设计文档和代码经常脱节 | 如果不写,决策背景会永久丢失 |
关键差异在于:设计文档回答“系统是怎么设计的”,ADR 回答“这个决策是怎么来的,为什么这样做”。两者的生命周期不一样,设计文档会随着方案迭代持续修改,ADR 则在决策成立后尽量保持不被篡改,确有必要的变化通过新 ADR 来体现。
2. 理解 uber/ADR 的模板结构与文件组织
2.1 文件命名与存储位置
落地 ADR 的第一步不是写内容,而是先把文件组织定清楚。推荐在仓库根目录下建立docs/adr目录,把决策记录和普通文档分开。名称上使用“数字前缀 + 短横线语义化标题”的格式,例如:
docs/adr/0001-config-center.md docs/adr/0002-cache-redis.md docs/adr/0003-message-queue-kafka.md数字前缀用于排序,也用于生成 ADR 的唯一标识。如果使用日期作为前缀,同一天出现两条决策会比较麻烦;使用递增序号更稳定。slug部分要尽量简洁,能让人从文件名判断这次决策主题。
在 GitHub 风格仓库中,路径可以直接链接到对应 PR,评审时方便引用。目录结构示例:
project/ ├── docs/ │ └── adr/ │ ├── README.md │ ├── template.md │ ├── 0001-config-center.md │ └── 0002-cache-redis.md └── src/README 负责说明本目录的规则:状态有哪些、模板在哪个文件、谁可以修改状态。模板文件则作为新决策的起点。目录结构一旦确定,就不要频繁调整,否则已有的链接和引用都会失去作用。
2.2 元数据(Front Matter)字段
工程化的 ADR 通常会在 Markdown 文件顶部加入 YAML Front Matter,用统一字段存放结构化信息。uber/ADR项目的核心价值之一,就是把这套字段和模板暴露给团队,使不同团队写出的 ADR 保持同一风格。
一个常见的 Front Matter 示例:
--- id: ADR-0001 title: 引入配置中心管理多环境配置 status: Accepted date: 2025-01-15 decision-makers: - name: 张三 role: 后端负责人 - name: 李四 role: 架构师 considered-options: - name: 自研配置系统 pros: 完全可控 cons: 开发维护成本高 - name: 开源配置中心 pros: 功能成熟,社区活跃 cons: 需要引入额外依赖 chosen-option: 开源配置中心 related-adrs: - ADR-0005 ---字段不是越多越好,但下面几个建议保留:
| 字段 | 含义 | 示例 | 必要性 |
|---|---|---|---|
id | 决策唯一标识,用于链接和讨论 | ADR-0001 | 必填 |
title | 决策标题,一句话概括 | 引入配置中心管理多环境配置 | 必填 |
status | 当前状态 | Accepted | 必填 |
date | 决策创建或接受日期 | 2025-01-15 | 必填 |
decision-makers | 参与决策的人 | 列表形式 | 建议 |
considered-options | 被考虑的候选方案 | 列表形式 | 建议 |
chosen-option | 最终选择的方案 | 开源配置中心 | 必填 |
related-adrs | 关联的 ADR 编号 | [ADR-0005] | 按需 |
为什么要写决策人?因为后续有人对决策有疑问时,最先要找的就是当时的决策人。为什么要列候选方案?因为被否决的方案往往比被选中的方案更有信息量,它记录了团队评估范围的边界。使用 YAML 而不是纯文本段落,是因为字段可以被检索,也能在 CI 脚本中做校验,这是结构化带来的直接好处。
2.3 正文结构
Front Matter 下方是正文,正文部分建议按固定的标题顺序展开。下面是一个可以在template.md里使用的结构:
## Context:描述背景、问题、约束条件和触发原因。## Decision:写出最终决策,使用准确、可执行的表述。## Consequences:列出决策带来的收益、成本、风险和后续影响。## Alternatives Considered:列出候选方案和否决原因。## References:指向相关代码、Issue、PR 或外部文档。
以“缓存层选型”为例,一个完整的 ADR 可以写成:
--- id: ADR-0002 title: 引入 Redis 作为缓存层 status: Accepted date: 2025-02-10 decision-makers: - name: 张三 role: 后端负责人 considered-options: - name: 本地内存缓存 pros: 零依赖,实现简单 cons: 多实例不共享,缓存一致性问题 - name: Redis pros: 读写性能好,支持过期和持久化 cons: 需要维护 redis 服务 chosen-option: Redis related-adrs: - ADR-0001 --- ## Context 订单查询接口每天产生大量重复查询,数据库主库负载接近 70%。经过压测, 热点商品详情查询的 QPS 达到 3000,其中约 80% 请求访问的是同一批热点数据。 ## Decision 引入 Redis 作为缓存层,对商品详情、用户会话等热点数据做缓存。 缓存 key 统一使用 `order:detail:{orderId}` 格式,数据更新时同步失效。 Redis 以集群模式部署,先部署 3 节点,后续根据监控扩容。 ## Consequences - 数据库读压力明显下降,QPS 峰值时可支撑当前业务的 3 倍。 - 需要新增 Redis 运维能力,包括监控、告警、备份和故障演练。 - 缓存淘汰、序列化、key 规范需要一并纳入团队约定。 ## Alternatives Considered - 本地内存缓存:实现简单,但多实例部署时无法共享,最终放弃。 - Memcached:适合纯 key-value,但缺少持久化能力,放弃。 ## References - 性能压测报告见 issue #188 - 部署方案见基础设施仓库 `infra/redis`这段示例里的每个部分都有具体含义。Context 里写的是“为什么现在要做”,Decision 里写的是“具体选择了什么以及怎么用”,Consequences 里同时写了收益和成本,Alternatives 给出了被否决的方案。这样一份 ADR 已经具备做后续决策依据的价值。
3. 在自己项目里落地一套最小可用的 ADR 流程
3.1 先搭目录和规范
落地不要追求一步到位。先用最简单的方式跑通流程,再逐步增加约束。第一步是建立目录和模板。
mkdir -p docs/adr touch docs/adr/README.md touch docs/adr/template.mdREADME 里至少写清楚三件事:
- ADR 的存放位置和文件命名规则。
- 状态有哪些,分别代表什么。
- 新增决策的流程:创建文件、填写模板、提交 PR 评审、合并后算生效。
这些内容不一定长,但必须让新成员第一次看到就知道怎么用。模板文件可以直接参考上一节的正文结构,保留字段占位符。
检查点:确认目录结构存在,README 有明确规则,模板文件可以复制使用。这里要特别注意,模板文件里的id不要写成固定值,