跳转到内容

架构决策记录规范

ADR / RFC 编写与决策流程


规则:架构决策记录(Architecture Decision Record, ADR)是记录重要技术决策的轻量级文档,每个决策一个文件,使用统一模板。

[Requirement] 涉及技术选型、架构变更、关键接口设计的决策,必须产出 ADR。

[Requirement] ADR 文件命名格式为 NNNN-<决策标题>.md,其中 NNNN 为 4 位递增序号(如 0001-use-redis-as-cache.md)。

[Requirement] ADR 必须包含:状态、背景、决策、后果四个核心章节,缺一不可。

[Standard] ADR 存放于 docs/adr/ 目录,按序号排序,不允许修改已归档的 ADR 内容(新增 ADR 取代旧决策)。

[Default] 模板采用 Michael Nygard 风格,章节固定如下。

ADR 模板:

# ADR-NNNN: <决策标题>
 
## 状态
<!-- Proposed | Accepted | Deprecated | Superseded by ADR-XXXX -->
 
## 背景
<!-- 描述面临的问题、约束条件、为什么需要做这个决策 -->
 
## 决策
<!-- 我们决定做什么,以及选择了哪个方案 -->
 
## 后果
<!-- 采用这个决策后带来的正面和负面影响 -->
<!-- - 正面:...
     - 负面:...
     - 风险:... -->
 
## 备选方案
<!-- 列出考虑过的其他方案及其被否决的原因 -->
<!-- 1. 方案 A:... — 否决原因:...
     2. 方案 B:... — 否决原因:... -->
 
## 参考
<!-- 相关文档、链接、讨论记录 -->

[Suggestion] ADR 控制在 1-2 页内,聚焦决策本身,不展开实现细节。

反模式:

  • ❌ 决策做完了不写 ADR,半年后无人记得为什么这么选。
  • ❌ ADR 写成技术方案,长篇大论实现细节,失去”决策记录”的意义。
  • ❌ 修改已 Accepted 的 ADR 内容,导致历史决策失真。

规则:RFC(Request for Comments)是面向重大架构变更的评审流程,比 ADR 更正式,须经过提案、评论、修订、决议四个阶段。

[Requirement] 涉及跨团队、影响面大、不可逆的架构变更,必须走 RFC 流程。

[Requirement] RFC 须公开评审,允许所有相关方提出意见,评审期不少于 3 个工作日。

[Standard] RFC 流程:提案 → 公开评论 → 修订 → 决议(通过/拒绝/搁置)。

[Standard] RFC 通过后,须产出对应的 ADR 记录最终决策。

[Default] 评审期默认 3-5 个工作日,重大决策可延长至 1-2 周。

RFC 流程:

提案人撰写 RFC
│
▼
公开评审(≥3 工作日)
│
├─ 收到评论 ──→ 提案人修订 RFC ──→ 重新评审
│
▼
决议(通过/拒绝/搁置)
│
▼
通过 → 产出 ADR → 进入实施

RFC 适用场景判断:

场景是否需要 RFC说明
引入新的中间件/数据库是影响运维、成本、团队技能
改变服务间通信协议(HTTP→gRPC)是跨团队影响
重构模块内部实现否影响范围小,写 ADR 即可
新增一个 API 接口否常规开发,技术方案即可
更换核心框架(Spring→Quarkus)是影响巨大,不可逆

反模式:

  • ❌ 重大架构变更只在小群里讨论,相关团队不知情,上线后才发现冲突。
  • ❌ RFC 评审期只有几小时,无人有时间仔细看。
  • ❌ RFC 通过后不产出 ADR,决策无正式记录。

规则:每个 ADR/RFC 必须有明确的状态,状态变更须记录原因,禁止状态模糊或跳变。

[Requirement] ADR 状态必须为以下之一:Proposed / Accepted / Deprecated / Superseded。

[Requirement] 状态变更须在 ADR 顶部注明变更日期和原因。

[Standard] RFC 状态:Draft / Reviewing / Revised / Approved / Rejected / Withdrawn。

[Default] 新建 ADR 默认状态为 Proposed,评审通过后转为 Accepted。

ADR 状态定义:

状态含义后续动作
Proposed已提出,待评审进入评审流程
Accepted已通过,当前生效团队遵循此决策
Deprecated已废弃,不再使用新代码不遵循,旧代码逐步迁移
Superseded被新决策取代指向新的 ADR(如 Superseded by ADR-0015)

状态流转图:

Proposed ──评审通过──→ Accepted ──被取代──→ Superseded
│ │
└──评审未通过──→ Deprecated

[Caution] Accepted 状态的 ADR 不允许直接修改内容;如需变更决策,须新建 ADR 并将旧 ADR 标记为 Superseded。

示例:状态标注

## 状态
 
Accepted(2024-10-14)
 
历史状态:
- 2024-10-10: Proposed,提交评审
- 2024-10-14: Accepted,评审通过

反模式:

  • ❌ ADR 状态永远是 Proposed,无人确认是否生效。
  • ❌ 修改 Accepted 的 ADR 内容,导致历史与现状不一致。
  • ❌ 决策变了但旧 ADR 没标 Superseded,新人还在按旧决策做。

规则:ADR/RFC 须集中存储、统一索引,确保可检索、可追溯。

[Requirement] 所有 ADR 存放于仓库 docs/adr/ 目录,RFC 存放于 docs/rfc/ 目录。

[Requirement] docs/adr/README.md 须维护 ADR 索引表,包含序号、标题、状态、日期。

[Standard] ADR 文件名须包含序号与简短标题,如 0001-use-redis-as-cache.md。

[Standard] 索引表须按序号倒序排列,最新决策在最上方。

[Default] 默认使用 Markdown 格式,便于版本管理与 diff。

目录结构:

docs/
├── adr/
│ ├── README.md # ADR 索引
│ ├── 0001-use-redis-as-cache.md
│ ├── 0002-use-kafka-as-mq.md
│ └── 0003-switch-to-grpc.md
└── rfc/
├── README.md # RFC 索引
├── 0001-service-mesh.md
└── 0002-multi-region.md

ADR 索引表示例(docs/adr/README.md):

序号标题状态日期
0003服务间通信切换为 gRPCAccepted2024-10-14
0002使用 Kafka 作为消息队列Accepted2024-09-20
0001使用 Redis 作为缓存Accepted2024-08-01

[Suggestion] 每个 ADR 在关联的技术方案、代码注释中反向引用 ADR 编号,便于追溯决策上下文。

反模式:

  • ❌ ADR 散落在各个仓库和文档系统里,找不到。
  • ❌ 没有索引表,要逐个打开文件才知道有哪些决策。
  • ❌ ADR 存在个人电脑里,团队其他人看不到。

完整 ADR 示例:0001-use-redis-as-cache.md

# ADR-0001: 使用 Redis 作为缓存
 
## 状态
 
Accepted(2024-08-01)
 
## 背景
 
随着用户量增长,数据库查询压力增大,热点数据(用户信息、商品详情)频繁查询导致
数据库 CPU 使用率超过 80%。需要引入缓存层降低数据库压力。
 
约束条件:
- 团队已有 Redis 运维经验
- 允许短暂的缓存不一致(最终一致可接受)
- 预算有限,优先选择开源方案
 
## 决策
 
我们决定使用 Redis 作为分布式缓存。
 
具体方案:
- 缓存热点数据:用户信息、商品详情
- 缓存策略:Cache-Aside(旁路缓存)
- 过期时间:用户信息 30 分钟,商品详情 10 分钟
- 淘汰策略:allkeys-lru
 
## 后果
 
正面:
- 数据库查询压力预计降低 60%
- 热点数据响应时间从 200ms 降至 10ms
 
负面:
- 引入 Redis 运维成本
- 存在缓存与数据库不一致的时间窗口
 
风险:
- 缓存击穿/雪崩:通过互斥锁 + 随机过期时间缓解
- Redis 故障:降级直连数据库,保证可用性
 
## 备选方案
 
1. 本地缓存(Caffeine)
   - 优点:无网络开销,速度更快
   - 缺点:多实例间数据不一致,内存占用大
   - 否决原因:分布式场景下一致性难以保证
 
2. Memcached
   - 优点:纯内存,性能高
   - 缺点:数据结构单一,不支持持久化
   - 否决原因:Redis 功能更丰富,团队更熟悉
 
## 参考
 
- 技术方案:docs/design/20240715-cache-design.md
- 压测报告:docs/reports/cache-benchmark.md

反模式示例(不合格的 ADR):

# ADR-0005: 用 Kafka
 
## 状态
Accepted
 
## 背景
要用消息队列。
 
## 决策
用 Kafka。
 
## 后果
挺好的。

问题:

  • ❌ 背景没有说明为什么需要消息队列、约束条件是什么。
  • ❌ 决策没有说明具体怎么用(Topic 设计、消费模式、可靠性保障)。
  • ❌ 后果只说”挺好的”,没有正面/负面影响和风险。
  • ❌ 没有备选方案对比,无法判断选型合理性。