技术决策记录:用图表辅助做架构选型的技术决策文档模板 技术决策记录用图表辅助做架构选型的技术决策文档模板一、深度引言与场景痛点你有没有遇到过这种场景团队花了三周时间选型消息队列最终定了 Kafka。三个月后新人入职问为什么不直接用 RabbitMQ你翻遍 Slack 聊天记录和会议纪要只找到一句Kafka 吞吐高。新人追问多高比 RabbitMQ 高多少我们现在的量需要这么高吗你答不上来——因为你忘了当初的决策依据。这就是技术决策记录Architecture Decision RecordADR存在的理由。它不是事后补的文档而是决策当下留下的第一手记录我们面临什么问题、考虑了哪些方案、为什么选 A 不选 B、预期后果是什么。ADR 的价值不在决策本身而在于让后来者能追溯决策的上下文避免前人挖坑后人填的信息断代。但现实中的 ADR 质量参差不齐。有的只有一句话有的长篇大论全是主观判断有的遗漏了关键备选方案。一个好的 ADR 应该结构化、可对比、有图表辅助——图表让不同方案的优劣一目了然比纯文字主观描述有力得多。二、底层机制与原理深度剖析决策矩阵是 ADR 的核心对比组件。以运维复杂度、功能丰富度和吞吐能力评估候选方案时Kafka 处于高吞吐区但运维复杂Redis Streams 更简单但吞吐和功能不如 KafkaPulsar 吞吐最高但复杂度也最高。一个完整的 ADR 包含六个部分标题简洁描述决策内容如ADR-007选择 Kafka 作为 Agent 事件总线背景与问题当前面临的技术挑战为什么必须做这个决策有哪些约束条件时间、人力、预算决策最终选了什么为什么不选其他方案。这是 ADR 的核心部分需要用数据支撑决策矩阵用对比表格把候选方案的评分量化呈现后果正面后果解决了什么问题、带来了什么收益和负面后果引入了什么复杂度、有哪些技术债备选方案那些认真评估过但最终放弃的方案以及放弃的原因三、生产级代码实现import json import textwrap from dataclasses import dataclass, field from datetime import datetime, timezone from enum import Enum from typing import Any class DecisionStatus(str, Enum): PROPOSED proposed # 提议中 ACCEPTED accepted # 已通过 DEPRECATED deprecated # 已废弃 SUPERSEDED superseded # 被替代 class ImpactLevel(str, Enum): HIGH high MEDIUM medium LOW low dataclass class CriterionScore: 单项评分 criterion: str # 评分维度如 吞吐量 weight: float # 权重 0-1 scores: dict[str, float] # 方案名 - 分数 1-10 rationale: str # 评分理由 dataclass class Alternative: 备选方案 name: str description: str pros: list[str] field(default_factorylist) cons: list[str] field(default_factorylist) reason_rejected: str dataclass class ArchitectureDecisionRecord: 技术决策记录ADR id: str # 如 ADR-007 title: str status: DecisionStatus date: str field(default_factorylambda: datetime.now(timezone.utc).strftime(%Y-%m-%d)) author: str # 背景 context: str # 业务背景和约束 problem: str # 要解决的具体问题 # 决策 decision: str # 最终选择 criteria: list[CriterionScore] field(default_factorylist) alternatives: list[Alternative] field(default_factorylist) # 后果 positive_consequences: list[str] field(default_factorylist) negative_consequences: list[str] field(default_factorylist) risks: list[str] field(default_factorylist) # 元数据 superseded_by: str # 被哪个 ADR 替代 related_adrs: list[str] field(default_factorylist) tags: list[str] field(default_factorylist) def render_markdown(self) - str: 渲染为 Markdown 格式 lines [ f# {self.id}: {self.title}, , f| 属性 | 值 |, f| :--- | :--- |, f| 状态 | {self.status.value} |, f| 日期 | {self.date} |, f| 影响范围 | — |, , ## 一、背景与问题, , self.context, , f**核心问题**{self.problem}, , ## 二、决策, , self.decision, , ] if self.criteria: lines.append(### 决策矩阵) lines.append() # 表头 alt_names list(self.criteria[0].scores.keys()) header | 评分维度权重 | | .join(alt_names) | sep | :--- | | .join([:---:] * len(alt_names)) | lines.append(header) lines.append(sep) for c in self.criteria: row f| {c.criterion} ({c.weight:.0%}) | row | .join(f{c.scores.get(a, 0):.1f} for a in alt_names) row | lines.append(row) # 加权总分 lines.append() totals: dict[str, float] {} for a in alt_names: totals[a] sum( c.weight * c.scores.get(a, 0) for c in self.criteria ) total_row | **加权总分** | | .join(f**{totals[a]:.1f}** for a in alt_names) | lines.append(total_row) lines.append() if self.positive_consequences: lines.append(### 正面后果) for c in self.positive_consequences: lines.append(f- {c}) lines.append() if self.negative_consequences: lines.append(### 负面后果与风险) for c in self.negative_consequences: lines.append(f- {c}) for r in self.risks: lines.append(f- **风险**{r}) lines.append() if self.alternatives: lines.append(## 三、已评估的备选方案) lines.append() for alt in self.alternatives: lines.append(f### {alt.name}) lines.append(f**描述**{alt.description}) if alt.pros: lines.append(f**优势**{、.join(alt.pros)}) if alt.cons: lines.append(f**劣势**{、.join(alt.cons)}) lines.append(f**放弃原因**{alt.reason_rejected}) lines.append() return \n.join(lines) def to_json(self) - str: 序列化为 JSON return json.dumps(self.__dict__, defaultstr, ensure_asciiFalse, indent2) def create_kafka_adr() - ArchitectureDecisionRecord: 示例创建一条消息队列选型的 ADR return ArchitectureDecisionRecord( idADR-007, title选择 Kafka 作为 Agent 事件总线, statusDecisionStatus.ACCEPTED, contexttextwrap.dedent(\ Agent 系统需要事件总线来支撑以下场景 1. Agent 间异步通信任务派发、结果回传 2. 对话事件的持久化与重放审计、调试 3. 多 Agent 协作时的消息顺序保证 系统的目标吞吐为 5000 msg/s要求消息持久化且可回溯。 团队有 Kafka 运维经验但对 Pulsar 和 NATS 不熟悉。\ ), problem选出满足吞吐和持久化要求、运维成本可接受的事件总线方案, decision选用 Kafka基于 KRaft 模式部署去除 Zookeeper 依赖Topic 按 Agent 会话 ID 分区保证同一会话消息有序, criteria[ CriterionScore( criterion吞吐能力, weight0.30, scores{Kafka: 9, RabbitMQ: 5, Redis Streams: 7, NATS: 7, Pulsar: 9}, rationale基于公开 benchmark 和团队 POC 测试, ), CriterionScore( criterion运维复杂度, weight0.25, scores{Kafka: 6, RabbitMQ: 7, Redis Streams: 9, NATS: 8, Pulsar: 4}, rationale考虑集群搭建、监控、故障恢复的综合成本, ), CriterionScore( criterion消息持久化, weight0.20, scores{Kafka: 9, RabbitMQ: 8, Redis Streams: 5, NATS: 6, Pulsar: 9}, rationale持久化能力包括磁盘存储、消息回溯、TTL 控制, ), CriterionScore( criterion生态集成, weight0.15, scores{Kafka: 9, RabbitMQ: 8, Redis Streams: 7, NATS: 5, Pulsar: 6}, rationalePython 客户端成熟度、监控工具链、社区活跃度, ), CriterionScore( criterion团队熟悉度, weight0.10, scores{Kafka: 8, RabbitMQ: 6, Redis Streams: 9, NATS: 3, Pulsar: 3}, rationale考虑团队学习成本、线上故障处理经验, ), ], alternatives[ Alternative( nameRabbitMQ, description成熟的消息队列支持多种协议, pros[运维成熟度高, 灵活的路由策略], cons[吞吐上限较低单队列约 5K msg/s, 集群模式运维复杂], reason_rejected吞吐量无法满足未来 3 倍增长的预留空间, ), Alternative( nameRedis Streams, description基于 Redis 的轻量级流处理, pros[部署简单已有 Redis, 延迟极低], cons[消息持久化受内存限制, 无原生消费者组 Rebalance], reason_rejected缺少消息回溯和长期持久化的能力, ), Alternative( namePulsar, description下一代消息系统存算分离架构, pros[吞吐与 Kafka 相当, 存算分离扩展性好], cons[运维复杂度高, Python 客户端不够成熟], reason_rejected团队学习和运维成本过高短期无存算分离需求, ), ], positive_consequences[ Agent 间消息传递有了统一的可靠通道, 消息持久化支持对话审计和故障回溯, 分区机制保证同一会话的消息有序处理, ], negative_consequences[ 引入了 Kafka 集群的运维负担监控、扩容、故障恢复, Python 异步 Kafka 客户端的选择和调优需要额外投入, ], risks[ KRaft 模式在极端故障下的稳定性仍需验证, 如果会话数远超分区数热点分区可能导致负载不均, ], tags[消息队列, 事件驱动, 架构选型], ) def main() - None: adr create_kafka_adr() md adr.render_markdown() print(md) # 也可写入文件 with open(/tmp/adr-007-kafka.md, w) as f: f.write(md) print(\nADR 已写入 /tmp/adr-007-kafka.md) if __name__ __main__: main()代码里的render_markdown方法把结构化的 ADR 数据自动渲染成人类可读的 Markdown 文档包括决策矩阵表格和加权总分。加权评分机制让决策过程可量化、可追溯——即使新人质疑为什么选 Kafka也能看到每个维度的评分和权重理解当时的权衡逻辑。备选方案的放弃原因字段确保不是没考虑而是考虑了但不行。四、边界分析与架构权衡ADR 最大的挑战不是写而是维护。写多了变成文档债50 条 ADR 里只有 10 条还在生效写少了起不到追溯作用。一个实用的粒度判断标准如果一个决策会影响两个以上团队的工作方式或者一个决策在三个月后可能被质疑就写 ADR。框架选型、通信协议、数据模型、部署策略属于这个范畴。单个组件的实现细节不必写 ADR——那是代码注释和设计文档的职责。ADR 的废弃管理很重要。当一条 ADR 被新的决策替代时不要删除旧的——把它标记为superseded并在元数据里填写superseded_by指向新 ADR。这样决策演化链是完整的可以看到技术栈是如何一步步演进到今天的状态。图表在 ADR 中的角色是一眼看清全局——Mermaid 的象限图、雷达图和流程图比文字罗列直观得多。但图表不能替代文字——关键维度如具体性能数据、成本估算必须在文字中给出精确数字图表只负责呈现相对关系。五、总结技术决策记录ADR解决的是架构决策的可追溯性问题。结构化模板背景→决策→矩阵→后果→备选让决策过程可审计加权评分矩阵让权衡逻辑可量化Mermaid 图表让方案对比一目了然。核心原则是轻量、及时、在决策当下记录而不是事后补。一条好的 ADR 让三个月后质疑为什么这样选的人看完就能理解当时的约束和取舍。