当团队里开始有人问“这张架构图是谁画的?还能不能改?”的时候,基本意味着你们需要一个叫diagram-design的东西。
别被这个名字吓到,它不是什么高深框架,而是一套“把画图当写代码来对待”的思路和工具链。我最初关注到 diagram-design,是因为维护了几年的系统文档里,架构图全部躺在 draw.io 的二进制文件里,改一次要半天,改完还看不清谁改了什么。后来我彻底切到“文本代码化画图”的工作流,把流程图、时序图、架构图全部用 Mermaid、Graphviz 这类声明式工具管理起来,这才算真正解决了图表维护的痛点。
这篇文章我会从思路、工具选型、实操语法、工程化集成到踩坑实录,完整拆解我个人的 diagram-design 工作流。不管你是后端开发、前端、架构师,还是写技术文档的同学,只要能碰命令行,这套方法就能直接抄作业。
1. 我为什么不再拖拽画图了
先说结论:手动拖拽画图适合一次性的临时草图,不适合需要持续维护的技术图。这个观点可能有点绝对,但经历过“图永远比代码旧”的痛苦之后,你会明白它有多实在。
1.1 拖拽画图的三个长期痛点
过去我用传统画图工具,比如 Visio、draw.io、ProcessOn,画一张图并不难,问题出在图的“生命周期”上。
首先是版本管理。传统工具的源文件通常是二进制或者私有格式,存在 Git 里就是一个巨大的 blob,没法 diff、没法 review。同事改了节点位置、改了线条颜色,你根本不知道具体动了什么。有时候合并分支,图上莫名其妙多了一个方块,也没人说得清是谁加的。
其次是协作成本。团队里多个人同时编辑一张图,几乎一定会出现“各改各的、最后手工合并”的局面。因为图形工具的并集天然难,大家只能排队改,或者靠聊天工具传来传去,最后总有一版覆盖掉别人的修改。
最后是更新滞后。代码改了,图没改;图改了,代码又改了。这种“图文不同步”在拖拽工具下几乎无解,因为修改成本太高,大家宁愿去读代码也不愿去伺候图,久而久之图就成了摆设。
1.2 代码化画图的本质:让图成为工程的一部分
diagram-design 的核心思路是把图形看作一种结构化文本,用声明式语法描述节点、连线、分组和样式,再交给渲染引擎生成 PNG、SVG 或 HTML。
拿流程图举例子,传统方式是从工具栏拖一个“矩形”放到画布,双击改文字;代码化的方式是写一段文本:
graph LR A[用户请求] --> B{鉴权} B -- 通过 --> C[业务处理] B -- 拒绝 --> D[返回错误]这段文本就是一个图。你可以把它放进 Git,让同事给你提 PR,用 diff 工具看每一行改动,甚至可以在 CI 里自动渲染检查语法对不对。
打个比方,拖拽画图像是手写纸质简历,写错了只能用涂改液;代码化画图像是用 Markdown 写简历,内容、格式、版本全部可追溯。这个思维转变,是 diagram-design 带给我最大的收益。
1.3 什么样的人和团队适合 diagram-design
如果你是“画一张图交给老板看一眼”的场景,我建议你还是用拖拽工具,省事。但如果你属于下面任意一种情况,就很有必要切换到文本画图:
- 文档里需要长期维护架构图、时序图、状态图这些“易变图”,而不是一次性可视化;
- 团队希望对图表做 Code Review,让图跟着代码一起迭代;
- 需要跨平台、跨工具复用同一份图数据,比如既要在 GitHub 里显示,又要导出到 PDF;
- 有自动化需求,比如每次发版自动更新一张依赖关系图。
我接触过的不少团队,真正推不动 diagram-design,不是因为工具不好用,而是因为“手动拖图”已经成了习惯。所以下面我会把工具链和实操串起来讲,让大家看到一个完整的替代方案。
2. 主流 diagram-design 工具选型,我的选择思路
代码化画图的生态已经挺成熟了,但不同工具的定位差别很大。我梳理一下最常见的四类:Mermaid、Graphviz、PlantUML、diagrams.net(draw.io),给出选型建议。
2.1 Mermaid:轻量、生态好,最适合文档嵌图
Mermaid 是目前最“出圈”的文本画图语言。语法直观,学习曲线非常平缓,而且被 GitHub 原生支持,Markdown 里直接嵌代码块就能渲染。我日常工作里七八成的图都用 Mermaid,尤其是流程图、时序图、状态图和饼图。
优点是上手快、社区活跃、彩虹屁一样多的样式主题;缺点是复杂布局能力弱,节点一多容易挤成一团,对精确排版控得不够。所以它适合“表达逻辑”,不适合“做精细排版”。
2.2 Graphviz:布局算法强大,适合复杂关系图
Graphviz 是老牌开源工具,底层用 dot 语言描述图结构,由引擎自动计算节点位置。它的最大特点是布局算法强,比如层级图、依赖图、状态机这种节点多、关系密的场景,Graphviz 的自动布局比 Mermaid 更科学。
代价是语法偏“程序化”,刚开始用会觉得不直观,而且样式体系比较古典。但如果你要画“微服务调用关系图”“代码模块依赖图”,Graphviz 是非常可靠的选择。
2.3 PlantUML:面向 UML 的工程化利器
PlantUML 是我在项目内做架构文档时的秘密武器。它用文本描述 UML 图,时序图、用例图、组件图、部署图都能画,还支持 C4 模型,可以和架构描述结合得很好。
它比 Mermaid 更偏向“工程建模”,语法里有类的属性和方法、参与者、消息序号这些概念,渲染出的图也更接近标准 UML 风格。缺点是依赖 Java 环境,稍微重一些。
2.4 diagrams.net(draw.io):半拖拽半文本的折中方案
有人会说 diagrams.net 不是拖拽工具吗?其实它支持 .drawio 文件直接存成 XML 文本格式,也能在 GitHub 里 diff。不过说实话,它更多是把“拖拽的结果”文本化了,并没有改变“手动摆放”的交互模式。
我的定位是:作为临时协作工具。别人发我一个 draw.io 文件,我能快速打开、补一笔、导出发回去。但它不是我的主战工具。
2.5 选型对照表与建议
| 工具 | 语法难度 | 布局能力 | 典型场景 | 渲染依赖 |
|---|---|---|---|---|
| Mermaid | 低 | 中 | 文档嵌图、快速流程、时序、状态 | 纯 JS / CLI |
| Graphviz | 中 | 强 | 依赖图、状态机、复杂关系 | 本地引擎 |
| PlantUML | 中高 | 中强 | UML 建模、C4 架构 | Java |
| diagrams.net | 极低 | 手动强 | 快速草图、跨团队临时协作 | 无 |
我个人的默认组合是:文档里用 Mermaid,复杂关系用 Graphviz,架构建模用 PlantUML,临时头脑风暴用 diagrams.net。这不是“都要学”的负担,而是不同场景下效率最优的自然选择。下面我挑最常用的 Mermaid 做主菜,详细走一遍实操。
3. 核心实操:用 Mermaid 完成一套架构图
3.1 环境准备:VS Code 插件与命令行 CLI
Mermaid 零依赖也能玩,GitHub 和很多笔记软件都支持。但如果你想在本地渲染、批量导出、接 CI,我建议装一下 CLI 工具。
Node.js 环境下执行:
npm install -g @mermaid-js/mermaid-cli装完之后,可以用mmdc命令把.mmd文件渲染成 PNG/SVG/PDF:
mmdc -i input.mmd -o output.svg --width 1200 --backgroundColor whiteVS Code 我推荐装“Markdown Preview Mermaid Support”插件,写 Markdown 时可以直接预览 Mermaid 代码块。预览的实时反馈很重要,能帮你快速调整语法错误。
提示:如果运行时提示 Puppeteer 相关错误,通常是浏览器内核下载失败,需要翻看你的网络代理或换用
puppeteer的 mirror 配置。这个我放到后面问题章节细讲。
3.2 三张最常用的图:流程图、时序图、状态图
Mermaid 能画的图很多,我先拆三张最常出现在研发文档里的。
流程图(graph)。
graph TD A[开始] --> B{是否有权限} B -->|是| C[执行操作] B -->|否| D[提示无权限] C --> E[记录日志] D --> ETD表示方向为从上到下,改成LR就是从左到右;- 节点里
[文本]是矩形,{文本}是菱形判断,(文本)是圆角矩形; - 连线后面加
|是|可以给边加标签。
时序图(sequenceDiagram)。
sequenceDiagram participant U as 用户 participant S as 服务端 participant D as 数据库 U->>S: 发起登录请求 S->>D: 查询用户信息 D-->>S: 返回结果 S-->>U: 返回登录结果participant声明参与对象,可以起别名;->>是实线箭头,-->>是虚线返回,能很好表达调用链。
状态图(stateDiagram-v2)。
stateDiagram-v2 [*] --> 待支付 待支付 --> 已支付: 支付成功 待支付 --> 已关闭: 超时关闭 已支付 --> 已完成: 确认收货 已支付 --> 退款中: 申请退款 退款中 --> 已完成: 退款成功状态图用于表达状态流转,和代码里的状态机一一对应,写的时候要保持状态命名和代码枚举一致。
3.3 从零设计:一张微服务系统架构图
我用一个“订单服务 + 支付服务 + 消息队列”的简化微服务架构图展示完整的思考过程。
先明确图里要表达什么:外部接入方、网关、核心服务、依赖组件、服务间调用关系。然后确定拆分层次:最上层是客户端,中间是网关和业务服务,底层是中间件和数据库。
写成 Mermaid:
graph TB subgraph 客户端层 U[Web 前端] A[App 客户端] end subgraph 接入层 G[API 网关] end subgraph 服务层 O[订单服务] P[支付服务] I[库存服务] end subgraph 依赖层 R[(Redis)] DB[(MySQL)] MQ[消息队列] end U --> G A --> G G --> O G --> P O --> I O --> DB P --> DB O --> R P --> R O --> MQ P --> MQ这里面有几个细节值得注意:
subgraph后面加名字,可以把一组节点收进一个区块,让复杂图有层次感;- 节点的 ID 我全部用大写字母别名,这样改动文字描述时不需要改连线引用;
- 箭头方向统一由调用方指向被调用方,读图很顺,不会出现一半反向一半正向的别扭感。
架构图的节点不要放太多业务细节,每个服务一个方框,下面可以用文字补充职责。如果有更多描述文字,我通常用换行或悬浮注释,避免图上塞满字。
3.4 样式与主题定制,让图不再“默认脸”
Mermaid 给每个企业级文档要做品牌化,默认配色确实不够好看。我平时会这样定制:
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e6f0ff', 'lineColor': '#334466'}}}%% graph TB A[下单请求] --> B[校验库存] B --> C[创建订单] C --> D[返回成功]想对特定节点加样式,可以用classDef和class:
graph LR A[核心服务] --> B[辅助服务] classDef core fill:#ffe6e6,stroke:#cc0000,stroke-width:2px; class A core样式的重点不是“炫”,而是用颜色快速区分“核心链路”和“旁路逻辑”。这个对复杂系统理解帮助特别大。
4. 进阶:Graphviz 与 PlantUML,复杂图怎么画
4.1 Graphviz 的 dot 语言入门
当图里节点超过 30 个、关系高度交织时,Mermaid 往往会排出一坨让人绝望的线条。这时候我切 Graphviz。
先看个最小例子:
digraph G { rankdir=LR; node [shape=box]; A -> B [label="调用"]; B -> C [label="依赖"]; C -> A [label="回调"]; }digraph表示有向图;rankdir=LR指定布局方向从左到右;node [shape=box]对所有节点统一设置形状;label给边加文字。
Graphviz 的厉害之处是自动布局引擎,把节点扔进去,边连好,它自己会计算位置。复杂依赖关系下,它会尽量让连线不交叉或减少交叉。相比之下,Mermaid 在这方面弱很多。
我常用它的一个场景是录入系统模块之间的数据流。比如有个几十个微服务互相调用,手动画图绝对会崩溃,但用 dot 描述依赖关系后,渲染出一张全局调用关系图,可以很直观地发现“某个服务被所有服务调用,这可能是瓶颈”。
4.2 PlantUML 的 C4 模型玩法
C4 模型是一种分层描述软件架构的方法,Context(上下文)、Container(容器)、Component(组件)、Code(代码)。PlantUML 提供了 C4 相关宏,可以用文本代码快速生成架构图。
例如:
@startuml !include C4_Context.puml Person(user, "普通用户", "") System(store, "商城系统", "") System_Ext(pay, "第三方支付", "") Rel(user, store, "浏览/下单") Rel(store, pay, "调用支付") @enduml这种写法的好处是架构角色有语义,而且官方提供了一系列图标主题,生成出来的架构图非常专业。做架构汇报、系统设计评审的时候,拿这套图出来很有说服力。
4.3 布局调优的两个小经验
第一个经验:别手动调坐标,靠约束引导布局。Graphviz 里可以通过rank、weight、constraint=false这些属性影响布局,而不是手动指定 x/y。手动调坐标的图,一旦数据变化就全乱;靠约束引导的图,增减节点后布局会自动调整。
第二个经验:复杂图优先分组,而不是追求一图打尽。我见过有人非要把所有系统画到一张图里,结果图放大到 200% 都看不清。正确的做法是用subgraph或cluster把系统按层次分组,每张图重点讲一条主线,细节图单独再画一张,用“图链接”串联。
5. 让图表进入工程流程:版本管理、渲染与文档集成
5.1 把 .mmd 文件纳入 Git 管理
画图一旦变成文本,就能直接纳入版本管理。我习惯在仓库里建一个docs/diagrams目录:
docs/diagrams/ ├── order-flow.mmd ├── auth-seq.mmd ├── system-arch.dot └── README.md所有.mmd、.dot、.puml文件都用文本保存。改图 = 改文本 = 走 Merge Request 流程,评审人打开 diff 就能看到“哪个节点加了”“哪条连线改了”,这个体验是拖拽工具完全给不了的。
5.2 在 CI 中自动渲染图片
让 CI 自动渲染图,可以保证“代码库里的图永远可用”。我提供一个简单的 GitHub Actions 思路:
name: render-diagrams on: push: paths: - "docs/diagrams/**" jobs: render: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 - run: npm install -g @mermaid-js/mermaid-cli - run: | mkdir -p docs/assets for f in docs/diagrams/*.mmd; do mmdc -i "$f" -o "docs/assets/${f%.mmd}.svg" done - uses: actions/upload-artifact@v3 with: name: rendered-diagrams path: docs/assets这套流水线会监听图片源文件的变更,重新渲染 SVG,把产物传到构建记录里。团队文档链接可直接指向产物的 URL,保证每次看到的是最新版。
如果文档平台支持直接渲染 Mermaid,那段循环可以省略,比如 GitHub 的 Markdown 预览天然支持。但需要长图、导出或统一缓存时,我推荐自渲染。
5.3 不同文档平台集成差异
GitHub、GitLab、语雀、Notion 对 Mermaid 的生态支持不一:
- GitHub/GitLab:Markdown 内置支持 mermaid 代码块,最省心;
- 语雀:支持 mermaid 绘图,但版本和语法支持可能滞后;
- Notion:原生不支持 mermaid 代码块,需要生成图片后再插入;
- 自建 Wiki:多数支持插件,Confluence 可通过第三方宏实现。
我的原则是:重要且频繁更新的图,全部走 CI 渲染成 SVG,再放到文档;临时分享直接用平台内置渲染。两条路并行,既不丢失实时性,也不让平台限制工具链。
6. 常见问题与排查技巧实录
切换到 diagram-design 的过程中,我踩过不少坑,整理出来给后来人省时间。
6.1 高频问题速查表
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 中文显示成方块或乱码 | 缺少中文字体或 SVG 字体未嵌入 | 给渲染环境安装中文字体;SVG 里指定font-family;PNG 渲染时设置--fontFamily |
| 子图(subgraph)背景不显示 | 使用了较老版本语法 | 确认 Mermaid 版本支持subgraph title写法;升级到 v10+ |
| 箭头的方向不是预期 | TD/LR方向混用,或节点引用方向反了 | 统一graph TB或graph LR;按调用方向描边 |
| 节点太多,布局混乱 | Mermaid 对超大型图支持有限 | 拆图、分组或换 Graphviz |
| Mermaid CLI 报 Puppeteer 错误 | 本地没下载 Chromium | 配置PUPPETEER_SKIP_DOWNLOAD或手动安装浏览器 |
| PlantUML 需要 Java 环境 | 本身依赖 | 本地项目安装 JDK;或使用 PlantUML 在线服务 |
| CI 渲染乱码/字体缺失 | 容器里没有字体文件 | Docker 里安装fonts-noto-cjk |
6.2 几个“文档不会写”的调试心得
调试代码化图时,最快的方法是二分法:先把所有节点和连线删到只剩三条,确认能正常渲染;再逐步加回来,哪一步开始崩,就是那部分语法或结构有问题。
还有一个很有用的技巧:给每个节点 ID 起“语义化名字”,避免使用 a、b、c 这种。语义化名字在 diff 时一眼能看出改了什么,而且代码里也有自注释效果。比如orderService和paymentService,比node1清楚得多。
另外,长文本节点一定要换行。一个节点里塞 100 个字,渲染出来又长又丑。控制在两行以内,太多就提取核心词,详情放注释或说明文档。
6.3 一个常见的认识误区:代码化画图不等于“只会简单图”
很多人以为 Mermaid 只能画“入门流程图”。其实 Mermaid 还能画象限图、甘特图、需求图、C4 图(实验性),Graphviz 更是可以画极其复杂的网络拓扑。
难画的是“审美的图”,因为自动布局不一定符合人的阅读习惯。我的解决办法是:让图的结构主导布局,让文字精简主导可读性,颜色作为唯一强调手段。少即是多,在 text 模式下比拖拽模式更容易实现。
7. 一点个人体会(也当作结尾)
在整个 diagram-design 的实践过程中,最大的收获其实是思维转变。以前我画图是为了“交差”,画完就忘;现在写图是为了“表达”,图跟着代码迭代、跟着架构演进,成为真正活着的一等公民文档。
用熟了之后我发现,并不是所有图都需要代码化。一次性摊开讲个思路,或者开白板头脑风暴,拖拽反而更自然。但凡是会进仓库、会被反复修改、需要多人协作的图,我都会优先写在文本里。这个边界感很重要,别为了技术情怀把一次性的图也搬上 CLI,那就是过度工程了。
最后再分享一个小习惯:每次画完架构图后,我会在图下面附带一段“变更记录”,用注释写清楚谁在什么时候改了什么。
graph LR A[核心服务] --> B[新增加密模块] %% v2.1 - 2025-01-15 - 增加加密模块 %% v2.0 - 2024-12-01 - 初始化核心链路半年后你再看这张图,会感谢当时多写了这三行注释。