架构决策记录规范
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.mdADR 索引表示例(docs/adr/README.md):
| 序号 | 标题 | 状态 | 日期 |
|---|---|---|---|
| 0003 | 服务间通信切换为 gRPC | Accepted | 2024-10-14 |
| 0002 | 使用 Kafka 作为消息队列 | Accepted | 2024-09-20 |
| 0001 | 使用 Redis 作为缓存 | Accepted | 2024-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 设计、消费模式、可靠性保障)。
- ❌ 后果只说”挺好的”,没有正面/负面影响和风险。
- ❌ 没有备选方案对比,无法判断选型合理性。