架构不是一张画在墙上的静态图纸,而是一连串有据可查的决策所累积出来的结果。

接手一个运行了五年的系统时,最让人无从下手的往往不是代码本身,而是那些「为什么」:为什么这个模块要单独拆出来、为什么这里用消息队列而不是直接调用、为什么明明有更好的方案却选择了这个看起来更笨的实现;代码能告诉你系统是什么,却几乎无法告诉你系统为什么是这样;而这种知识的流失,正是系统逐渐变得不可维护的起点。

Architecture-Decision-Records-traceable-technology-selection-1

一、为什么架构会「失忆」

架构知识的载体通常有三种:文档、代码和人的记忆;三者的衰减速度完全不同。

文档衰减得最快,因为它与代码之间没有强制的一致性约束,一次赶工期的重构就足以让文档彻底失真;代码衰减得最慢,但它只保留了结果,丢失了所有的比较信息——你无法从代码里知道当时还考虑过哪些方案、为什么放弃了它们;人的记忆最完整,却最不可靠:它会随着人员流动而消失,而软件行业的流动率恰恰很高。

三者叠加的结果是:三五年之后,团队面对关键模块时只剩下两种态度——不敢改,或者推倒重写;前者让系统僵化,后者代价高昂且往往重蹈覆辙;而这两种困境的根源,都是决策依据的丢失。

二、架构决策记录的结构

架构决策记录(Architecture Decision Record,ADR)用最小的成本解决了这个问题;它的核心思想是:不记录架构的最终状态,只记录每一次决策的上下文与理由。

一条完整的 ADR 通常包含六个部分:标题(编号 + 一句话描述这次决策)、状态(提议中、已采纳、已废弃、已被取代)、背景(当时面临的问题与约束条件)、决策(最终选择了什么)、理由(为什么选它而不是其他方案)、后果(这次决策带来的正面影响、负面代价与后续需要跟进的约束)。

用 Markdown 写成的一条 ADR 大概长这样:

# ADR-007:订单查询服务引入读写分离

## 状态
已采纳(2026-03-14),取代 ADR-003 中对订单库直接读写的约定

## 背景
订单详情页在大促期间 QPS 峰值达到 12000,主库 CPU 长期处于 85% 以上;
同时报表类查询与分析任务占用大量连接,与在线交易互相争抢资源;
预算约束下暂不考虑分库分表,团队也没有相应的运维经验。

## 决策
订单主库保持单写,新增两个只读从库;
在线交易类查询走主库,列表、详情、报表类查询路由到从库;
从库容忍 200ms 以内的复制延迟,超出范围的关键业务回退到主库。

## 理由
读写分离是本方案中改造成本最低的一种:不需要改动数据模型,
也不需要引入新的中间件组件(复用已有的数据库代理);
相比之下,分库分表会带来跨分片查询与分布式事务问题,
在团队经验不足的阶段风险过高。

## 后果
正面:主库负载预计下降 60%,报表查询不再影响在线交易;
负面:引入了复制延迟这一新的故障模式,需要在业务侧接受短暂不一致;
后续:需要在监控体系中新增从库延迟告警,并在半年后重新评估是否分表。

这份记录的价值不在于它写得多规范,而在于两年之后,任何一个接手的人都能看懂当时的约束是什么、取舍是什么、什么时候应该重新评估。

三、ADR 与架构文档、方案评审的边界

三者常被混淆,职责其实很清楚。

架构文档描述系统的现状与目标形态,是「名词」,回答系统是什么样;技术方案评审是一次面向当下的集体讨论过程,是「动词」,回答这个方案能不能通过;ADR 则是决策结论的长期存档,是「快照」,回答当时为什么这么定。评审会开完就结束了,结论如果不落成 ADR,半年后就没有人记得;文档描述现状,但它无法解释现状中的妥协从何而来。

一个实用的分工是:方案评审产出 ADR,架构文档引用 ADR;文档负责描述「现在是什么」,ADR 负责解释「为什么会这样」;当文档与 ADR 冲突时,以 ADR 的时间线为准来判断哪一方已经过时。

Architecture-Decision-Records-traceable-technology-selection-2

四、如何在团队里落地 ADR

什么时候写:判断标准是「这个决定是否难以逆转」;技术栈选型、数据模型设计、服务边界划分、对外接口协议、安全与鉴权方案,这些一旦落地就要付出很大代价才能改变,必须写;日常的代码实现细节、样式约定、临时兼容逻辑,不值得写。写得多不如写得准,一份被淹没在噪音里的 ADR 集,等于没有。

谁来写、谁来审:由提出决策的人写,通常是这个模块的负责人或架构师;由受影响的人审,包括上下游团队、运维、测试、安全;关键决策需要有一个明确的决策责任人(Owner),避免「集体负责等于无人负责」;评审意见不要求全部收进 ADR,但反对意见与未采纳的方案应当保留在「理由」一节里,因为被放弃的选项往往比被选择的选项更有信息量。

存在哪里、怎么维护:最可靠的做法是与代码同仓,放在 docs/adr/ 目录下,以 Pull Request 的方式提交与评审;这样决策记录天然进入了版本管理,可以被检索、被 diff、被追溯;文件名按编号递增,例如 007-order-read-write-splitting.md,编号一旦分配就不再复用;ADR 的修改遵循「不删不改,只新增」的原则——当决策变化时,写一条新的 ADR,把旧的那条状态改为「已被取代」,并互相引用。

怎么让更多人真的去读:把 ADR 纳入新人上手路径,把关键 ADR 摘要进架构文档,把「有没有对应的 ADR」作为技术评审的一个检查项;制度设计上只做一件事就够了——让不写 ADR 的决策在评审时被明确地问一次「为什么不需要记录」。

五、从决策记录到适应度函数

ADR 解决的是「为什么」,但它无法保证决策在未来被遵守;一个久经演化的系统里,最初定下的边界约束往往会被人无意间越过,而越过的那一刻通常无人察觉。

这就轮到适应度函数登场了:把架构约束翻译成可以自动执行的检查。例如「订单模块不得直接依赖支付模块的内部实现」可以写成一条依赖分析规则,在 CI 中扫描包依赖,越界即失败;「核心链路不得引入新的同步远程调用」可以写成接口扫描;「公共组件的测试覆盖率不得低于 80%」就是一条覆盖率门禁。

ADR 写清意图,适应度函数守住边界;一个是文档,一个是代码,两者配合才是完整的架构治理:只有文档没有检查,约束会静默失效;只有检查没有文档,规则会变成无人理解的形式主义。

Architecture-Decision-Records-traceable-technology-selection-3

六、结语

架构决策记录不是一份文档模板,而是一种工程习惯:默认把重要的判断写下来、署名、留档,并承认它未来可能被推翻。

它带来的收益是复利的——写的时候只需要十几分钟,省下的却是后来者数天甚至数周的摸索;它最大的价值不是让架构更漂亮,而是让团队在关键路口少走一次已经走过的弯路,尤其是那条走错了的路。