同一个问题在半年内讨论了三次:内部任务系统要继续用数据库队列,还是引入专门消息中间件。每次参与者不同,大家重新列一遍优缺点,最后凭印象说“上次好像觉得暂时不用”。决定没有进入代码,也没有留下复审条件,文档只有两页会议纪要。

我们开始写 Architecture Decision Record。它不是架构说明书,也不是会议转录,只保存当时为什么做这个选择、承担什么代价、什么变化会让它需要重审。

ADR 由上下文、决定和后果构成,并包含验证指标与复审触发器

图 1:决定的寿命往往比参与会议的人长,必须保留当时的约束和反证条件。

一页 ADR 的最小结构

# ADR-017: 任务调度继续使用 PostgreSQL 租约表
 
状态: Accepted
日期: 2024-10-19
负责人: Platform
 
## Context
当前峰值 40 jobs/s;要求事务内创建任务;团队无 Kafka 运维经验。
 
## Options
1. PostgreSQL lease table
2. Redis Streams
3. Kafka
 
## Decision
未来两个季度继续使用 PostgreSQL,补充索引、租约回收和容量指标。
 
## Consequences
接受轮询成本和单库容量上限;暂不承担新中间件运维成本。
 
## Revisit when
持续 15 分钟超过 200 jobs/s,或跨区域消费成为硬需求。

上下文里写量级和组织能力,比“PostgreSQL 简单”更有用。规模变化后,旧结论可能自然失效。

备选方案必须真实可选

为了证明自己的选择正确而列一个明显荒谬的对手,没有决策价值。每个选项写同一组维度:可靠性语义、吞吐与延迟、事务边界、运维成本、迁移成本、团队熟悉度和退出路径。

选项 优势 当时不能接受的代价
PostgreSQL 与业务事务一致、已有运维 轮询和单库扩展上限
Redis Streams 延迟低、消费组 持久性与现有备份流程需补齐
Kafka 高吞吐、重放能力 规模不匹配,运维和迁移成本高

反对意见不要被会议结论抹掉

ADR 里保留主要反对理由和为什么仍未选择。Kafka 方案支持者担心两年后迁移更贵,这是合理风险;我们把它转成复审触发器和接口约束:业务层不依赖 PostgreSQL 表结构,任务协议保持可迁移。

决定要连接到执行证据

ADR 链接实现 PR、仪表盘和后续事故。代码中重要边界可以引用 ADR 编号,监控直接展示复审指标。当吞吐连续接近触发器时,自动创建技术任务,而不是等某个人想起旧文档。

状态不是只有 Accepted/Rejected。被新决定替代时标记 Superseded 并链接新 ADR;试验性选择用 Proposed;明确放弃的方案用 Rejected 并保留原因。不要修改旧 ADR 伪装历史一直正确。

不是什么都值得写 ADR

可轻易回退、影响范围小、遵循现有标准的选择不需要文档。ADR 留给跨团队、长期、昂贵或难逆的决定。数量太多会淹没真正重要的判断。

使用一段时间后,最直接的收益不是少开会,而是新成员能理解“为什么现在这样”,并知道哪些条件变化后可以合理挑战它。好文档不是阻止未来推翻决定,而是让未来的人不必先重演过去的全部争论。

ADR 也会过时。被新决定替代时标记 Superseded 并链接新 ADR;明确放弃的方案保留原因;复审触发器被触发过但没有跟进,是记录失效的最早信号。决定的价值在写下那一刻只是开始,后续每次重审都在更新它。这个“决定 → 利息 → 触发器 → 重审”的循环,在架构债务不是旧代码里有更完整的模型——那里把复审条件从文档变成了可监控的指标。

对负责人而言,ADR 还有一个附加作用:它让“这个决定当时为什么这么做”脱离个人记忆,团队不必事事等我解释。这与技术负责人的边界里把决定权下放给最接近问题的人,是同一套分权机制的两半。