记录架构决策,让下一个智能体知道为什么

使用 AI 辅助编写与复核。

本文目录

架构决策记录(Architecture Decision Record,简称 ADR)解释一项会产生后续影响的选择:要解决的问题、备选方案、选定做法、决策状态和后果。把某个边界存在的原因交给下一个智能体,它才能判断这个原因是否仍然成立。

任务交接可能写着:“缓存模块已完成,接下来补测试。”ADR 回答的是另一类问题:“缓存为什么放在这里?出现什么条件时,才值得把它移走?”这份解释应当在产生改动的那段对话结束后继续保留。

Michael Nygard 的原始 ADR 文章介绍了包含背景、决策、状态和后果的简短记录,也主张保留被替代的决策,并指向替代它的新记录。下面的例子借鉴这一历史方法;额外字段是本教程的编辑选择,不是通用标准。

选择一项会持续影响后续工作的决策

考虑一个虚构服务:它根据带版本的源文档,渲染公开帮助片段。最初只有一个应用进程,拟建的缓存只保存渲染后的文本,不存账户数据。

团队需要决定缓存放在哪里。应用进程内缓存把条目留在处理请求的那个进程中;共享缓存服务则增加一个独立组件,让多个应用进程通过它读取和删除条目。

这个服务、它的需求和决策全部为教学编写。下文记录中的 Accepted 描述虚构团队的决策状态,不是真实的 CueSpec 架构决策、客户案例或部署报告。

比较方案之前,先明确假设。否则,智能体只要悄悄改变场景,就能替几乎任何架构找到理由。本例的缓存条目可以丢弃:缓存失败时,应用可以回到权威来源重新渲染。缓存键包含文档标识符和源文档修订版本。本例没有延迟目标,也没有声称测得性能改善。

围绕这些假设比较备选方案

问题 缓存在应用进程内 独立的共享缓存组件
需要额外运维什么组件? 本例不需要额外组件 缓存服务及其客户端连接
应用进程重启时会怎样? 按设计丢失该进程的内存条目 条目有独立生命周期,由所选服务定义
谁能删除条目? 持有条目的进程 经共享服务接口授权的调用方
一个进程能否复用另一个进程已填入的条目? 按本例设计,不能 可以,前提是二者使用同一服务和同一套缓存键规则
缓存不可用时怎么办? 从源文档渲染 从源文档渲染
初期决策面临什么取舍? 让首版实现保持小规模 在有具体需要之前,避免增加额外组件

这张表比较的是拟议设计,不是厂商性能。现有材料不足以说明共享服务在真实部署中更快、更便宜或更可靠。这些问题需要选定实现,并取得对应运行条件下的证据。

写出一份完整的初始决策

下面这份记录可以放在虚构仓库的 decisions/0001-local-snippet-cache.md:

# ADR 0001:将公开片段缓存放在应用进程内

Status: Accepted(在初始单进程阶段接受)。
Scope: 仅限渲染后的公开帮助片段。

## 背景

服务目前只有一个应用进程,渲染依据是带版本的公开源文档。
我们希望在该进程内复用已渲染文本,不引入需要单独运维的缓存组件。

源文档仍是权威来源。缓存条目不包含账户、权限或用户专属数据。
在这个阶段,没有跨进程共享条目或集中删除缓存文档的需求。
这些是设计假设,不是测得的生产运行情况。

## 考虑过的方案

A. 在应用进程内保留数量有上限的条目。
B. 现在就引入独立的共享缓存服务。

B 可以提供共同的条目存储,但也需要服务生命周期管理、连接处理
和可用性策略。目前我们不需要它所提供的共同所有权。

## 决策

使用应用进程内缓存保存渲染后的公开片段。
缓存键包含文档标识符和准确的源文档修订版本。
拟议缓存最多保存 200 个条目,每个条目的存活时间为 60 秒。
这是示例选定的限制,不是基准测试结果。
实现必须明确过期和淘汰行为。

缓存未命中或发生错误时,应用从权威来源重新渲染。
不以缓存条目作为授权依据。

## 后果

首版实现不需要独立缓存服务。进程重启会丢弃其条目。
以后若增加应用进程,每个进程都会有自己的独立条目,
可能重复进行渲染。

文档修订版本改变时,查询键也随之改变;旧条目仍可能保存在缓存中,
直到过期或被淘汰。本决策不提供集中清除操作、延迟保证
或正确性验证结果。

## 何时重新评估

需要多个应用进程共同管理缓存,或者出现独立本地缓存
无法满足的明确删除策略时。

## 后续工作

实现并检查缓存键构造、条目数量上限、过期、缓存出错时的回退,
以及排除用户专属数据的规则。本例没有记录执行或部署结果。

这份记录给出一个选择,而不是列出所有可能的缓存技术。它也说明了团队接受的代价。重启后重复渲染是所选设计的预期后果,不是一个来由不明、等着下个智能体去“修好”的缺陷。

重新评估的条件很重要。没有这个条件,“保持简单”可能演变为无限期禁止变更,而原作者从未考虑过这些后来的情况。

条件改变时,写一份替代记录

现在改变虚构需求:团队计划运行多个应用进程,编辑人员还需要一次统一的缓存删除操作,删除某篇文档在所有应用进程范围内已存储的条目,并收到删除确认。这项操作不承诺收回读者已经收到的文本,也不承诺取消正在进行的渲染。

新需求暴露了初始设计的限制:各进程独立保存的本地条目,无法直接提供所需的统一删除接口。共享组件是一种可能方案;协调失效机制也可能解决问题,但它同样需要定义实现方式及失败行为。

替代决策应放在新记录 decisions/0002-shared-snippet-cache.md 中:

# ADR 0002:将公开片段条目移到共享缓存组件

Status: Accepted(在后来的虚构设计阶段接受)。
Supersedes: 0001-local-snippet-cache.md
Scope: 渲染后的公开帮助片段的缓存存储与删除。

## 背景

计划中的服务将有多个应用进程。修订后的需求是提供一个缓存删除接口,
让编辑人员删除某篇文档在整个服务中的已存储条目,并收到操作结果。

ADR 0001 中各自独立的本地缓存不提供这个接口。
源文档仍是权威来源,缓存数据仍是公开数据。
删除缓存文本不同于撤回源文档;请求在使用缓存条目前,
必须继续检查源文档是否可用。

## 考虑过的方案

A. 保留本地缓存,另建覆盖每个应用进程的失效协调和确认机制。
B. 将条目放入一个共享缓存组件,并移除各进程的渲染文本缓存。

我们选择 B,把缓存所有权和删除操作放到同一个接口之后。
我们接受额外的服务依赖,而不为各自独立的本地缓存实现分布式协调。

## 决策

将渲染后的片段条目移到共享缓存组件。
应用进程不再保留第二层渲染文本缓存。
缓存键仍包含文档标识符和准确的源文档修订版本。
面向编辑人员的操作,只有在共享组件确认所请求的删除后,
才能报告成功。删除失败或结果未知时,调用方收到的结果
也必须仍是失败或未知。

每次渲染在查询缓存之前,都先取得当前源文档的可用状态和修订版本。
源文档缺失或已撤回时,不得从缓存提供内容。
缓存查询和写入失败时,回退到对可用源文档进行渲染;
不能因此把失败的删除操作报告成成功。

## 后果

所有应用进程现在都依赖共享接口获取缓存条目。
上线前,必须明确并检查超时、条目限制、删除范围
和所选服务的实际行为。

进行中的渲染可能与删除操作重叠。删除缓存操作不是内容撤回保证;
源文档撤回必须由权威来源检查来落实。
缓存无法收回已经交付的文本。

我们尚未选择厂商,也没有测量性能。
这份记录接受的是设计方向,不是经过验证的实现或已部署服务。

## 与 ADR 0001 的关系

单进程假设和不需要集中删除的条件已经改变。
ADR 0001 继续解释较早的设计。
其中关于缓存键和公开数据的约束在本记录中延续;
其存储位置选择由本记录替代。

## 后续工作

选择具体实现。检查共同的缓存键构造、源文档撤回、缓存故障、
删除失败,以及渲染与删除重叠时的行为。
修改运行中的服务之前,先定义上线和回退方案。

第二份记录明确写出需求发生了什么变化,也限制了保证的范围:删除一个条目,不等于阻止内容以任何方式继续显示。这一区别应写进决策,因为它决定后续工作能安全依赖什么。

保留旧记录,并添加清楚的指向

更新 ADR 0001 顶部的状态,不要抹去原来的理由:

Status: Superseded by [ADR 0002](0002-shared-snippet-cache.md).
Originally accepted at the initial single-process stage.

这两行状态标识保留英文:Superseded by 表示已被 ADR 0002 替代,第二行保留它最初在单进程阶段被接受的历史。旧记录的背景和后果保持不动。ADR 0002 也指回 ADR 0001,让读者从任一方向进入都能看清先后关系;索引应将 ADR 0002 标为当前的缓存存储决策。

不要悄悄重写 ADR 0001,假装共享设计从一开始就是原意。那会删除理解早期实现所需的依据,使后来的人无法知道:在当时的假设下,旧方案为什么合理。

把决策和需要回答的问题一起交给智能体

提出缓存变更之前,先读 ADR 0001 及替代它的 ADR 0002。
在这个虚构仓库中,把 ADR 0002 作为当前决策。

对照公开数据边界、含修订版本的缓存键、源文档撤回检查,
以及删除确认要求,检查你的方案。指出哪些假设仍然成立,
你的方案改变了哪个需求。

如果方案与当前决策冲突,起草一份新的 ADR,写明备选方案和后果。
在决策负责人接受之前,保持 Proposed(拟议)状态。
不要重写历史记录来掩盖变化。

有用的 ADR 让下一个智能体能够依据团队约束进行推理。它不意味着设计永远不能改,也不证明实现已经有效,更不授予部署替代方案的权限。