PPT 级技术架构图制作:从架构设计到可视化表达的完整工作流
一、深度引言与场景痛点
大家好,我是赵咕咕。
做了八年技术,我至少画过 200 张架构图。但坦白说,前 180 张都是不合格的——不是因为画得不好看,而是画的人没想清楚"给谁看"和"看什么"。
一张架构图,给老板看和给开发团队看,重点完全不同:
- 老板关心"整体结构、模块边界、数据流向"——三分钟内读懂。
- 开发团队关心"技术细节、接口定义、依赖关系"——精确到模块级别。
- 运维团队关心"部署拓扑、故障转移、监控节点"——看的是物理部署。
后来我总结了一套工作流:"先设计架构,再映射到图表",效果立竿见影。今天把方法分享出来——不是教你怎么用工具画图,而是教你怎么把架构思维表达成可视化图形。
二、底层机制与原理深度剖析
2.1 为什么大多数架构图都不合格?
常见的三类问题:
- 信息过载:一张图塞了 50 个组件,每个组件都有连线,密度太高没人看得懂。架构图不是 ER 图,应该控制 15-20 个核心元素。
- 缺少层次:所有组件平铺在一层,看不出主次关系。好的架构图有清晰的分层(用户层 → 网关层 → 业务层 → 数据层)。
- 连线混乱:箭头没有方向标记、没有标注协议、交叉线太多。架构图的连线不是随便拉的——REST/HTTP 是实线,消息队列是虚线,数据库连接是点线。
2.2 架构图的四层金字塔
2.3 架构图绘制的最佳实践十一条
- 左下到右上:数据流向从左下(输入)到右上(输出),这是人眼阅读习惯。
- 颜色编码:不超过 5 种颜色。红色 = 核心/瓶颈,蓝色 = 服务,绿色 = 存储,橙色 = 中间件,灰色 = 外部依赖。
- 分层标注:用虚线框标注层次,每层有名称。
- 连线协议标注:REST/gRPC/MQ/DB/Redis——每条线标注协议类型。
- 数字标注:标注 QPS、延迟、数据量——架构图加数字才有说服力。
- 不要标注所有连线:只标注"关键数据流",配置/监控/日志链路可以省略。
- 留白 30%:不要把图画满,密集的架构图没人看。
- 统一图标风格:全方框或全圆角,不要混用不同风格的图标。
- 版本号标注:架构图右下角标注日期和版本——架构是演进的。
- 一句话标题:图上方用一句话概括"这张图在表达什么"。
- Mermaid 优先:能用代码画的不用鼠标拖,Mermaid 可以版本控制、可以 diff、可以 CI 自动渲染。
三、生产级代码实现
3.1 用 Python 生成 Mermaid 架构图
手写 Mermaid 是可行的,但当组件超过 20 个时,手动维护很容易出错。更好的方式是用代码生成 Mermaid。
import asyncio import logging from dataclasses import dataclass, field from enum import Enum from typing import Any logger = logging.getLogger(__name__) class ComponentType(str, Enum): """组件类型。""" SERVICE = "service" # 微服务 DATABASE = "database" # 数据库 CACHE = "cache" # 缓存 QUEUE = "queue" # 消息队列 GATEWAY = "gateway" # 网关 EXTERNAL = "external" # 外部系统 STORAGE = "storage" # 对象存储 MONITOR = "monitor" # 监控 USER = "user" # 用户 class ConnectionType(str, Enum): """连接类型。""" HTTP = "http" GRPC = "grpc" MQ = "mq" DB = "db" CACHE_CONN = "cache" EVENT = "event" # 组件颜色映射 COLOR_MAP: dict[ComponentType, str] = { ComponentType.SERVICE: "#4A90D9", ComponentType.DATABASE: "#2ECC71", ComponentType.CACHE: "#E74C3C", ComponentType.QUEUE: "#F39C12", ComponentType.GATEWAY: "#9B59B6", ComponentType.EXTERNAL: "#95A5A6", ComponentType.STORAGE: "#1ABC9C", ComponentType.MONITOR: "#E67E22", ComponentType.USER: "#3498DB", } # 连线样式映射 CONNECTION_STYLE: dict[ConnectionType, str] = { ConnectionType.HTTP: "-->", ConnectionType.GRPC: "==>", ConnectionType.MQ: "-.->", ConnectionType.DB: "--o", ConnectionType.CACHE_CONN: "..->", ConnectionType.EVENT: "-.->>", } @dataclass class Component: """架构组件。""" name: str label: str comp_type: ComponentType layer: str = "" description: str = "" tech_stack: list[str] = field(default_factory=list) @dataclass class Connection: """组件间连接。""" source: str target: str conn_type: ConnectionType label: str = "" bidirectional: bool = False protocol: str = "" # e.g., "REST", "gRPC", "Kafka" class ArchitectureDiagram: """架构图生成器。 支持生成: - Mermaid flowchart(逻辑架构图) - Mermaid C4 Context(系统上下文图) """ def __init__(self, title: str, version: str = "v1.0"): self._title = title self._version = version self._components: dict[str, Component] = {} self._connections: list[Connection] = [] self._layers: dict[str, list[str]] = {} def add_component(self, comp: Component) -> None: """添加组件。""" self._components[comp.name] = comp if comp.layer: if comp.layer not in self._layers: self._layers[comp.layer] = [] self._layers[comp.layer].append(comp.name) def add_connection(self, conn: Connection) -> None: """添加连接。""" # 验证源和目标组件是否存在 if conn.source not in self._components: logger.warning("源组件不存在: %s", conn.source) if conn.target not in self._components: logger.warning("目标组件不存在: %s", conn.target) self._connections.append(conn) def build_mermaid(self) -> str: """构建 Mermaid flowchart 代码。""" lines = [ "```mermaid", "flowchart TB", ] # 分层 if self._layers: for layer_name, comp_names in self._layers.items(): safe_name = layer_name.replace(" ", "_") lines.append(f" subgraph {safe_name}[\"{layer_name}\"]") for name in comp_names: comp = self._components[name] color = COLOR_MAP.get(comp.comp_type, "#AAAAAA") lines.append( f" {name}[\"{comp.label}" ) if comp.tech_stack: tech = ", ".join(comp.tech_stack[:3]) lines[-1] += f"<br/><small>{tech}</small>" lines[-1] += f"\"]" lines.append(f" style {name} fill:{color},color:#fff") lines.append(" end") lines.append("") else: # 无分层,直接列出组件 for name, comp in self._components.items(): color = COLOR_MAP.get(comp.comp_type, "#AAAAAA") lines.append(f" {name}[\"{comp.label}\"]") lines.append(f" style {name} fill:{color},color:#fff") # 连接 for conn in self._connections: style = CONNECTION_STYLE.get(conn.conn_type, "-->") label = f"|{conn.label}|" if conn.label else "" protocol = f"<br/>{conn.protocol}" if conn.protocol else "" if conn.bidirectional: lines.append( f" {conn.source} {style}{label} {conn.target}" ) lines.append( f" {conn.target} {style} {conn.source}" ) else: lines.append( f" {conn.source} {style}{label} {conn.target}" ) # 版本标注 lines.append("") lines.append( f" %% {self._title} | {self._version} | " f"Generated by ArchitectureDiagram" ) lines.append("```") return "\n".join(lines) def build_c4_context(self) -> str: """构建 C4 系统上下文图(简化版)。""" lines = [ "```mermaid", "C4Context", f" title {self._title}", ] # 用户 users = [ c for c in self._components.values() if c.comp_type == ComponentType.USER ] for user in users: lines.append( f" Person({user.name}, \"{user.label}\")" ) # 系统 services = [ c for c in self._components.values() if c.comp_type == ComponentType.SERVICE ] for svc in services: lines.append( f" System({svc.name}, \"{svc.label}\")" ) # 外部系统 externals = [ c for c in self._components.values() if c.comp_type == ComponentType.EXTERNAL ] for ext in externals: lines.append( f" System_Ext({ext.name}, \"{ext.label}\")" ) # 关系 for conn in self._connections: label = conn.label or conn.conn_type.value lines.append( f" Rel({conn.source}, {conn.target}, \"{label}\")" ) lines.append("```") return "\n".join(lines) def build_sequence(self, title: str, steps: list[dict[str, str]]) -> str: """构建时序图。""" lines = [ "```mermaid", "sequenceDiagram", f" title {title}", ] participants = set() for step in steps: participants.add(step["from"]) participants.add(step["to"]) for p in sorted(participants): lines.append(f" participant {p}") lines.append("") for step in steps: from_p = step["from"] to_p = step["to"] msg = step["message"] arrow = step.get("arrow", "->>") lines.append(f" {from_p} {arrow} {to_p}: {msg}") lines.append("```") return "\n".join(lines) def build_er_diagram(self, tables: list[dict[str, Any]]) -> str: """构建 ER 图。""" lines = [ "```mermaid", "erDiagram", ] for table in tables: name = table["name"] lines.append(f" {name} {{") for col in table.get("columns", []): col_type = col.get("type", "string") col_key = "" if col.get("primary_key"): col_key = " PK" elif col.get("foreign_key"): col_key = " FK" lines.append( f" {col_type} {col['name']}{col_key}" ) lines.append(" }") lines.append("") # 关系 for table in tables: for rel in table.get("relations", []): lines.append( f" {table['name']} ||--o{{ {rel['target']} : \"{rel.get('label', '')}\"" ) lines.append("```") return "\n".join(lines) def save(self, filepath: str, diagram_type: str = "flowchart") -> None: """保存 Mermaid 图到文件。""" if diagram_type == "flowchart": content = self.build_mermaid() elif diagram_type == "c4": content = self.build_c4_context() else: content = self.build_mermaid() from pathlib import Path Path(filepath).parent.mkdir(parents=True, exist_ok=True) Path(filepath).write_text(content, encoding="utf-8") logger.info("架构图已保存: %s", filepath) # ─── 使用示例:构建 RAG 服务架构图 ─── async def main(): diagram = ArchitectureDiagram( title="RAG 检索增强生成服务架构", version="v2.3.0", ) # 添加组件 diagram.add_component(Component( name="user", label="用户", comp_type=ComponentType.USER, layer="用户层", )) diagram.add_component(Component( name="gateway", label="API Gateway\nKong/Nginx", comp_type=ComponentType.GATEWAY, layer="接入层", tech_stack=["Kong", "Nginx"], )) diagram.add_component(Component( name="rag_service", label="RAG 检索服务\nFastAPI", comp_type=ComponentType.SERVICE, layer="业务层", tech_stack=["FastAPI", "Python 3.11", "asyncio"], )) diagram.add_component(Component( name="embedding", label="Embedding 服务\nvLLM", comp_type=ComponentType.SERVICE, layer="业务层", tech_stack=["vLLM", "bge-large-zh"], )) diagram.add_component(Component( name="redis", label="Redis\n缓存 + 向量", comp_type=ComponentType.CACHE, layer="数据层", tech_stack=["Redis Stack"], )) diagram.add_component(Component( name="milvus", label="Milvus\n向量数据库", comp_type=ComponentType.DATABASE, layer="数据层", tech_stack=["Milvus 2.4"], )) diagram.add_component(Component( name="llm_api", label="LLM API\nGPT-4o", comp_type=ComponentType.EXTERNAL, layer="外部依赖", tech_stack=["OpenAI"], )) # 添加连接 diagram.add_connection(Connection( source="user", target="gateway", conn_type=ConnectionType.HTTP, protocol="HTTPS", )) diagram.add_connection(Connection( source="gateway", target="rag_service", conn_type=ConnectionType.HTTP, protocol="REST", )) diagram.add_connection(Connection( source="rag_service", target="embedding", conn_type=ConnectionType.GRPC, protocol="gRPC", label="文本→向量", )) diagram.add_connection(Connection( source="rag_service", target="redis", conn_type=ConnectionType.CACHE_CONN, protocol="Redis", label="查询缓存", )) diagram.add_connection(Connection( source="rag_service", target="milvus", conn_type=ConnectionType.DB, protocol="gRPC", label="向量检索", )) diagram.add_connection(Connection( source="rag_service", target="llm_api", conn_type=ConnectionType.HTTP, protocol="HTTPS", label="生成回答", )) # 保存 diagram.save("/tmp/rag_architecture.md", "flowchart") # 也可以生成 C4 图 diagram.save("/tmp/rag_c4.md", "c4") # 打印 Mermaid 代码 print(diagram.build_mermaid()) if __name__ == "__main__": asyncio.run(main())代码的关键设计:
- 组件 + 连接模型:用
Component和Connection两类数据结构描述架构,然后渲染为 Mermaid。这比直接在 Mermaid 里写更容易维护,而且可以从同一个数据模型生成多种图表(flowchart、C4、时序图)。 - 颜色编码自动化:组件类型自动映射颜色。数据库永远是绿色,缓存永远是红色——不需要每次手动选颜色。
- 连线样式自动化:连接类型自动映射 Mermaid 箭头样式。HTTP 用实线箭头
-->,消息队列用虚线-.->,一目了然。 - 分层支持:
_layers字典将组件分组到不同的 subgraph 中,自动生成分层结构。 - 多图导出:同一份架构数据可以导出为 flowchart、C4、时序图、ER 图。
四、边界分析与架构权衡
4.1 用代码生成架构图 vs 手画
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手画(Draw.io/Figma) | 自由度高,好看 | 不可版本管理,不可自动化 | 一次性汇报,不需要更新 |
| Mermaid 手写 | 版本管理,diffable | 手动维护,组件多时容易乱 | 小团队,组件 < 20 |
| 代码生成 Mermaid | 自动化,数据驱动 | 灵活性受限 | 大项目,架构频繁变更 |
| PlantUML | 功能强 | 语法复杂 | 需要 C4 标准图 |
推荐组合:代码生成 + Mermaid。架构数据从 CMDB 或配置文件中来,每次变更自动更新架构图。避免"架构图跟不上代码"的老问题。
4.2 架构图的版本管理
架构图和代码一样需要版本管理。建议:
- Mermaid 源文件放在 Git 仓库的
docs/architecture/目录。 - CI 自动渲染为 PNG/SVG,附在 Release Notes 中。
- 每次架构变更 PR 必须更新对应的架构图。
4.3 给不同受众的不同视角
| 受众 | 图表类型 | 关键数据 |
|---|---|---|
| CTO/VP | C4 系统上下文图 | 系统边界 + 数据流向 |
| Tech Lead | 逻辑架构图 | 模块划分 + 接口 |
| 开发团队 | 时序图 + API 文档 | 调用流程 + 参数 |
| SRE/运维 | 部署拓扑图 | 故障转移 + 监控 |
| 数据团队 | 数据流图 | ETL + 存储 |
4.4 Mermaid 的局限性
Mermaid 不适合的场景:
- 需要精确位置的图(Mermaid 是自动布局)
- 需要大量自定义图标的图
- 需要拖拽交互的图
- 商业级别的精美图表(建议用 Draw.io 或 Lucidchart)
但 Mermaid 在"版本管理 + 自动化 + 快速迭代"这三个维度上是无可替代的。
五、总结
架构图绘制不是画图技能问题,是架构思维的可视化表达能力问题。
核心方法论:
- 先分层,再填充:一张好的架构图,自上而下至少有 3-4 个清晰的逻辑层。
- 用代码管理架构图:Mermaid + Python 代码生成,把架构图纳入 Git 版本管理。
- 颜色和连线有语义:不是随机选颜色——红色 = 瓶颈,绿色 = 存储,虚线 = 异步。
- 给不同受众画不同的图:老板看大局,开发看接口,运维看拓扑。
- 架构图是活的:架构在演进,图必须跟随更新。做到"代码变了,图自动变"是终极目标。
一张好的架构图,能让新成员 10 分钟内理解系统,能让老板 3 分钟内做出技术决策,能让运维 1 分钟内定位故障。这不是画图的问题,是沟通效率的问题。
下一篇预告:Agent 商业化的五个坑——技术之外还需要考虑的合规、成本和用户。