ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

diagram-design 实战:如何用一张图讲清楚复杂系统架构

diagram-design 实战:如何用一张图讲清楚复杂系统架构

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题

第一次看到“diagram-design”这个标题,很多人会以为它只是教你怎么画流程图、时序图或者架构图。但真正做过技术方案、写过设计文档、给团队做过架构评审的人都知道,画图这件事远没有想象中那么简单。它背后牵扯的是一整套信息表达、逻辑梳理、视觉降噪和协作沟通的方法论。我见过太多项目,代码写得漂漂亮亮,一到画图环节就露怯:架构图里箭头乱飞、模块边界模糊、数据流向和调用关系混在一起,评审会上讲了十分钟,听众还是一头雾水。

diagram-design 这个主题,核心要解决的就是“如何把复杂系统用一张图讲清楚”的问题。它适合的人群非常广:后端工程师需要画服务依赖图,前端工程师需要画组件树和状态流转图,产品经理需要画业务流程图,技术写作者需要画原理示意图,甚至运维同学也需要画部署拓扑图。不管你是刚入行的新手,还是带过几十人团队的技术负责人,只要你的工作里涉及“把想法可视化”,这套东西就值得花时间研究。

我自己的经历比较典型。早些年做微服务改造,画了一张所谓的“架构图”,结果被评审专家一句话问住:“你这个网关到用户服务之间的箭头,到底代表同步调用还是异步消息?”当时我就意识到,图不是画给自己看的,它是团队共识的载体。一张合格的 diagram,必须做到三件事:第一,让不懂技术的人也能看懂大致流程;第二,让懂技术的人能快速定位关键路径;第三,让未来的自己或接手的人能顺着图还原设计意图。这三件事听起来简单,做起来需要一套系统的方法。

接下来的内容,我会从设计思路、核心细节、实操过程、常见问题四个维度,把 diagram-design 这件事拆开揉碎。我会解释为什么有些图一看就专业,有些图怎么看怎么别扭;会给出具体的工具选型建议和参数配置;也会分享我在实际项目中踩过的坑和总结出的技巧。无论你习惯用哪种绘图工具,这些原则都是通用的。

2. 内容整体设计与思路拆解

2.1 为什么“画什么”比“怎么画”更重要

很多人一上来就纠结用什么工具:是 draw.io 还是 Excalidraw,是 PlantUML 还是 Mermaid。工具当然重要,但 diagram-design 的第一性原理是:先想清楚这张图要回答什么问题。我习惯把图分成四类,每类的设计目标完全不同。

第一类是结构图,比如系统架构图、组件图、部署图。它的核心是回答“系统由哪些部分组成,它们之间是什么关系”。这类图的关键在于边界清晰、层次分明。我通常会把图分成三层:最上层是接入层,中间是业务层,底层是数据层。每一层内部的模块用矩形表示,跨层的调用用带箭头的实线表示,异步消息用虚线表示。这样一眼就能看出哪些是同步依赖,哪些是解耦的。

第二类是行为图,比如流程图、时序图、状态机图。它的核心是回答“事情是怎么一步步发生的”。这类图最怕的是把异常分支和正常流程混在一起。我的做法是:主流程用粗线,异常分支用细线,并且把异常处理单独放在一个区域。时序图则要注意生命线的对齐,消息的先后顺序必须严格从上到下,不能为了省空间把两条消息画在同一水平线上。

第三类是数据图,比如 ER 图、数据流图。它的核心是回答“数据从哪里来,经过哪些处理,最终到哪里去”。这类图的关键是字段级别的准确性。我见过很多 ER 图只画了表名和关系,没有标注关键字段和索引,结果开发的时候还得回去翻数据库设计文档。更好的做法是在图上直接标注主键、外键和重要字段,虽然图会大一些,但信息密度高,后续沟通成本低。

第四类是概念图,比如思维导图、业务全景图。它的核心是回答“这个领域里有哪些关键概念,它们怎么关联”。这类图不需要太精确,但要求覆盖全面、层级合理。我通常用中心辐射式布局,中心是核心概念,周围是子概念,再用不同颜色区分不同的业务域。

注意:不要试图用一张图回答所有问题。我见过有人把架构图、流程图、ER 图塞进同一张画布,结果就是谁也看不懂。正确的做法是分图表达,每张图只回答一个核心问题,图与图之间用编号或链接关联。

2.2 视觉降噪:让图“会说话”的五个原则

确定了画什么之后,下一步是解决“怎么画得清楚”。我总结了五个视觉降噪原则,这些原则在任何一个绘图工具里都适用。

原则一:一图一色系。很多新手喜欢用五颜六色来区分模块,结果图看起来像打翻了调色盘。我的建议是:主色不超过三种,分别代表不同的层级或状态。比如蓝色代表核心服务,灰色代表基础设施,橙色代表外部依赖。同一层级的模块用同一种颜色,这样读者不需要看图例就能大致判断模块的归属。

原则二:箭头要有语义。箭头是 diagram 里信息量最大的元素,但也是最容易被滥用的。我要求团队里的每一张图,箭头必须能回答三个问题:从哪来,到哪去,代表什么动作。如果是同步调用,箭头用实线;如果是异步消息,箭头用虚线;如果是数据流向,箭头用带空心三角的线。不要用没有箭头的线来表示关系,那会让读者猜半天。

原则三:对齐和间距比美观更重要。我见过很多图,模块大小不一、间距忽大忽小,看起来就很业余。其实不需要什么设计天赋,只要做到两点:同一层级的模块高度一致,水平间距相等;垂直方向的层级之间保持固定的间距。这样图看起来就会很整齐。draw.io 和 Excalidraw 都有对齐辅助线,画的时候多利用。

原则四:文字要能扫读。图里的文字不是越多越好。我的经验是:模块名称控制在 6 个字以内,关键说明用短语而不是句子。如果某个模块需要详细解释,用编号标注,然后在图下方或单独的文档里展开。这样图本身保持清爽,需要细节的人可以去看注释。

原则五:留白是设计的一部分。新手总想把画布填满,结果图看起来很压抑。我通常会在图的四周留出至少 10% 的空白,模块之间也保持足够的呼吸感。留白不仅让图更好看,也方便后续添加新的模块或注释。

2.3 工具选型:什么场景用什么工具

工具没有绝对的好坏,关键看场景。我根据多年的使用经验,把常见的绘图工具分成了三类,并给出了适用场景。

工具类型代表工具适用场景优点缺点
手绘风格Excalidraw快速草图、头脑风暴、非正式评审上手快、风格轻松、支持协作不适合正式文档、精度较低
代码驱动PlantUML、Mermaid需要版本控制、自动化生成、频繁修改可 diff、可复用、适合 CI 集成学习曲线陡、样式调整麻烦
通用绘图draw.io、Figma正式架构图、需要精细控制样式灵活、功能全、导出格式多手动操作多、协作需付费

我自己的习惯是:早期讨论用 Excalidraw,快速画个草图,大家边聊边改;方案定稿后用 draw.io 画正式图,导出 PNG 和 SVG 两种格式,PNG 放文档里,SVG 放知识库方便缩放;如果是需要频繁更新的图,比如 API 时序图,我会用 PlantUML 写成代码,提交到 Git 仓库,每次接口变更就改代码重新生成。

提示:不管用什么工具,一定要保留源文件。我见过太多人只导出了 PNG,结果半年后要改图,发现源文件丢了,只能重新画。源文件是资产,导出图只是快照。

3. 核心细节解析与实操要点

3.1 架构图的分层与边界处理

架构图是 diagram-design 里最考验功力的类型。我画架构图有一个固定的套路:先画三条横线,把画布分成四个区域,从上到下分别是接入层、应用层、领域层、基础设施层。然后往每个区域里填模块。

接入层通常放网关、负载均衡、CDN 这些组件。应用层放具体的业务服务,比如用户服务、订单服务、支付服务。领域层放领域模型和核心业务逻辑,比如聚合根、领域事件。基础设施层放数据库、缓存、消息队列、对象存储。

分层的目的是让读者快速定位。比如有人问“用户认证是在哪一层做的”,你直接指到接入层或应用层,不需要在一堆模块里找。边界处理的关键是:跨层的调用必须明确标注协议。比如网关到用户服务是 HTTP 调用,用户服务到数据库是 JDBC 连接,订单服务到消息队列是 AMQP 协议。这些信息不一定要写在箭头上,但可以在图例里说明。

我见过一个反例:某项目的架构图把所有模块画成同样大小的方块,箭头横七竖八,没有分层。结果新来的同事看了半天,问“这个缓存模块到底属于哪一层?”这就是边界不清导致的沟通成本。

3.2 时序图的 lifeline 对齐与消息编号

时序图是表达交互过程最有力的工具,但也是最容易画乱的。核心难点在于 lifeline(生命线)的对齐和消息的编号。

我的做法是:先确定参与交互的角色,按调用顺序从左到右排列。每个角色下面画一条垂直虚线,这就是 lifeline。然后从上到下画消息箭头。同步消息用实线实心箭头,异步消息用实线空心箭头,返回消息用虚线箭头。

消息编号很重要。我习惯用 1、2、3 这样的序号,如果某个消息有子步骤,用 1.1、1.2 表示。这样在评审的时候,大家可以直接说“第 3.2 步这里有问题”,而不需要描述半天。

注意:时序图不要画得太长。如果交互步骤超过 15 个,建议拆成多张图,或者把一些次要的交互折叠起来。我见过一张时序图画了 40 多个消息,打印出来有两米长,根本没法看。

3.3 流程图的异常分支处理

流程图的核心是表达“正常流程”和“异常流程”。很多新手把异常分支和正常流程混在一起,导致图看起来很乱。我的做法是:主流程用粗线,从左到右或从上到下排列;异常分支用细线,从主流程的节点引出,指向右侧或下方的异常处理区域。

比如一个订单创建的流程:正常流程是“接收请求 -> 校验参数 -> 创建订单 -> 扣减库存 -> 返回成功”。异常分支包括“参数校验失败 -> 返回错误”、“库存不足 -> 返回错误”、“数据库超时 -> 重试或降级”。这些异常分支不要穿插在主流程里,而是统一放在图的右侧,用不同的颜色标注。

这样画的好处是:读者第一眼看到的是正常流程,需要了解异常处理时再去看右侧的分支。信息层次分明,不会互相干扰。

3.4 颜色、字体、图例的规范

颜色和字体看似是细节,但直接影响图的可读性。我给自己团队定了一套简单的规范:

  • 颜色:主色用 #4A90D9(蓝色),辅助色用 #7B8794(灰色),强调色用 #E8833A(橙色)。蓝色代表核心服务,灰色代表基础设施,橙色代表外部依赖或警告。
  • 字体:统一用无衬线字体,字号不小于 12px。模块名称加粗,说明文字常规。
  • 图例:每张图必须有图例,说明颜色和线型的含义。图例放在图的右下角或左下角,不要放在中间。

这套规范看起来简单,但坚持下来,团队里所有人画的图风格一致,放在一起就像一套完整的文档,而不是拼凑出来的。

4. 实操过程与核心环节实现

4.1 从需求到草图:30 分钟快速出图

我以“设计一个短链接服务”为例,演示从需求到草图的完整过程。这个服务的基本功能是:接收长链接,生成短链接,访问短链接时重定向到长链接。

第一步,明确核心问题。这张图要回答的是“短链接服务的核心组件和数据流向”。所以它应该是一张架构图,而不是流程图或 ER 图。

第二步,列出关键模块。我通常会先在纸上或白板上列出所有能想到的模块:API 网关、短链接生成服务、短链接解析服务、缓存、数据库、ID 生成器。然后归类:网关属于接入层,生成和解析服务属于应用层,缓存和数据库属于基础设施层,ID 生成器属于领域层。

第三步,确定交互关系。网关到生成服务是 HTTP 调用,生成服务到 ID 生成器是 RPC 调用,生成服务到数据库是写操作,解析服务到缓存是读操作,缓存未命中时回源到数据库。

第四步,画草图。我用 Excalidraw 快速画出模块和箭头,不纠结样式,先把逻辑关系画出来。这一步大概 10 分钟。

第五步,评审和修改。把草图发给团队,大家提意见。比如有人指出“ID 生成器应该独立部署,不应该放在领域层”,或者“缓存和数据库之间应该有异步同步机制”。根据反馈修改,形成最终草图。

4.2 从草图到正式图:draw.io 的配置细节

草图确认后,我会用 draw.io 画正式图。这里有几个配置细节值得注意。

画布设置:我通常用 A3 横向画布,网格大小设为 10px,开启对齐网格。这样模块的位置和大小都是 10 的倍数,看起来整齐。

形状库:draw.io 自带了很多形状库,我常用的是“网络”和“软件”两个库。网络库里有服务器、数据库、负载均衡的图标,软件库里有微服务、消息队列的图标。用标准图标比用矩形更直观。

连接线:draw.io 的连接线支持“正交”和“曲线”两种模式。架构图我推荐用正交模式,箭头横平竖直,看起来专业。时序图用曲线模式,因为生命线是垂直的,曲线更自然。

导出设置:导出 PNG 时,我通常设置缩放比例为 200%,背景透明,这样图片清晰且可以放在深色背景的文档里。导出 SVG 时,勾选“包含文本”选项,这样 SVG 里的文字可以被搜索。

4.3 代码驱动绘图:PlantUML 实战

对于需要频繁更新的图,比如 API 时序图,我会用 PlantUML。下面是一个短链接服务的时序图代码示例。

@startuml actor User participant "API Gateway" as GW participant "Shorten Service" as SS participant "ID Generator" as ID database "Database" as DB participant "Cache" as Cache User -> GW: POST /shorten GW -> SS: 转发请求 SS -> ID: 获取唯一ID ID --> SS: 返回ID SS -> SS: 生成短链接 SS -> DB: 保存映射关系 DB --> SS: 保存成功 SS --> GW: 返回短链接 GW --> User: 201 Created User -> GW: GET /{shortCode} GW -> SS: 转发请求 SS -> Cache: 查询短链接 alt 缓存命中 Cache --> SS: 返回长链接 else 缓存未命中 SS -> DB: 查询映射关系 DB --> SS: 返回长链接 SS -> Cache: 写入缓存 end SS --> GW: 302 Redirect GW --> User: 重定向到长链接 @enduml

这段代码生成的时序图,清晰地展示了短链接生成和解析两个流程。alt 分支表示缓存命中和未命中的不同处理。每次接口有变更,只需要修改代码,重新生成图即可,不需要手动调整。

提示:PlantUML 支持 include 和变量,可以把公共的角色定义抽出来,多个图复用。这样团队里所有人画的时序图风格一致,维护成本也低。

4.4 图与文档的集成:让 diagram 成为活文档

图不是画完就结束了,它需要和文档集成,成为“活文档”。我的做法是:在 Confluence 或 Notion 里嵌入 draw.io 的源文件链接,这样点击图就能跳转到编辑页面。同时在图的旁边放一个“更新记录”表格,记录每次修改的时间、修改人和修改内容。

对于代码驱动的图,我会把 PlantUML 文件放在 Git 仓库的 docs 目录下,每次代码合并时,CI 自动生成最新的图并部署到文档站点。这样图永远和代码同步,不会出现“文档过期”的问题。

5. 常见问题与排查技巧实录

5.1 图太乱怎么办:三步降噪法

图太乱是最高频的问题。我的三步降噪法是:第一步,删掉所有非必要的模块。问自己“这个模块不画出来,读者能理解核心流程吗?”如果答案是能,就删掉。第二步,合并同类模块。比如三个数据库可以合并成一个“数据存储层”,用注释说明包含哪些数据库。第三步,调整布局。把相关的模块放在一起,用分组框框起来,减少交叉的箭头。

5.2 箭头交叉太多怎么处理

箭头交叉是架构图的常见问题。我的处理原则是:能分层就分层,能分组就分组。比如用户服务和订单服务都依赖数据库,不要让两条箭头都指向同一个数据库,而是画一个“数据访问层”,两个服务都指向这一层,再由这一层指向数据库。这样箭头数量减少,图也更有层次感。

5.3 评审时被问住的常见问题

评审时最怕被问住。我总结了几个高频问题,提前准备好答案,评审时就不会慌。

问题应对思路
这个箭头代表同步还是异步?提前在图例里说明,实线同步,虚线异步
这个模块挂了会影响哪些功能?在图上标注单点,或者用红色高亮关键路径
数据一致性怎么保证?在数据库和缓存之间标注同步策略,比如“写后失效”
为什么不用消息队列解耦?如果用了,在图上画出来;如果没用,准备好理由
这个图的更新频率是多少?说明图的维护机制,比如“随代码提交自动更新”

5.4 跨团队协作时的图版本管理

跨团队协作时,图版本管理是个大问题。我的做法是:每张图都有唯一的编号和版本号,比如“ARCH-001-v2.3”。版本号规则是:大版本号表示架构重大调整,小版本号表示细节修改。每次修改后,在图的右下角更新版本号,并在文档里记录变更日志。这样不同团队看到的图版本一致,不会出现“你看的是旧版,我看的是新版”的尴尬。

5.5 工具迁移的注意事项

有时候团队会更换绘图工具,比如从 draw.io 迁移到 Figma。迁移时要注意:第一,保留原始文件,不要只导出图片;第二,检查图例和颜色规范是否在新工具里能还原;第三,重新导出所有格式,确保文档里的图是最新的。我见过一次迁移,因为没检查字体,结果所有图的文字都变成了乱码,花了半天时间才修复。

6. 我个人的实操心得与建议

画了这么多年图,我最大的体会是:diagram-design 不是美术课,而是逻辑课。一张好图的标准不是“好看”,而是“好懂”。我见过用 ASCII 字符画的架构图,虽然简陋,但逻辑清晰,比很多花里胡哨的图都管用。所以不要纠结工具和样式,先把逻辑理清楚。

另外,图是给人看的,不是给机器看的。画完图后,找一个不了解这个项目的同事,让他看图复述一遍流程。如果他能在不看文字说明的情况下复述出 80% 的内容,这张图就合格了。如果不行,就回去改。

最后分享一个小技巧:我习惯在图的空白处放一个“图例”和“更新记录”,图例说明颜色和线型的含义,更新记录写清楚每次修改的内容。这样图本身就是一个完整的文档,不需要额外的说明。这个习惯坚持了几年,团队里的图越来越规范,评审效率也高了很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进