news 2026/8/30 23:10:27

架构决策记录(ADR)实践:基于uber/ADR模板建立可追溯的技术选型文档

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
架构决策记录(ADR)实践:基于uber/ADR模板建立可追溯的技术选型文档

架构决策记录(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.md

README 里至少写清楚三件事:

  • ADR 的存放位置和文件命名规则。
  • 状态有哪些,分别代表什么。
  • 新增决策的流程:创建文件、填写模板、提交 PR 评审、合并后算生效。

这些内容不一定长,但必须让新成员第一次看到就知道怎么用。模板文件可以直接参考上一节的正文结构,保留字段占位符。

检查点:确认目录结构存在,README 有明确规则,模板文件可以复制使用。这里要特别注意,模板文件里的id不要写成固定值,

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 23:07:58

一个读取uint32数据标志位的宏定义

这是一个常用的读取 uint32 数据指定标志位的宏定义:// 读取第 n 位(n 从 0 开始计数),返回该位的值(0 或 1) #define GET_BIT(value, n) (((value) >> (n)) & 0x01)// 判断第 n 位是否为 1…

作者头像 李华
网站建设 2026/8/30 23:06:58

《易学・大壮䷡|道影子新解 034》

摘要大壮卦(䷡)承接遁卦 “退避保全、藏器待时” 之后,揭示当阴消阳长、阳气大壮、力量强盛时,系统便进入 “刚健强盛、以正用壮” 的大壮力场。其本质是雷在天上、刚健而动,四阳盛长、阴气渐消,力量充沛、…

作者头像 李华
网站建设 2026/8/30 23:04:56

LangGraph背后的运行机制

揭秘 LangGraph:从手写 While 循环到 Pregel 图计算内核 很多开发者在学习 LangGraph 时,都会产生一个疑问: 在手写的 Agent 代码(如基于 OpenAI 官方 API 写的脚本)中,逻辑一目了然:一个 for …

作者头像 李华
网站建设 2026/8/30 23:04:19

企业文件管理平台的选型实战:为什么我们从网盘迁移到了巴别鸟

企业文件管理平台的选型实战:为什么我们从网盘迁移到了巴别鸟 作为一枚在后端开发岗干了5年的工程师,这几年陆陆续续参与过好几次文件管理相关的选型和改造项目。从最早用Windows共享目录,到后来折腾SMB、NFS,再到云时代开始用各种…

作者头像 李华
网站建设 2026/8/30 23:01:42

一块芯片,卡住了所有手机厂商的脖子

2026年8月24日,北京,小米发布会现场。 当雷军把"玄戒 O3"这颗自研芯片的参数打上大屏幕时,台下的手机行业记者们,注意力大多没落在那颗 SoC 的算力上,而是落在了一句被刻意放在角落的标注——"行业首发支持 LPDDR6"。 这个细节,普通消费者几乎不会…

作者头像 李华
网站建设 2026/8/30 23:00:22

PR评审新利器:用动画架构图看清代码变更影响

在 PR(Pull Request)评审中,读代码 diff 只是第一步,真正费时间的是把变更映射回系统架构。一个 PR 可能只改了三个文件,但影响的服务边界、依赖关系、消息链路,往往牵动一整条业务分支。评审者如果只能在脑…

作者头像 李华