今天想聊一个我最近一直在折腾的方向:diagram-design。说白了,就是用代码去设计、生成和维护各种图表——架构图、流程图、时序图、拓扑图,全都能用文本定义、自动渲染、进版本库。我以前总觉得画图这件事,找一款顺手的工具就够了,直到做过的项目里每次方案变更都要手动改图、文档里的图总跟实际部署对不上,才意识到问题不在工具,而在工作流。这篇文章把我在 diagram-design 这个项目上的整体思路、技术选型和踩过的坑整理一遍,适合正在准备做技术文档、接口文档、系统设计文档的开发者参考。
1. 拆解核心需求:为什么要把“画图”变成“写代码”
1.1 手工画图的痛点是真实存在的
我见过太多团队用手绘工具维护架构图,刚开始还挺工整,三个月以后就没人敢动了。原因很简单:代码改了、服务拆了、调用关系变了,但那张静态图片还原封不动躺在文档里。你问负责的同学为什么不更新,对方大概率会告诉你“最近太忙,等这轮迭代完再说”,然后这轮迭代永远完不了。
另一个痛点是协作。用在线绘图工具分享出来的链接,经常出现多人同时编辑然后互相覆盖的情况;用设计软件画的图,文件动辄几十兆,放进代码仓库里每次拉取都卡。更要命的是,图片是二进制格式,Git 能告诉你“这张图变了”,但没法告诉你“哪里变了”,代码评审的时候只能靠肉眼一张张对比,效率极低。
我在 diagram-design 这个项目里想做的第一件事,就是把这类问题从根上解决掉。思路是对齐软件开发里那句老话:一切能用文本表达的东西,都应该用文本表达。图也一样,把节点、连线、分组、样式这些要素写成结构化文本,再用工具渲染成图,这就等于给架构图、流程图装上了版本控制、代码评审和自动化构建的能力。
1.2 文本定义图到底解决了哪些问题
先说说版本可控。当图变成了.mmd、.dot、.py这样的文本文件,提交 PR 的时候评审人可以直接看到 diff:哪个节点改了颜色、哪条线换了方向、什么节点被删掉了,全在代码变更里清清楚楚。再也没有“我看不出你改了哪里”的困惑。
然后是数据可复用。图里的很多内容其实是重复的,比如一套系统里的基础组件、数据库、中间件,几乎每一张架构图都有它们。手绘工具里只能一次次复制粘贴,而代码画图可以把这些节点抽成公共变量,甚至抽成函数,一处定义,到处引用。后面改组件命名,改一张源头图,所有引用它的图一起更新。
还有批量生成。项目进入稳定期以后,我经常需要给不同版本、不同模块分别出一套图。手绘可能需要一整天,而代码画图只需要改几个配置参数,然后跑一遍脚本,十几张图一次性全部重新渲染。这种能力在手绘工作流里是几乎不可能实现的。
1.3 diagram-design 的整体定位
我对 diagram-design 的定位不是“某款画图软件的替代品”,而是一套完整的工作流。它的链路可以抽象成三部分:
结构化文本(图源文件) -> 解析与布局引擎 -> 目标图片(PNG/SVG)最左边是上图中的源文件,承载信息;中间是渲染引擎,决定布局和美观度;最右边是产物,直接嵌入文档或幻灯片。这个项目本身不追求发明一种新语言,而是把已有的文本绘图生态拼装起来,再补充统一的构建、校验、发布流程。
打个生活化的比方:手绘工具像 Word,排版自由但难追踪;文本绘图像 Markdown,有规范、易协作、能自动构建。diagram-design 负责的,就是把 Markdown 这套思路搬到图形世界里。
2. 技术选型:不同图型要用不同发动机
2.1 先看看主流工具怎么选
我一开始也天真地以为,只要选一款工具就能搞定所有图。实际用下来发现,不同类型的图对布局算法、表达方式、渲染质量的诉求差异很大,强行统一会非常别扭。下面是我接触过的几个主要方案对比:
| 工具 | 擅长场景 | 学习成本 | 输出格式 | 我的综合评价 |
|---|---|---|---|---|
| Mermaid | 流程图、时序图、甘特图 | 低,半小时上手 | PNG/SVG | 跟 Markdown 文档配合极好,适合日常文档插图 |
| Graphviz | 复杂有向图、依赖关系图 | 中,语法较晦涩 | PNG/SVG/PDF | 自动布局算法强,节点多了也压得住 |
| Python diagrams | 系统架构图、云资源拓扑 | 低,有 Python 基础就行 | PNG/SVG | 节点图标丰富,适合画部署架构 |
| PlantUML | UML 类图、时序图、用例图 | 中 | PNG/SVG | 团队协作建模神器,但依赖 Java 环境 |
这四种我最后都留在了工具链里,不是因为贪多,而是它们的重叠度其实很低。Mermaid 负责文档里轻量级插图,Graphviz 处理数据流转类复杂图,Python diagrams 画系统架构,PlantUML 留给正式的 UML 建模。每个工具只做自己最擅长的事。
2.2 文档插图首选 Mermaid
Mermaid 是我在日常技术文档里用得最多的。它的语法足够简单,团队成员几乎不需要培训就能看懂并且动手修改,社区生态也很好,很多在线文档平台直接支持渲染 Mermaid 代码块。
我在流程图里最常用的是graph TD这种写法,上下方向的布局很容易理解。实际项目里我写过一个需求审批流程,大概长下面这样:
graph LR A[需求输入] --> B{方案设计} B --> C[评审会议] C --> D{审批通过?} D -- 通过 --> E[进入开发] D -- 驳回 --> B这段文本用任何支持 Mermaid 的编辑器打开就能渲染成图。我一般会把这类.mmd文件放进docs/diagrams目录,然后在文档里引用它,而不是直接把图片贴死。
2.3 复杂依赖图交给 Graphviz
当图里的节点超过二十个、连线超过三十条的时候,Mermaid 的自动布局就会开始力不从心,图要么变得很宽,要么边穿来穿去。这时候我会搬出 Graphviz,它最核心的能力就是基于图论的布局算法,能把复杂的依赖关系排列得相对整齐。
Graphviz 使用 DOT 语言描述图,我画服务依赖关系的时候长这样:
digraph G { rankdir=LR; node [shape=box]; a [label="接入层"]; b [label="业务层"]; c [label="数据层"]; a -> b; b -> c; }虽然语法看着比 Mermaid 朴素,但正是它把布局算法交给引擎处理,所以对大型图特别友好。不过我要提醒一句,Graphviz 对中文的支持比较挑剔,后面第 4 部分会专门讲怎么处理。
2.4 架构图与 UML 图各自的选择
Python diagrams 是画系统架构图的一把好手。它的原理是在 Python 代码里定义节点、连线、分组,然后调用 Graphviz 做渲染,所以既有代码的灵活,又有自动布局的底子。我用它画过接入层、应用层、数据层的分层架构图,节点图标都是现成的,非常省事。
PlantUML 则更适合严谨的 UML 建模场景。比如时序图、用例图、类图,它有完整的语法定义,团队要统一建模规范的时候很合适。缺点是运行时依赖 Java,在一些纯净环境里部署起来比较麻烦,所以我在选型上会对它稍微保守一点。
2.5 为什么不只锁死一款工具
有朋友问过我,为啥不直接统一成 Mermaid 一个工具,文档渲染、代码高亮都方便。我的答案是:锁死工具等于锁死表达方式。系统的复杂度是分层的,有些图只有五个节点,用 Mermaid 很轻快;有些图涉及几十个服务间调用,必须靠 Graphviz 的重型布局;还有些图要进 PPT 汇报,需要 Python diagrams 那种带图标的高颜值渲染。
diagram-design 的哲学是“图源文件统一管理,渲染引擎按需选择”。底层不依赖某一个具体工具,上层通过统一脚本去调起不同的引擎,这样工具链升级或者换引擎时,已有图源文件和目录结构都不用推倒重来。
3. 实操过程:搭一套能真正落地的图表生成工作流
3.1 先把环境准备齐
我用的主力语言是 Python,所以整个工作流以 Python 为核心,Node.js 作为补充。第一步是安装依赖,我在自己的机器上跑的是 macOS,命令大概这样:
# 安装 Graphviz 渲染引擎 brew install graphviz # 安装 Python diagrams pip install diagrams # 安装 Mermaid CLI,用于把 .mmd 渲染成图片 npm install -g @mermaid-js/mermaid-cliWindows 用户可以把 brew 替换成 winget 或 chocolatey,Linux 用户则用 apt 或 yum,总之关键是先把dot命令装好,因为 Python diagrams 的底层渲染依赖它。装完以后跑一下dot -V,能输出版本号就说明环境 OK 了。
3.2 用 Python diagrams 画一张系统架构图
我在 diagram-design 里最常产出的是系统架构图。下面是一个典型的“用户 -> 网关 -> 业务服务 -> 数据库/存储”的分层结构示例:
from diagrams import Diagram, Cluster, Edge from diagrams.generic.compute import Compute from diagrams.generic.database import SQL from diagrams.generic.storage import Storage from diagrams.generic.network import Internet from diagrams.generic.os import Linux with Diagram("app-architecture", show=False, direction="LR"): user = Internet("用户") with Cluster("接入层"): gateway = Compute("API网关") nginx = Linux("Nginx") with Cluster("业务层"): service = Compute("业务服务") db = SQL("数据库") storage = Storage("对象存储") user >> Edge(color="#4A6FA5") >> gateway >> nginx nginx >> service service >> Edge(label="读写") >> db service >> Edge(label="上传/下载") >> storage代码里的Cluster用来做视觉分组,Edge可以自定义颜色和标签,节点类型我从generic分类里引用,均为通用计算资源、数据库、存储图标,不依赖特定云厂商资源,跨团队复用的时候也比较中立。保存成arch.py后直接python arch.py,会在当前目录生成app-architecture.png。
这里有个容易踩的细节:两个节点之间如果出现同一条边被重复定义,最终渲染会显示两条平行线。我自己的习惯是严格用>>运算符表达数据流向,避免手滑写错。
3.3 用 Mermaid 画一张流程图
系统里另一类高频图是流程图。我会把流程描述写在.mmd文件里,然后统一渲染成图片。下面这段是我做权限申请流程时的真实示例:
graph TD A[发起申请] --> B{角色校验} B -- 校验通过 --> C[权限评估] B -- 校验失败 --> Z[拒绝申请] C --> D{数据范围} D -- 全量 --> E[开放全部权限] D -- 部分 --> F[勾选数据范围] F --> G[提交审批] E --> G G --> H[审批中] H --> I{审批结果} I -- 通过 --> J[权限生效] I -- 驳回 --> Z用 Mermaid CLI 渲染时,我一般这样执行:
mmdc -i permission-flow.mmd -o permission-flow.svg如果想画高清 PNG,可以在命令里指定背景色和缩放:
mmdc -i permission-flow.mmd -o permission-flow.png -b white -s 2-s 2表示二倍缩放,用来保证放进文档里足够清晰。
3.4 用 PlantUML 画时序图
模型设计阶段,时序图是讨论接口调用关系最好的工具。我第一次用它的时候差点被语法劝退,但掌握了几个关键词以后,发现表达效率确实比白板绘图高出一截。下面是一个简化的登录流程:
@startuml actor 用户 participant "前端服务" as F participant "业务服务" as B participant "数据服务" as DB 用户 -> F: 输入账号密码 F -> B: 登录请求 B -> DB: 验证凭证 DB --> B: 返回校验结果 B --> F: 颁发会话凭证 F --> 用户: 登录成功 @enduml渲染时推荐用命令行或 IDE 插件,比如plantuml sequence.puml。这时候产出的时序图完全由文本驱动,后续接口变更时,我只需要改代码里的调用动作,其他部分自动保持同步,简直不要太省心。
3.5 用统一脚本一键全量渲染
单个图手动渲染没问题,但项目里可能有十几张图,如果每次都手动跑命令,早晚会漏。我写了一个简单的build.sh脚本,放在docs/diagrams目录下,作为所有图的统一构建入口:
#!/usr/bin/env bash set -e cd "$(dirname "$0")" # 渲染所有 Python 架构图 for py in *.py; do [ -e "$py" ] || continue echo "渲染 $py ..." python "$py" done # 渲染所有 Mermaid 流程图 for mmd in *.mmd; do [ -e "$mmd" ] || continue echo "渲染 $mmd ..." npx --yes @mermaid-js/mermaid-cli -i "$mmd" -o "${mmd%.mmd}.svg" done # 渲染所有 PlantUML 时序图 for puml in *.puml; do [ -e "$puml" ] || continue echo "渲染 $puml ..." plantuml "$puml" done echo "全部渲染完成"有了这个脚本,同事改完图源文件后只需要跑一下bash build.sh,所有图片自动更新。如果再配合 Git hooks,把脚本挂到提交前的钩子里,还能顺便检查有没有忘记更新图片产物的疏漏。
3.6 目录结构与团队协作约定
一套协作流程要落地,目录结构必须统一。我在项目里的习惯是:
docs/ └── diagrams/ ├── arch.py ├── permission-flow.mmd ├── login-sequence.puml ├── build.sh └── README.mdREADME.md里写清楚每个文件的维护人、最后更新日期、以及对应文档的链接。别小看这些元信息,半年后回来看项目,真正帮你的不是代码好不好看,而是能不能第一时间定位到“这张图该找谁改、改完影响哪些文档”。
4. 常见问题与排查技巧实录
4.1 导出的 PNG 不清晰,字都是虚的
这个坑几乎每个人都踩过。Mermaid 和 Graphviz 默认输出有时候会因为缩放比例不足,导致放到 PPT 里被拉伸糊掉。我现在的做法是:能用 SVG 就用 SVG,文档和网页场景下矢量图永远最清晰;如果对方一定要求 PNG,就用两倍甚至三倍缩放导出。
比如 Mermaid CLI 用-s 2参数,PlantUML 用-scale 2,Python diagrams 在Diagram初始化时可以指定输出格式和清晰度,或者在生成后用外部命令进一步放大。总而言之,先确认好目标载体是屏幕还是打印,再定输出规格。
4.2 中文全部变成方框或者乱码
Graphviz 对中文支持不友好,是很多初学者的噩梦。核心原因是渲染引擎找不到合适的中文字体。解决办法分两步:第一步,系统里安装中文字体,比如 Noto Sans CJK;第二步,在 DOT 文件里明确指定字体名:
digraph G { graph [fontname="Noto Sans CJK SC"]; node [fontname="Noto Sans CJK SC"]; edge [fontname="Noto Sans CJK SC"]; }Mermaid 的容器渲染通常没有这个问题,但如果你在 CLI 导出 SVG 后发现字体不对,可以在代码块开头统一设置字体族:
classDef default font-family: "Noto Sans CJK SC", sans-serif;另外,团队协作时最好固定一台构建机或统一 Docker 镜像,因为不同操作系统自带的字体不一样,容易出现“我这台机器上渲染没问题,别人机器上有乱码”的经典问题。
4.3 图节点太多,画出来乱成一团
图表最怕的不是复杂,而是乱。我遇到过一张 Graphviz 画的依赖关系图,节点叠在一起、箭头满天飞,根本没法看。后续我总结了几个对策:
- 拆分。一张图超过四五十个节点,就考虑按业务模块拆成多张子图,再用一张总览图把子图串起来。
- 改方向。Graphviz 里
rankdir=TB换成rankdir=LR,往往能让长链式依赖图瞬间清爽很多。 - 强化分组。Mermaid 用
subgraph,Python diagrams 用Cluster,Graphviz 用subgraph cluster_xxx,把相近节点圈起来,视觉层次会清晰不少。 - 精简标签。节点文本能短则短,长文本放到图下方的说明文字里,别把节点撑得和卡片一样大。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 推荐解决方式 |
|---|---|---|
| PNG 放大模糊 | 默认缩放比不够 | CLI 指定-s 2或-scale 2,矢量用 SVG |
| 中文显示为方框 | 缺少中文字体配置 | 安装字体并在源文件中指定fontname |
| 边交叉严重 | 布局算法默认策略不佳 | 调整rankdir,拆图,或加大节点间距 |
| 导出文件为空白 | Graphviz 未正确安装 | 检查dot -V,重新安装渲染引擎 |
| 多人改图互相冲突 | 图片二进制无法 diff | 改用文本图源文件,纳入 Git 管理 |
| 渲染结果不一致 | 字体、版本、环境不同 | 统一构建环境,最好用 Docker |
4.5 自动化构建里的隐藏坑
最后说一个我在 CI 里遇到的教训:千万别在构建脚本里直接覆盖已经提交的图片产物,除非加好 diff 检测。更好的做法是,在流水线里先执行构建脚本生成图片,再用git diff -s exit检查有没有改动,如果有改动说明有人改了源文件但忘了更新产物,直接让流水线失败,提醒开发者提交更新的图片。
这个机制能在代码评审之前就能拦截掉大部分“图和源码不一致”问题。我刚接上这个检查的时候,几乎每周都有同事触发,后来大家逐渐习惯了改图源文件的同时跑一次构建,问题就消失了。
我个人在实际操作中最深刻的体会是:diagram-design 这个方向,真正的难点从来不是绘图工具本身,而是把图当成工程制品来对待。图源文件要有责有人、版本可控、构建可复现,图才能长期保持准确。最后分享一个我坚持到现在的小技巧:每个图源文件的开头都写三行注释,注明这张图的维护人、最近一次更新的日期、以及它对应的文档链接。每次准备改图前,先看一眼这三行,你会发现自己省下了一堆靠猜测才能做对的事。