技术决策记录(ADRs)
概述
架构决策记录(ADRs)是轻量级文档,用于捕获重要的架构决策及其理由,提供历史记录,帮助团队理解过去选择的"为什么"。这项技能提供了一种系统化的方法来创建、管理和维护ADRs,减少重新讨论已解决决策的时间,并在团队成员变动时防止知识丢失。ADRs既是当前团队成员的沟通工具,也是理解系统演变过程的学习资源。
为什么这很重要
- 保存知识:决策比团队成员更长久;ADRs防止人员更替时的知识丢失
- 减少重辩:清晰的文档阻止团队反复回顾已解决的决策
- 提高决策质量:书面决策迫使对替代方案和权衡进行深入思考
- 便于入职:新团队成员快速理解架构选择,无需在历史中搜寻
- 提供责任制:明确决策者和决策理由
核心概念
1. 决策重要性阈值
并非每个决策都需要ADR。当决策是:
- 重要:影响系统结构或架构
- 长期:有持久的后果或难以逆转
- 昂贵变更:需要大量努力修改
- 跨领域:影响多个团队或组件
- 权衡重大:涉及重大的妥协或权衡
- 先例设定:为未来决策建立模式
不要为以下情况编写ADRs:
- 微不足道的实现细节(使用代码注释)
- 容易逆转的决策
- 团队惯例(使用风格指南)
- 临时解决方案
2. ADR结构
标准ADR模板确保一致性和完整性:
# ADR-XXX: [简短标题]
## 状态
[提议中 | 已接受 | 已弃用 | 被ADR-XXX取代]
## 背景
问题是什么?什么因素驱动这个决策?
包括背景、约束和需求。
## 决策
做出了什么决策?具体且明确。
## 后果
正反两面的结果是什么?
### 正面
- 好处1
- 好处2
### 负面
- 缺点1
- 缺点2
## 考虑的其他选项
评估了哪些其他选项?
### 选项1: [名称]
- 优点:...
- 缺点:...
- 为什么拒绝:...
3. 版本控制和链接
通过ADR关系维护可追溯性:
- 顺序编号:使用ADR-001、ADR-002等
- 永不删除:标记为已弃用或被取代,不要删除
- 链接相关ADRs:引用相关或依赖的决策
- 取代:当ADR-010取代ADR-005时,链接它们
- 版本控制:将ADRs保存在Git中与代码一起
4. 存储和组织
组织ADRs以便发现和维护:
在仓库中(推荐)
docs/adr/
├── 0001-use-postgresql.md
├── 0002-jwt-authentication.md
├── 0003-graphql-api.md
└── README.md
其他存储选项:
- Wiki/Confluence(便于发现)
- 专用工具(log4brains、ADR Manager)
- 文档平台(Notion、GitBook)
快速开始
- 确定决策:判断决策是否达到重要性阈值(影响架构、难以逆转、长期影响)
- 创建ADR:使用标准模板记录决策、背景、替代方案和后果
- 起草内容:编写背景(问题、约束)、决策(具体选择)、后果(利弊)和考虑的其他选项
- 审查:团队审查ADR以收集反馈和验证理由
- 批准:一旦达成共识,将状态标记为"已接受"
- 实施:根据批准的ADR执行决策
- 跟踪:监控结果并更新ADR;如果情况变化,标记为已弃用
# ADR-001: 使用PostgreSQL作为主数据库
## 状态
已接受(2024-01-15)
## 背景
我们需要为我们的电子商务平台选择一个数据库。需求:
- 订单处理需要ACID事务
- 报告需要复杂查询
- 可扩展到100K+用户
- 库存管理需要强一致性
- 团队有SQL经验
- 预算限制(优先开源)
## 决策
我们将使用PostgreSQL作为主数据库。
## 后果
### 正面
- ACID合规性确保金融交易的数据完整性
- 丰富的查询能力支持复杂报告需求
- 成熟的生态系统有广泛的工具和扩展
- 强大的社区支持和文档
- 团队已经熟悉SQL
- 免费开源(无需许可成本)
- 出色的性能满足我们的预期规模
### 负面
- 垂直扩展限制(虽然对我们的需求足够)
- 与托管服务相比,设置高可用性更复杂
- 需要事先仔细设计模式
## 考虑的其他选项
### 选项1: MongoDB
- 优点:灵活的模式、水平扩展、JSON原生
- 缺点:一致性保证较弱、团队不熟悉NoSQL、对我们的结构化数据来说过于复杂
- 为什么拒绝:我们的数据高度结构化和关系化;ACID保证至关重要
### 选项2: Amazon DynamoDB
- 优点:完全托管、出色的可扩展性、可预测的性能
- 缺点:大规模时昂贵、供应商锁定、查询能力有限、学习曲线
- 为什么拒绝:成本问题和查询限制超过了可扩展性的好处
### 选项3: MySQL
- 优点:与PostgreSQL类似,广泛使用,性能良好
- 缺点:功能不如PostgreSQL丰富,JSON支持较弱
- 为什么拒绝:PostgreSQL在相同复杂性下提供更好的功能
## 注释
- 日期:2024-01-15
- 作者:Jane Smith
- 审核者:John Doe、Alice Johnson
- 下次审核:2025-01-15(或当扩展超过100K用户时)
生产清单
- [ ] 评估决策重要性阈值
- [ ] 使用标准模板
- [ ] 背景清晰描述问题和约束
- [ ] 决策具体且明确
- [ ] 记录后果(正面和负面)
- [ ] 考虑其他选项并列出优缺点
- [ ] 包括拒绝理由
- [ ] ADR由团队审查
- [ ] 状态标记为已接受
- [ ] 分配顺序编号
- [ ] 在版本控制中保存ADR
- [ ] 链接相关ADRs
- [ ] 实施遵循ADR
- [ ] 在代码注释中引用ADR
- [ ] 安排审核日期(如适用)
反模式
- 过度文档化:为琐碎或容易逆转的决策编写ADRs浪费时间并使文档混乱
- 理由模糊:未能解释"为什么"使ADRs对于理解过去决策的用处减少
- 缺少替代方案:未列出考虑的选项使人无法知道是否正确评估了替代方案
- 不更新:未能在情况变化时更新ADRs会导致过时的指导
- 无审查流程:跳过团队审查破坏了集体智慧和认同的价值
- 组织不善:组织不善的ADRs难以查找和使用;建立清晰的结构和编号
- 更新旧ADRs:永远不要修改已接受的ADRs;创建新的来取代或弃用
集成点
- 架构审查:在架构审查过程中创建和引用ADRs
- 代码注释:在代码中引用ADRs以将实现与架构理由联系起来
- 拉取请求:在PR描述中链接相关ADRs以提供上下文
- 文档:在架构文档和wiki中包含ADRs
- CI/CD:使用ADRs在自动化检查中验证架构决策
- 工具集成:使用adr-tools、MADR或log4brains进行ADR管理
- 回顾:在回顾中审查ADR结果以从决策中学习
进一步阅读
- ADR GitHub组织 - 原始ADR模式和工具
- 通过Michael Nygard记录架构决策
- ADR工具 - 用于管理ADRs的命令行工具
- MADR - Markdown ADR模板
- log4brains - ADR知识库工具
- IEEE 1471 - 架构描述推荐实践
ADRs是什么以及为什么它们重要
ADRs是:
- 捕获重要决策的轻量级文档
- 架构选择的历史记录
- 当前和未来团队成员的沟通工具
- 理解系统演变的学习资源
为什么它们重要:
- 知识保存 - 决策比团队成员更长久
- 上下文共享 - 新团队成员理解"为什么"
- 决策质量 - 写作迫使深入思考
- 责任制 - 明确决策的所有权
- 避免重复 - 不要重新审视已解决的决策
何时编写ADR
决策重要性阈值
当决策: ✅ 编写ADR:
- 影响系统结构或架构
- 有长期后果
- 难以或昂贵逆转
- 影响多个团队或组件
- 涉及重大权衡
- 为未来决策设定先例
❌ 不要编写ADR:
- 琐碎的实现细节
- 容易逆转的决策
- 团队惯例(使用风格指南代替)
- 临时解决方案
示例
| 决策 | ADR需要? | 为什么 |
|---|---|---|
| 选择PostgreSQL与MongoDB | ✅是 | 难以改变,影响整个系统 |
| 使用JWT进行身份验证 | ✅是 | 安全关键,影响所有服务 |
命名变量userId与user_id |
❌否 | 琐碎,使用linter/style guide |
| 向函数添加日志 | ❌否 | 容易逆转 |
| 采用微服务架构 | ✅是 | 主要架构决策 |
ADR结构和格式
标准模板
# ADR-001: [简短标题]
## 状态
[提议中 | 已接受 | 已弃用 | 被ADR-XXX取代]
## 背景
我们面临什么问题?什么因素驱动这个决策?
包括相关背景、约束和需求。
## 决策
我们做出了什么决策?具体且明确。
## 后果
这个决策的正面和负面结果是什么?
### 正面
- 好处1
- 好处2
### 负面
- 缺点1
- 缺点2
### 风险
- 风险1
- 风险2
## 考虑的其他选项
我们评估了哪些其他选项?为什么被拒绝?
### 选项1: [名称]
- 优点:...
- 缺点:...
- 为什么拒绝:...
### 选项2: [名称]
- 优点:...
- 缺点:...
- 为什么拒绝:...
## 参考
- 相关文档链接
- 讨论链接
- 原型链接
## 注释
- 日期:YYYY-MM-DD
- 作者:姓名
- 审核者:姓名
ADR示例
示例1:数据库选择
# ADR-001: 使用PostgreSQL作为主数据库
## 状态
已接受(2024-01-15)
## 背景
我们需要为我们的电子商务平台选择一个数据库。需求:
- 订单处理需要ACID事务
- 报告需要复杂查询
- 可扩展到100K+用户
- 库存管理需要强一致性
- 团队有SQL经验
- 预算限制(优先开源)
## 决策
我们将使用PostgreSQL作为主数据库。
## 后果
### 正面
- ACID合规性确保金融交易的数据完整性
- 丰富的查询能力支持复杂报告需求
- 成熟的生态系统有广泛的工具和扩展
- 强大的社区支持和文档
- 团队已经熟悉SQL
- 免费开源(无需许可成本)
- 出色的性能满足我们的预期规模
### 负面
- 垂直扩展限制(虽然对我们的需求足够)
- 与托管服务相比,设置高可用性更复杂
- 需要事先仔细设计模式
### 风险
- 随着流量增长,可能需要添加读取副本
- 模式迁移需要仔细规划
- 如果查询没有优化,可能会出现性能问题
## 考虑的其他选项
### 选项1: MongoDB
- 优点:灵活的模式、水平扩展、JSON原生
- 缺点:一致性保证较弱、团队不熟悉NoSQL、对我们的结构化数据来说过于复杂
- 为什么拒绝:我们的数据高度结构化和关系化;ACID保证至关重要
### 选项2: Amazon DynamoDB
- 优点:完全托管、出色的可扩展性、可预测的性能
- 缺点:大规模时昂贵、供应商锁定、查询能力有限、学习曲线
- 为什么拒绝:成本问题和查询限制超过了可扩展性的好处
### 选项3: MySQL
- 优点:与PostgreSQL类似,广泛使用,性能良好
- 缺点:功能不如PostgreSQL丰富,JSON支持较弱
- 为什么拒绝:PostgreSQL在相同复杂性下提供更好的功能
## 参考
- [PostgreSQL文档](https://www.postgresql.org/docs/)
- [数据库比较电子表格](link-to-internal-doc)
- [团队讨论线程](link-to-slack/email)
## 注释
- 日期:2024-01-15
- 作者:Jane Smith
- 审核者:John Doe、Alice Johnson
- 下次审核:2025-01-15(或当扩展超过100K用户时)
示例2:认证策略
# ADR-002: 使用JWT和刷新令牌进行身份验证
## 状态
已接受(2024-01-20)
## 背景
我们需要一个适用于我们API的身份验证机制,该机制:
- 与我们的React前端和移动应用一起工作
- 支持无状态API服务器以实现水平扩展
- 提供合理的安全性
- 允许在需要时撤销令牌
- 在安全性和用户体验之间取得平衡
当前情况:
- 多种客户端类型(Web、iOS、Android)
- 微服务架构
- 需要水平扩展
- 安全性很重要但不是超高安全性(不是银行/医疗保健)
## 决策
我们将实现基于JWT的身份验证,带有刷新令牌:
- 短命的访问令牌(15分钟)
- 长命的刷新令牌(7天)
- 刷新令牌存储在数据库中以供撤销
- 访问令牌是无状态的(不存储)
## 后果
### 正面
- 无状态访问令牌实现水平扩展
- 在Web和移动设备上无缝工作
- 行业标准方法,有良好的库支持
- 可以通过刷新令牌失效来撤销访问
- 减少了数据库负载(仅在刷新时击中DB)
- 明确区分认证和授权
### 负面
- 无法立即撤销访问令牌(必须等待到期)
- 需要管理刷新令牌的存储和轮换
- 比基于会话的认证稍微复杂一些
- 如果不小心处理,令牌可能会被盗
- 需要在客户端实现令牌刷新逻辑
### 风险
- XSS攻击可能盗取令牌(通过Web的httpOnly cookies缓解)
- 令牌重放攻击(通过短到期缓解)
- 刷新令牌盗窃(通过轮换和安全存储缓解)
## 考虑的其他选项
### 选项1: 基于会话的身份验证
- 优点:易于实现,立即撤销,熟悉模式
- 缺点:需要粘性会话或共享会话存储,水平扩展不佳,与多个客户端复杂
- 为什么拒绝:不符合我们的微服务架构和扩展需求
### 选项2: OAuth 2.0与外部提供商(Auth0、Cognito)
- 优点:完全托管,经过实战测试,包括MFA和社交登录
- 缺点:供应商锁定,持续成本,控制较少,对我们的需求来说过于复杂
- 为什么拒绝:希望保持控制并避免在此阶段产生持续成本
### 选项3: API密钥
- 优点:简单,无状态
- 缺点:没有用户上下文,难以轮换,没有到期,安全问题
- 为什么拒绝:不支持用户特定权限,缺乏安全功能
### 选项4: 无刷新令牌的JWT
- 优点:实现更简单
- 缺点:要么长命令牌(安全风险)要么频繁重新认证(不良用户体验)
- 为什么拒绝:刷新令牌提供更好的安全/用户体验平衡
## 实施注释
- 使用RS256(非对称)签名,允许在没有共享密钥的情况下进行验证
- 在数据库中存储哈希刷新令牌
- 实施令牌轮换
- 设置适当的CORS和安全标头
- 对Web客户端使用httpOnly cookies
- 在认证端点实施速率限制
## 参考
- [JWT最佳实践](https://tools.ietf.org/html/rfc8725)
- [OWASP认证备忘单](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
- [Auth0博客关于刷新令牌](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/)
## 注释
- 日期:2024-01-20
- 作者:John Doe
- 审核者:Jane Smith、安全团队
- 实施目标:Sprint 5
- 审核触发:安全审计或扩展超过50K用户
ADR存储和版本控制
存储选项
-
在仓库中(推荐)
docs/adr/ ├── 0001-use-postgresql.md ├── 0002-jwt-authentication.md ├── 0003-graphql-api.md └── README.md -
Wiki/Confluence
- 便于发现
- 可能与代码脱节
-
专用工具
- log4brains
- ADR Manager
版本控制
- 使用顺序编号:
0001、0002等 - 永不删除ADRs(标记为弃用/取代)
- 链接相关ADRs
- 与代码一起保存在版本控制中
链接ADRs
取代
# ADR-015: 使用REST API代替GraphQL
## 状态
已接受(2024-06-01)
取代:ADR-003
## 背景
在GraphQL(ADR-003)使用4个月后,我们遇到了问题:
- 查询复杂性导致性能问题
- 团队在GraphQL概念上挣扎
- 第三方开发人员请求REST
- 缓存复杂性超过了好处
## 决策
恢复到经过精心设计的REST API...
弃用
# ADR-003: 为公共API使用GraphQL
## 状态
已弃用(2024-06-01)
被取代:ADR-015
## 弃用原因
GraphQL的复杂性超过了我们用例的好处。
请参阅ADR-015了解新方法。
[原始内容保留在下面以供历史参考]
ADR审查流程
- 起草 - 作者创建ADR
- 审查 - 团队审查和评论
- 讨论 - 处理反馈
- 决策 - 接受、拒绝或请求更改
- 实施 - 执行决策
- 回顾 - 在3-6个月后审查结果
工具
adr-tools
# 安装
npm install -g adr-tools
# 初始化
adr init docs/adr
# 创建新的ADR
adr new "使用PostgreSQL作为主数据库"
# 列出ADRs
adr list
# 生成图表
adr generate graph
MADR(Markdown ADR)
# 安装
npm install -g madr
# 创建ADR
madr new "数据库选择"
log4brains
# 安装
npm install -g log4brains
# 初始化
log4brains init
# 预览
log4brains preview
# 构建静态网站
log4brains build
写作风格
做✅
- 简洁明了
- 使用主动语态
- 包括日期和作者
- 列出考虑的其他选项
- 解释权衡
- 必要时使用图表
- 链接到参考
不要做❌
- 写小说(保持在2页以内)
- 使用没有解释的行话
- 跳过"为什么"
- 忽略其他选项
- 将其作为规范(ADR≠规范)
- 更新旧ADRs(相反,创建新的)
常见陷阱
- 太啰嗦 - 保持简洁
- 缺少上下文 - 总是解释为什么
- 没有其他选项 - 显示你考虑了选项
- 没有后果 - 列出利弊
- 太迟 - 在决策时编写,而不是实施后
- 太早 - 等到决策清晰
- 错误范围 - 不是每个决策都需要ADR
与文档集成
docs/
├── adr/ # 架构决策
├── api/ # API文档
├── guides/ # 如何指南
└── architecture/ # 架构图
交叉引用:
- 从架构文档中链接ADRs
- 在代码注释中引用ADRs
- 在PR描述中包含ADR链接
团队采用策略
- 从小开始 - 只从主要决策开始
- 以身作则 - 架构师编写第一个ADRs
- 使其简单 - 提供模板和工具
- 一起审查 - 在团队会议中讨论ADRs
- 庆祝 - 认可好的ADRs
- 迭代 - 根据反馈改进流程
最佳实践
- 在决策时编写 - 而不是实施后
- 保持简短 - 最多1-2页
- 诚实 - 包括负面和风险
- 展示其他选项 - 证明你考虑了选项
- 使用模板 - 一致性有助于可读性
- 版本控制 - 与代码一起保存
- 定期审查 - 定期重新审视决策
- 链接相关ADRs - 显示决策演变
- 包括日期 - 上下文随时间变化
- 使其可搜索 - 使用清晰的标题和标签