1. 从“diagram-design”这个标题说起:它到底在解决什么问题
第一次看到“diagram-design”这个标题,很多人会下意识觉得它是个画图工具,或者某个前端组件库。但如果你真正做过技术方案评审、系统架构梳理、或者给非技术同事讲清楚一个复杂流程,你就会明白:画图这件事,难的从来不是“画”,而是“设计”。图表的本质是一种信息压缩与传递的媒介,一张好的架构图能让三个人吵半小时的问题在五分钟内达成共识,一张糟糕的图则会让所有人陷入“这个箭头到底指向谁”的沉默。
“diagram-design”这个标题背后,指向的是一类非常具体的需求:如何用代码或结构化描述来生成、管理和维护图表。它不是一个孤立的绘图动作,而是一整套从“想法”到“可交付图表”的工作流设计。我见过太多团队在项目初期随手用在线工具拖了几张图,结果三个月后系统改了,图没人更新,文档里的架构图和实际代码完全对不上,最后所有人都默认“图是过时的,别看了”。这个问题的根源不在于工具不好,而在于图表没有被当作代码一样管理。
所以这篇内容适合谁看?如果你是后端开发、架构师、技术文档写作者,或者任何需要频繁输出系统图、流程图、时序图的人,这篇内容会帮你建立一套可维护、可版本控制、可自动化生成的图表工作流。如果你只是偶尔画一张示意图,那可能用现成工具拖拽更快,但如果你面对的是一个持续迭代的项目,那“diagram-design”这套思路值得你花时间理解。
我自己的经历是,早期做微服务拆分时,用某在线工具画了二十多张服务依赖图,结果每次服务增减都要手动改一遍,改到第三轮就彻底放弃了。后来转向用文本描述生成图表的方式,虽然前期学习曲线陡了一点,但一旦跑通,后续维护成本几乎为零。这就是“diagram-design”这个标题真正吸引我的地方:它把图表从“一次性消费品”变成了“可维护的工程资产”。
2. 图表即代码:为什么文本描述比拖拽更值得投入
2.1 拖拽式工具的隐性成本
大多数人画图的第一反应是打开一个在线白板或者桌面绘图软件,拖几个方框、连几条线、调一下颜色,导出PNG,完事。这个过程在单次任务里确实高效,但它的成本被隐藏在了后续的维护环节。我总结下来,拖拽式工具在项目场景下有四个绕不开的问题。
第一是版本对比几乎不可能。你改了图里的一个服务名,导出新图,旧图和新图之间没有任何结构化的差异信息。你没法像看代码diff一样看到“哪个节点被删了、哪条边被加了”。第二是复用性差。同一个系统在不同文档里需要不同视角的图,拖拽方式下你只能重新画一遍,或者复制粘贴后手动调整,一致性全靠人眼保证。第三是协作冲突。两个人同时改一张图,最后只能靠“谁最后保存谁说了算”,没有合并机制。第四是与代码脱节。系统架构变了,代码仓库里有commit记录,但图不会自动更新,久而久之图就成了“历史文物”。
这些问题的本质是:拖拽式工具把图表当成了“图片”,而不是“数据”。图片是不可解析的像素集合,而数据是可以被程序处理、对比、生成的结构化信息。
2.2 文本描述生成图表的核心逻辑
“diagram-design”这套思路的核心,是把图表的定义从“画布上的像素”转变为“结构化的文本描述”。你写一段类似代码的描述,工具负责把它渲染成图。这个转变带来的好处是连锁性的:文本可以进Git仓库,于是有了版本历史;文本可以被diff,于是有了变更对比;文本可以被模板化,于是有了复用;文本可以被程序生成,于是有了自动化。
具体来说,常见的文本描述格式有几种流派。一种是声明式DSL,比如用类似A -> B: 请求这样的语法描述节点和边,工具解析后布局渲染。另一种是基于现有编程语言,比如用Python或JavaScript的库来定义图表结构,好处是可以直接调用代码逻辑做条件生成。还有一种是基于标准建模语言,比如用PlantUML或Mermaid这类专门为图表设计的文本格式。
我个人的选择倾向是:如果图表需要频繁嵌入技术文档,优先选Mermaid,因为它在主流文档平台里的渲染支持最广;如果图表逻辑复杂、需要条件分支或循环生成,优先选代码库方案,因为编程语言的表达能力更强;如果团队有统一的建模规范,PlantUML的标准化程度更高。但不管选哪种,核心原则是一样的:图表的定义必须是一段可读、可diff、可版本控制的文本。
2.3 一个最小可用的工作流长什么样
假设你现在要为一个新项目画一张系统上下文图,用“diagram-design”的思路,工作流大概是这样的。第一步,在代码仓库里建一个docs/diagrams目录,所有图表源文件放这里。第二步,选一个文本描述格式,写第一版图表定义。第三步,用命令行工具或编辑器插件把文本渲染成图片,渲染产物可以提交也可以不提交,取决于你的CI流程。第四步,把渲染命令写进项目的构建脚本或CI配置,每次文档构建时自动生成最新图表。第五步,当系统变更时,改文本描述,提交,CI自动更新图表。
这个流程跑通之后,你会发现图表维护从“想起来才做”变成了“跟着代码走”。我现在的习惯是,任何涉及系统结构变更的PR,如果影响了架构图,就必须同时更新图表源文件,否则CI会报一个提醒。这个约束一开始有点烦,但两周之后就习惯了,而且效果非常明显:文档里的图和代码永远是一致的。
提示:不要一开始就追求大而全的图表体系。先从一张最核心的架构图开始,跑通“文本定义-渲染-提交-CI”这个最小闭环,再逐步扩展。
3. 选型对比:几种主流文本绘图方案的实战取舍
3.1 Mermaid:文档嵌入的首选,但有边界
Mermaid是目前在技术文档领域渗透率最高的文本绘图方案,没有之一。它的最大优势是渲染环境极其广泛:主流代码托管平台的Markdown渲染器原生支持,很多文档生成工具也内置了支持,甚至一些笔记软件都能直接渲染。这意味着你写一段Mermaid代码,在大多数技术场景下不需要额外安装任何东西就能看到图。
它的语法设计偏向“够用就好”,流程图、时序图、状态图、类图、甘特图这些常见类型都有覆盖。写起来大概是这样:
flowchart LR A[客户端] --> B[网关] B --> C[用户服务] B --> D[订单服务] C --> E[(用户库)] D --> F[(订单库)]这段代码渲染出来就是一张简单的服务依赖图。学习成本很低,半小时就能上手基本语法。
但Mermaid的边界也很明显。第一,布局控制能力弱。它自动布局,你很难精确控制节点位置,复杂图的布局有时候会出乎意料。第二,样式定制有限。虽然支持一些主题和样式配置,但深度定制比较吃力。第三,复杂逻辑表达困难。当图表需要根据条件动态生成不同结构时,Mermaid的静态文本描述就不够用了。第四,大图性能问题。节点超过几十个之后,渲染速度和可读性都会明显下降。
我的经验是:Mermaid适合中小规模、结构相对稳定、嵌入文档场景的图表。如果你的图是给文档读者看的,节点在二十个以内,Mermaid是最省事的选择。但如果图很复杂,或者需要频繁动态生成,就要考虑其他方案。
3.2 PlantUML:建模能力强,但渲染链路重
PlantUML是另一个老牌的文本绘图方案,它的定位更偏向“建模”而不是“画示意图”。它支持的图表类型非常丰富,除了常见的流程图、时序图,还支持组件图、部署图、活动图等偏UML规范的图种。语法上比Mermaid更严谨,表达能力也更强。
它的优势在于布局算法成熟,尤其是时序图和类图的布局质量,在复杂场景下明显优于Mermaid。另外它的样式定制能力更强,可以通过skinparam等配置做比较细致的视觉调整。对于需要输出正式建模文档的团队,PlantUML的规范性是加分项。
但它的短板也很突出。第一,渲染依赖Java环境,虽然现在有在线渲染服务,但本地渲染需要装Java和Graphviz,链路比Mermaid重不少。第二,文档平台原生支持不如Mermaid广,很多Markdown渲染器不直接支持PlantUML,需要额外配置。第三,语法相对繁琐,写同样一张图,PlantUML的代码量通常比Mermaid多。
我自己的取舍是:如果团队已经在用PlantUML做正式建模,或者图表复杂度高到Mermaid布局撑不住,就选PlantUML。否则,优先Mermaid,省事。
3.3 代码库方案:灵活性的天花板
除了Mermaid和PlantUML这类DSL方案,还有一类是用通用编程语言来定义图表,比如用Python的diagrams库、JavaScript的mermaidAPI、或者更底层的Graphviz绑定。这类方案的核心优势是可以写逻辑。
举个例子,假设你要为十个微服务生成依赖图,每个服务的依赖关系从一个配置文件读取。用DSL方案,你得手动把十个服务的节点和边都写出来;用代码库方案,你可以写一个循环,读配置,自动生成节点和边。当服务数量变化时,代码不用改,配置改了图就自动变。
from diagrams import Diagram from diagrams.aws.compute import Lambda from diagrams.aws.database import RDS services = load_config("services.yaml") with Diagram("Service Map"): nodes = {s["name"]: Lambda(s["name"]) for s in services} for s in services: for dep in s.get("deps", []): nodes[s["name"]] >> nodes[dep]这段伪代码展示了代码库方案的核心价值:图表结构由数据驱动,而不是硬编码在文本里。当系统规模大到一定程度,这种灵活性是DSL方案给不了的。
但代价是学习成本更高,你需要熟悉对应库的API,而且渲染环境需要安装依赖。另外,代码库方案生成的图表通常不如DSL方案那么“声明式”,可读性上对非开发者不够友好。
3.4 选型决策表
| 维度 | Mermaid | PlantUML | 代码库方案 |
|---|---|---|---|
| 学习成本 | 低 | 中 | 高 |
| 文档平台原生支持 | 广 | 窄 | 无 |
| 布局控制 | 弱 | 中 | 强 |
| 样式定制 | 有限 | 较强 | 最强 |
| 动态生成能力 | 无 | 有限 | 强 |
| 渲染依赖 | 无 | Java+Graphviz | 视库而定 |
| 适合场景 | 文档嵌入、中小图 | 正式建模、复杂图 | 大规模、数据驱动 |
这张表不是让你死记,而是帮你在具体场景下快速定位。我的建议是:先用Mermaid跑通流程,遇到瓶颈再升级。不要一上来就选最复杂的方案,那样很容易在配置环境阶段就放弃。
4. 落地实操:从零搭建一套可维护的图表工作流
4.1 目录结构与命名约定
一套可维护的图表工作流,第一步是把目录结构定清楚。我的习惯是在项目根目录下建docs/diagrams,下面按图表类型或模块分子目录。比如:
docs/ diagrams/ architecture/ system-context.mmd service-deps.mmd flows/ order-flow.mmd payment-flow.mmd sequences/ auth-sequence.mmd rendered/ system-context.svg service-deps.svg源文件用.mmd或.puml后缀,渲染产物放rendered目录。命名上我坚持用小写加连字符,比如system-context.mmd而不是SystemContext.mmd,这样在命令行里操作不用频繁切换大小写。另外,文件名要能自解释,不要用diagram1、diagram2这种,三个月后你自己都记不住哪个是哪个。
注意:渲染产物是否提交到仓库,取决于你的CI流程。如果CI会在构建时自动渲染,那
rendered目录可以加进.gitignore;如果文档平台需要直接引用图片文件,那就提交。我倾向于提交,因为这样本地预览和线上展示一致,减少“本地能看线上不能看”的问题。
4.2 渲染命令与CI集成
Mermaid的本地渲染可以用@mermaid-js/mermaid-cli,安装后命令行调用:
npm install -g @mermaid-js/mermaid-cli mmdc -i docs/diagrams/architecture/system-context.mmd -o docs/diagrams/rendered/system-context.svg批量渲染可以写个脚本遍历目录:
#!/bin/bash for file in docs/diagrams/**/*.mmd; do output="${file%.mmd}.svg" output="${output/docs\/diagrams/docs\/diagrams\/rendered}" mmdc -i "$file" -o "$output" done把这个脚本挂到package.json的scripts里,或者直接写进CI配置。比如在CI的构建阶段加一步npm run render-diagrams,这样每次合并到主分支,图表都会自动更新。
PlantUML的渲染类似,用plantuml命令行工具:
plantuml -tsvg docs/diagrams/**/*.pumlCI集成的好处是强制一致性。你可以加一个检查步骤:如果源文件有变更但渲染产物没更新,就报错。这样就不会出现“改了源文件忘了渲染”的情况。
4.3 编辑器实时预览配置
命令行渲染适合CI,但日常写图的时候,实时预览能大幅提升效率。主流编辑器都有Mermaid和PlantUML的预览插件。以VS Code为例,装一个Mermaid预览插件,打开.mmd文件后按快捷键就能在旁边看到实时渲染结果。PlantUML也有类似插件,配置好本地渲染服务后同样支持实时预览。
我自己的习惯是双屏操作:左边写源文件,右边开预览,改一行看一眼效果。这样调布局和样式的时候效率很高,不用反复跑命令行。另外,有些插件支持导出当前预览为图片,临时需要一张图的时候很方便。
提示:实时预览插件通常有自己的渲染引擎版本,可能和CI用的版本有细微差异。如果发现本地预览和CI渲染结果不一致,优先检查版本号,统一到同一个版本。
4.4 版本控制与变更审查
图表源文件进了Git之后,变更审查就变得很自然。当有人改了架构图,PR里会显示源文件的diff,reviewer可以看到具体哪个节点被加了、哪条边被删了。这比看两张图片的视觉差异要精确得多。
我还会在PR模板里加一条检查项:“如果本次变更涉及系统结构,是否同步更新了docs/diagrams下的相关图表?”这个提醒看起来简单,但实际效果很好,因为它把“更新图表”从“可做可不做”变成了“流程的一部分”。
另外,我建议给图表源文件也加上简单的注释。比如在文件开头写一段说明,解释这张图的用途、更新频率、负责人。这样当别人接手时,不用猜这张图是干什么的。
%% 系统上下文图 %% 用途:展示核心服务与外部依赖的关系 %% 更新频率:随架构变更同步更新 %% 负责人:后端架构组 flowchart LR ...注释不会影响渲染,但能大幅降低沟通成本。
5. 踩过的坑与实战心得:那些文档里不会写的事
5.1 布局失控:为什么你的图总是“歪”的
用文本绘图最让人抓狂的问题之一,就是自动布局的结果和你脑子里的预期不一致。你明明想让A在B左边,结果渲染出来A跑到了B上面。这个问题在Mermaid里尤其常见,因为它的布局算法对节点顺序和连接关系很敏感。
我踩过的坑包括:节点定义顺序影响布局、边的方向影响层级、子图嵌套导致整体偏移。解决这些问题的核心思路是理解工具的布局逻辑,而不是对抗它。比如Mermaid的流程图默认是从左到右或从上到下布局,你可以通过flowchart LR或flowchart TD指定方向。如果布局还是不理想,可以尝试调整节点定义顺序,或者用不可见的连接线来“引导”布局。
另一个实用技巧是拆图。一张图如果超过二十个节点,布局失控的概率会大幅上升。与其花半小时调一张大图,不如拆成两张小图,每张聚焦一个视角。读者看起来也更轻松。
5.2 中文支持与字体问题
文本绘图工具在中文场景下经常遇到字体问题。Mermaid在某些渲染环境下,中文会显示成方块或者字体不一致。这个问题的根源是渲染环境缺少中文字体,或者字体配置没有正确传递。
我的解决方案是:在渲染配置里显式指定中文字体。Mermaid CLI支持通过配置文件指定fontFamily,PlantUML可以通过skinparam设置字体。另外,如果渲染产物要嵌入网页,确保网页的CSS也指定了合适的中文字体栈。
{ "theme": "default", "themeVariables": { "fontFamily": "PingFang SC, Microsoft YaHei, sans-serif" } }这个配置在大多数场景下能解决中文显示问题。如果还是不行,检查渲染环境是否安装了对应字体。
5.3 复杂图的性能与可读性平衡
当图表节点超过一定数量,渲染性能和可读性都会下降。我实测下来,Mermaid在节点超过五十个之后,渲染时间明显变长,而且图本身也变得难以阅读。PlantUML的布局算法更强一些,但同样有上限。
我的处理原则是:一张图只讲一件事。如果一张图需要表达三个以上的核心概念,就拆成多张。比如系统架构图、数据流图、部署图分开画,每张聚焦一个维度。这样不仅渲染更流畅,读者理解起来也更轻松。
另外,对于确实需要展示大量节点的场景,可以考虑分层展示。先画一张高层概览图,把主要模块标出来,然后每个模块单独画一张详细图。概览图里用链接指向详细图,形成层次结构。
5.4 团队协作中的约定与工具链统一
图表工作流在个人使用时很顺畅,但到了团队协作阶段,问题会集中爆发。最常见的问题是工具链不统一:有人用Mermaid,有人用PlantUML,有人还在用拖拽工具。结果就是图表风格混乱,维护责任不清。
我的经验是:在团队层面明确一套标准工具链,并写进开发规范。比如规定“所有技术文档中的图表统一使用Mermaid,源文件放在docs/diagrams,CI自动渲染”。然后提供一份模板和示例,降低上手成本。对于确实需要其他工具的场景,要求说明理由并保持源文件可维护。
另外,指定图表负责人也很重要。每张图或每个模块的图,有一个明确的负责人,负责在系统变更时更新。这个负责人不一定是画图的人,但要是最了解那块系统的人。
5.5 从“画图”到“设计图”的思维转变
最后分享一个我觉得最重要的心得:“diagram-design”的核心不是工具,而是设计思维。工具只是手段,真正决定图表质量的是你对“这张图要解决什么问题”的理解。
我见过太多人花大量时间调颜色、调布局,但图的核心信息却模糊不清。一张好的图,首先要想清楚:读者是谁?他们需要从这张图里获取什么信息?哪些细节可以省略?这些问题想清楚了,工具选择反而是次要的。
我的习惯是,画图之前先用一句话写下这张图的“核心信息”。比如“这张图要说明订单服务依赖用户服务和支付服务,且支付服务是异步调用”。然后围绕这句话来组织节点和边,所有不服务于这句话的元素都删掉。这样画出来的图,信息密度高,读者一眼就能抓住重点。
这个思维转变需要练习,但一旦养成,你画的每一张图都会比之前更有价值。工具会过时,但设计思维不会。