架构决策记录(ADR)总览
架构决策记录(Architecture Decision Record,ADR)用于捕捉 EnerOS 项目中对架构有持续影响的关键决策。每条 ADR 记录决策的背景、动机、结论与后果,便于团队复盘与新成员理解设计脉络。
ADR 不是设计文档,也不是实现说明,而是回答”为什么在当时做出了这个选择”。当代码无法解释决策动机时,ADR 是最后的依据。EnerOS 是一套面向电力关键基础设施的操作系统,许多决策受合规、物理约束与长期演进影响,必须显式记录,否则随着人员更迭,决策原因会被湮没。
为什么需要 ADR
电力系统的软件开发具有以下特征,使得 ADR 尤为重要:
- 长生命周期:电力系统投运后通常运行 10-30 年,决策影响远超个人在岗周期
- 强合规约束:需同时满足 IEC 62443、NERC CIP、等保 2.0 等多套标准
- 物理不可逆:错误的遥控命令可能造成设备损坏或大面积停电
- 多方协作:电网公司、设备厂商、监管机构、研究机构均需理解系统设计
- 技术演进快:AI、边缘计算、量子安全等新技术持续冲击既有架构
ADR 通过结构化记录决策上下文,让新加入的工程师在 30 分钟内理解”为什么内核要强制执行约束”、“为什么所有内部通信必须 mTLS”等关键问题,避免无意识推翻既定决策。
ADR 格式
EnerOS 采用 Michael Nygard 提出的轻量级 ADR 模板,并扩展为六节结构。每条 ADR 必须包含以下章节:
| 章节 | 必填 | 内容 |
|---|---|---|
| 状态(Status) | 是 | 提议 / 已接受 / 已废弃 / 已被取代 |
| 背景(Context) | 是 | 决策时面临的问题、约束与动机 |
| 决策(Decision) | 是 | 最终选择的方案及核心理由 |
| 后果(Consequences) | 是 | 带来的好处、代价与后续工作 |
| 备选方案(Alternatives) | 是 | 考虑过但未采用的方案及否决原因 |
| 参考(References) | 否 | 相关 ADR、标准、论文、Issue 链接 |
状态机
ADR 状态遵循以下流转规则:
提议 (Proposed)
│
├─ 评审通过 ──► 已接受 (Accepted)
│ │
│ ├─ 后续 ADR 取代 ──► 已被取代 (Superseded)
│ ├─ 严重问题暴露 ──► 已废弃 (Deprecated)
│ └─ 长期有效
│
└─ 评审拒绝 ──► 已拒绝 (Rejected)
一旦状态变为”已接受”,原文件不再修改内容,只能被新的 ADR 取代。这是为了保证历史可追溯——任何决策的演进都通过新 ADR 显式记录。
ADR 列表
下表是 EnerOS 当前所有 ADR 的索引。编号从 0001 起,按时间递增,不复用。缺失的编号代表被拒绝或撤回的提议,仅保留编号占位以维持时序完整。
| 编号 | 标题 | 状态 | 日期 | 关键决策 |
|---|---|---|---|---|
| 0002 | Power-Native AgentOS 设计 | 已接受 | 2024-03 | 电力领域知识下沉为内核一等公民 |
| 0007 | 零信任与 mTLS 强制 | 已接受 | 2024-09 | 所有内部服务通信强制 mTLS 双向认证 |
| 0010 | Agent 智能进阶路径 | 已接受 | 2025-03 | 内核内置智能层,规则/模型/LLM 可插拔 |
注:编号 0001、0003-0006、0008-0009 的 ADR 涉及内部组件选型(如时序存储引擎、调度器实现、事件总线协议等),将在后续版本公开后逐步补全索引。
ADR 模板
新建 ADR 时,复制以下模板并按字段填写:
---
title: "ADR-NNNN 标题"
description: "一句话描述决策内容"
category: "架构决策记录"
order: NNNN
---
# ADR-NNNN 标题
- **状态(Status)**:提议 | 已接受 | 已废弃 | 已被取代
- **日期**:YYYY-MM
- **相关概念**:[关联文档链接](/docs/...)
## 背景(Context)
描述决策时面临的问题、约束、动机。包括:
- 触发该决策的具体场景或痛点
- 当时的技术约束(性能、合规、团队规模等)
- 不做该决策的代价
## 决策(Decision)
用 1-3 句话陈述最终选择。例如:
> 我们决定将 X 下沉为内核原生组件,由 Y 提供统一接口,强制 Z。
随后展开 3-5 条核心理由,每条理由配 1-2 句解释。
## 后果(Consequences)
分三类列出:
**好处**:
- 好处 1
- 好处 2
**代价**:
- 代价 1
- 代价 2
**后续工作**:
- 待办 1
- 待办 2
## 备选方案(Alternatives)
列出考虑过但未采用的方案,每个方案包含:
### 方案 A:名称
- **描述**:方案概述
- **优点**:方案优点
- **否决原因**:为何未采用
## 参考(References)
- [相关 ADR](/docs/adr/NNNN-xxx)
- [标准/论文/Issue 链接]
- 相关代码 crate 名称
编写指南
何时写 ADR
满足以下任一条件即应撰写 ADR:
- 引入或移除一个内核一等公民抽象(如拓扑、约束、设备模型)
- 改变系统的安全边界或信任模型
- 选择某项影响全局的第三方依赖或协议
- 跨多个 crate 的接口变更
- 决策可能被未来的工程师质疑或推翻
- 涉及合规、性能、可用性的权衡取舍
何时不写 ADR
以下情况不需要写 ADR:
- 单个 crate 内部实现细节(应写在代码注释或 crate 级 README)
- Bug 修复(应写在 commit message 或 PR 描述)
- 文档完善(应直接修改文档)
- 临时实验性功能(应标记为 experimental,稳定后再考虑 ADR)
编写原则
- 一事一 ADR:一条决策对应一份文件,不要把多个决策合并
- 不可变:状态变为”已接受”后内容不再修改,只能被新 ADR 取代
- 可追溯:每个决策必须能回答”为什么不是另一个方案”
- 简洁:正文控制在 200-400 行,长篇分析放附录或独立文档
- 可验证:决策带来的后果应尽量给出可测量的指标(如延迟、吞吐)
- 关联:在前后相关的 ADR 之间建立显式引用
决策流程
EnerOS 的架构决策遵循以下流程,确保决策既严谨又高效:
┌──────────────┐
│ 1. 识别决策点 │ ← 工程师或架构师发起
└──────┬───────┘
▼
┌──────────────┐
│ 2. 起草 ADR │ ← 使用模板,状态为"提议"
└──────┬───────┘
▼
┌──────────────┐
│ 3. 公开评审 │ ← PR 形式,至少 2 名架构师审查
└──────┬───────┘
▼
┌──────────────┐
│ 4. 评审结论 │ ← 通过 / 拒绝 / 需修改
└──────┬───────┘
▼
┌──────────────┐
│ 5. 状态更新 │ ← 通过则"已接受",否则"已拒绝"
└──────┬───────┘
▼
┌──────────────┐
│ 6. 落地实施 │ ← 代码实现 + 测试 + 文档同步
└──────┬───────┘
▼
┌──────────────┐
│ 7. 复盘 │ ← 上线 3-6 个月后评估决策效果
└──────────────┘
评审角色
| 角色 | 职责 | 人数 |
|---|---|---|
| 发起人 | 起草 ADR,推动评审 | 1 |
| 架构师 | 评估技术合理性、与既有 ADR 一致性 | ≥2 |
| 安全官 | 评估安全与合规影响 | 1(涉及安全时) |
| 领域专家 | 评估电力领域正确性 | 1(涉及电力时) |
| 实施者 | 评估落地成本与工期 | 1 |
评审标准
评审时重点关注以下维度:
- 必要性:是否真的需要这个决策?维持现状的代价是什么?
- 充分性:备选方案是否考虑周全?否决理由是否成立?
- 一致性:与既有 ADR 是否冲突?是否引入新的矛盾?
- 可逆性:决策是否可逆?如果被取代,迁移成本如何?
- 可验证性:决策效果是否可测量?如何评估?
ADR 与其他文档的关系
EnerOS 的文档体系按”是什么 / 为什么 / 怎么做”三层组织,ADR 处于”为什么”层:
| 文档类型 | 回答 | 示例 | 变更频率 |
|---|---|---|---|
| 概念文档 | 是什么 | Power-Native First | 低 |
| 架构文档 | 怎么组织 | 分层架构 | 中 |
| ADR | 为什么这么决定 | 本文档 | 极低(一旦接受不可变) |
| 能力文档 | 能做什么 | Agent 智能进阶 | 中 |
| 操作指南 | 怎么用 | 首个 Agent | 高 |
| API 参考 | 接口签名 | API Reference | 高 |
ADR 是其他文档的”上游”:概念文档解释 ADR 引入的术语,架构文档实现 ADR 的决策,操作指南教用户使用 ADR 决定的能力。修改 ADR 时应同步检查下游文档是否需要更新。
编写约定
- 编号从 0001 起,按时间递增,不复用
- 一条决策对应一份文件,文件名格式
NNNN-kebab-case-title.md - 决策一旦被取代,原文件保留并标注
Status: 已被取代,新增 ADR 引用前置编号 - ADR 仅记录”为什么这么决定”,不重复写实现细节
- 日期精确到月份即可,无需到日
- 正文使用中文,技术术语可保留英文(如 mTLS、SPIFFE、RBAC)
- 代码示例必须完整可运行,不要用
// ...省略关键逻辑 - 表格必须包含完整的字段说明,不留空列
工具支持
EnerOS 仓库提供以下工具辅助 ADR 管理:
scripts/adr-new.sh:基于模板创建新 ADR,自动分配编号scripts/adr-check.sh:检查所有 ADR 的格式与链接是否有效scripts/adr-graph.sh:生成 ADR 之间的引用关系图
示例用法:
# 创建新 ADR
./scripts/adr-new.sh "timeseries-engine-selection"
# 检查所有 ADR
./scripts/adr-check.sh
# 生成关系图
./scripts/adr-graph.sh --output docs/adr/graph.svg