1. 从一张草图到一套系统:diagram-design 到底在解决什么问题
第一次看到 diagram-design 这个词,很多人会以为它只是“画图”的另一种说法。但如果你真正在技术团队、产品团队或者咨询项目里待过,就会明白:画图本身从来不是难点,难的是让图“活”起来——能复用、能迭代、能协作、能沉淀成团队资产。diagram-design 的核心,就是把“画一张图”升级为“设计一套图的生成系统”。
我最早接触这类需求,是在一个跨部门协作项目里。当时产品经理用一款在线工具画流程图,开发用另一款工具画架构图,测试又用截图加标注的方式画用例图。结果每次需求变更,三套图各改各的,版本对不上,评审会上光是对齐“哪张图才是最新版”就耗掉半小时。后来我们痛定思痛,开始尝试用代码驱动图表生成,把图的设计逻辑从“手工拖拽”转移到“结构化描述”上。diagram-design 这个方向,本质上就是解决这个问题的:它不关心你用什么工具画,它关心的是图背后的数据结构和设计规则。
说得再直白一点,diagram-design 是一套关于“如何设计图表”的方法论加工具链。它覆盖了从需求梳理、结构定义、样式规范到自动渲染的完整链路。适合谁来参考?我认为三类人最需要:第一类是技术文档工程师,天天跟架构图、流程图、时序图打交道;第二类是研发团队的技术负责人,需要把系统设计可视化并保持长期可维护;第三类是产品经理和解决方案架构师,需要用图来对齐认知、降低沟通成本。哪怕你目前只是偶尔画几张图,理解这套思路也能让你少走很多弯路。
2. 整体设计思路拆解:为什么“代码化图表”比“手工拖拽”更靠谱
2.1 核心思路:把图当成代码来管理
diagram-design 的第一个核心思路,就是把图表视为代码资产。这听起来有点抽象,我换个说法:你写代码的时候,会用 Git 管理版本、用 CI 做自动化检查、用 Code Review 保证质量。那为什么图就不行?手工拖拽出来的图,本质上是一个二进制文件或者私有格式文件,你没法 diff,没法 merge,没法做自动化校验。而代码化图表,比如用 Mermaid、PlantUML、Graphviz 或者 D2 这类 DSL(领域特定语言)来描述,图就变成了纯文本。纯文本意味着你可以用 Git 管理、可以写脚本批量生成、可以在 CI 流程里自动更新。
我实测下来,这个转变带来的最大收益不是“画图更快”,而是“改图不慌”。以前改一张架构图,要打开工具、找到对应文件、手动调整布局、导出图片、替换文档里的旧图,一套流程下来十分钟没了。现在改一行 DSL,重新渲染,图片自动更新,整个过程不到十秒。而且因为源文件是文本,我可以清楚地看到这次改动到底改了哪个节点、哪条连线,评审的时候直接看 diff 就行。
2.2 方案选型:为什么我最终选择了 DSL + 渲染引擎的组合
市面上做代码化图表的方案不少,我大致把它们分成三类:第一类是纯 DSL 方案,比如 Mermaid、PlantUML,写起来像写 Markdown,上手快但样式控制弱;第二类是编程语言驱动方案,比如用 Python 的 diagrams 库、用 JavaScript 的 D3.js,灵活度极高但学习曲线陡;第三类是混合方案,比如 D2、Pikchr,既有 DSL 的简洁,又支持一定程度的样式定制。
我最终选择的组合是D2 作为主 DSL,配合 Mermaid 做快速草图,再用 Graphviz 处理复杂布局。为什么这么选?D2 的语法比 PlantUML 更现代,支持嵌套结构、变量、主题,而且渲染出来的图默认样式就很好看,不需要花大量时间调 CSS。Mermaid 的优势在于生态好,很多 Markdown 编辑器和文档平台原生支持,适合写技术文档时随手画流程图。Graphviz 则是老牌布局引擎,处理大规模节点自动布局时依然是最稳的。
这里要特别提醒一点:不要试图用一种工具解决所有问题。我见过不少团队强行用 PlantUML 画所有图,结果画出来的架构图布局混乱、样式陈旧,最后还得手动调。选型的核心原则是:流程图和时序图优先用 Mermaid,架构图和网络图优先用 D2,复杂关系图用 Graphviz,需要高度定制化的展示图再用 D3.js 或 SVG 手写。
2.3 设计原则:一致性、可读性、可维护性
diagram-design 不是随便画,它有一套明确的设计原则。我把它总结为三个词:一致性、可读性、可维护性。
一致性指的是同一套文档里的图,风格要统一。颜色、字体、节点形状、连线样式,这些视觉元素必须遵循同一套规范。我通常会定义一个主题文件,把所有颜色变量、字体大小、间距参数集中管理。比如主色用深蓝表示核心服务,浅蓝表示辅助服务,灰色表示外部依赖,红色表示风险点。这样不管谁画图,只要引用主题文件,出来的风格就是一致的。
可读性指的是图要让人一眼看懂。这里有个常见误区:很多人觉得图越详细越好,恨不得把每个字段都标上去。但实际上,一张图如果信息密度过高,读者反而抓不住重点。我的经验是,一张图只讲一件事。架构图就讲服务划分和调用关系,不要混入部署细节;流程图就讲业务流转,不要混入技术实现。如果确实需要展示多个维度,那就拆成多张图,用超链接关联。
可维护性指的是图要容易改。这要求我们在设计图的时候,就要考虑未来的变化。比如节点命名要有规律,不要用“服务A”“服务B”这种无意义的代号;连线要有语义,不要只画箭头不写标签;结构要分层,不要把几十个节点平铺在一张图里。我通常会按照“总-分”结构来组织:一张总览图展示系统全貌,若干张分图展示每个子系统的细节,总览图里的节点链接到对应的分图。
3. 核心细节解析与实操要点:从零搭建一套图表生成流水线
3.1 环境准备:工具链安装与配置
要搭建一套完整的 diagram-design 流水线,你需要准备以下工具。我以 macOS 和 Linux 为例,Windows 用户可以用 WSL 或者直接找对应的安装包。
首先是 D2,它是目前我最推荐的 DSL 工具。安装方式很简单,macOS 用 Homebrew,Linux 用官方脚本:
# macOS brew install d2 # Linux curl -fsSL https://d2lang.com/install.sh | sh -s --安装完成后,用d2 --version验证一下。D2 支持导出 PNG、SVG、PDF 等多种格式,我通常导出 SVG,因为矢量图在文档里缩放不会糊。
然后是 Mermaid CLI,如果你需要在命令行批量渲染 Mermaid 图,这个工具很有用:
npm install -g @mermaid-js/mermaid-cli安装完成后,可以用mmdc --version检查。Mermaid CLI 依赖 Puppeteer,首次安装可能会下载 Chromium,网络不好的话需要耐心等一会儿。
最后是 Graphviz,虽然 D2 和 Mermaid 已经能覆盖大部分场景,但遇到复杂布局时 Graphviz 依然是兜底方案:
# macOS brew install graphviz # Ubuntu/Debian sudo apt-get install graphviz提示:如果你在 CI 环境里使用这些工具,建议用 Docker 镜像固定版本,避免因为工具升级导致渲染结果不一致。我吃过这个亏,某次 D2 升级后默认主题变了,所有图的颜色都偏了,排查了半天才发现是版本问题。
3.2 目录结构设计:让图表源文件井井有条
工具装好了,接下来是目录结构。我见过很多团队把图源文件随便丢在文档目录里,时间一长就找不到了。我的建议是单独建一个diagrams目录,按类型和模块分层:
diagrams/ ├── themes/ │ └── default.d2 # 全局主题文件 ├── architecture/ │ ├── overview.d2 # 系统总览图 │ ├── service-a.d2 # 服务A详细图 │ └── service-b.d2 # 服务B详细图 ├── flow/ │ ├── user-login.mmd # 用户登录流程 │ └── order-process.mmd # 订单处理流程 ├── sequence/ │ └── api-call.mmd # API 调用时序图 └── output/ # 渲染输出目录 ├── overview.svg └── ...这个结构的好处是:源文件和输出文件分离,主题文件集中管理,按图类型分目录。我通常会在output目录里加一个.gitignore,把渲染产物忽略掉,只提交源文件。这样仓库不会因为图片文件而膨胀,而且每次 CI 渲染都能保证输出是最新的。
3.3 主题文件编写:统一视觉风格的关键
主题文件是保证一致性的核心。以 D2 为例,你可以定义一个default.d2文件,里面集中管理所有样式变量:
# 颜色定义 vars: { primary-color: "#1a5276" secondary-color: "#2980b9" accent-color: "#e74c3c" bg-color: "#f8f9fa" text-color: "#2c3e50" } # 默认样式 style: { fill: ${vars.bg-color} stroke: ${vars.primary-color} font-color: ${vars.text-color} font-size: 14 border-radius: 8 } # 节点类型样式 classes: { service: { style: { fill: ${vars.primary-color} font-color: "#ffffff" stroke-width: 2 } } database: { style: { fill: ${vars.secondary-color} font-color: "#ffffff" shape: cylinder } } external: { style: { fill: "#e8e8e8" stroke-dash: 3 } } }写主题文件的时候有个小技巧:用变量而不是硬编码颜色值。这样以后想换配色方案,只需要改一处变量定义,所有图自动更新。我试过把主色从蓝色换成绿色,只改了一行,几十张图全部同步,那种感觉非常爽。
3.4 图表描述语言入门:D2 和 Mermaid 的核心语法
D2 的语法非常直观,基本就是“节点 -> 节点: 标签”的形式。比如描述一个简单的微服务调用关系:
用户 -> 网关: HTTPS 网关 -> 用户服务: gRPC 网关 -> 订单服务: gRPC 用户服务 -> 用户数据库: SQL 订单服务 -> 订单数据库: SQL 订单服务 -> 消息队列: 异步渲染出来就是一张清晰的架构图。D2 还支持嵌套容器,可以把相关服务放在一个框里:
前端: { 用户 -> 网关 } 后端: { 网关 -> 用户服务 网关 -> 订单服务 } 数据层: { 用户服务 -> 用户数据库 订单服务 -> 订单数据库 }Mermaid 的语法更接近 Markdown,适合写在文档里。比如画一个流程图:
graph TD A[用户发起请求] --> B{是否登录} B -->|是| C[查询用户信息] B -->|否| D[跳转登录页] C --> E[返回结果] D --> E注意:Mermaid 的布局引擎有时候会抽风,节点多了之后连线交叉严重。这时候可以试试调整方向(TD 改成 LR),或者手动指定节点层级。如果还是不行,就换 D2 或 Graphviz。
3.5 自动化渲染脚本:一键生成所有图表
手工一个个渲染太累,我写了一个简单的 Shell 脚本,遍历所有源文件并渲染到输出目录:
#!/bin/bash set -e DIAGRAM_DIR="diagrams" OUTPUT_DIR="diagrams/output" mkdir -p "$OUTPUT_DIR" # 渲染 D2 文件 find "$DIAGRAM_DIR" -name "*.d2" -not -path "*/themes/*" | while read -r file; do relative_path="${file#$DIAGRAM_DIR/}" output_file="$OUTPUT_DIR/${relative_path%.d2}.svg" mkdir -p "$(dirname "$output_file")" echo "Rendering $file -> $output_file" d2 --theme 0 --layout dagre "$file" "$output_file" done # 渲染 Mermaid 文件 find "$DIAGRAM_DIR" -name "*.mmd" | while read -r file; do relative_path="${file#$DIAGRAM_DIR/}" output_file="$OUTPUT_DIR/${relative_path%.mmd}.svg" mkdir -p "$(dirname "$output_file")" echo "Rendering $file -> $output_file" mmdc -i "$file" -o "$output_file" -b transparent done echo "All diagrams rendered successfully."这个脚本我放在项目根目录,命名为render-diagrams.sh,每次改完图源文件跑一下就行。如果配合 Git hooks,还可以做到提交前自动渲染,确保输出目录永远是最新的。
4. 实操过程与核心环节实现:一个真实项目的完整落地记录
4.1 需求分析:先搞清楚要画什么图
去年我参与了一个模拟项目,代号叫“某跨平台系统”,需要为它设计一套完整的架构文档。项目初期,我们花了整整两天时间梳理需要哪些图。这个过程很关键,因为如果一开始没想清楚,后面就会陷入“边画边补”的混乱状态。
我们最终确定的图表清单包括:一张系统上下文图(展示系统与外部实体的关系)、一张容器图(展示主要服务划分)、三张组件图(分别展示核心服务的内部结构)、两张时序图(关键业务流程)、一张部署图(展示运行环境)。总共八张图,覆盖了从宏观到微观的各个层次。
这里有个经验:不要一上来就画细节图。我见过不少团队直接画组件图,结果画到一半发现服务划分还没定,又回头改,浪费大量时间。正确的顺序是:先画上下文图,明确系统边界;再画容器图,确定服务划分;最后画组件图和时序图,深入细节。
4.2 结构定义:用 DSL 描述系统骨架
确定图表清单后,我开始用 D2 定义系统骨架。以容器图为例,我先定义所有服务和它们之间的关系:
direction: right 用户: { shape: person style.fill: "#e8e8e8" } 网关: { class: service } 用户服务: { class: service } 订单服务: { class: service } 支付服务: { class: service } 消息队列: { class: external } 用户数据库: { class: database } 订单数据库: { class: database } 用户 -> 网关: HTTPS 网关 -> 用户服务: gRPC 网关 -> 订单服务: gRPC 网关 -> 支付服务: gRPC 用户服务 -> 用户数据库: SQL 订单服务 -> 订单数据库: SQL 订单服务 -> 消息队列: 发布事件 支付服务 -> 消息队列: 发布事件这段代码定义了一个典型的微服务架构。我特意把用户画成 person 形状,把数据库画成 cylinder,把消息队列画成虚线框,这样一眼就能区分不同类型的组件。
4.3 样式调优:让图看起来专业而不花哨
骨架有了,接下来是调样式。我见过很多技术图,内容是对的,但看起来就是不舒服。问题通常出在几个地方:颜色太多太杂、字体大小不统一、间距太挤或太松。
我的调优步骤是这样的:第一步,统一颜色。整个系统只用三种主色:深蓝表示核心服务,浅蓝表示辅助服务,灰色表示外部依赖。第二步,统一字体。所有节点字体大小设为 14,连线标签设为 12。第三步,调整间距。D2 默认的节点间距有点小,我通过--pad参数调到 20,看起来就舒服多了。
提示:D2 的
--layout参数可以切换布局引擎。默认是 dagre,适合大多数场景。如果图特别大,可以试试 elk,布局更紧凑。我实测下来,节点超过 30 个的时候,elk 的效果明显更好。
4.4 集成到文档:让图自动出现在该出现的地方
图渲染好了,最后一步是集成到文档里。我用的方案是:在 Markdown 文档里直接引用 SVG 文件,然后用 CI 自动更新。具体做法是在文档里写:
然后在 CI 配置里加一个步骤,每次文档构建前先跑render-diagrams.sh,确保图片是最新的。这样文档作者只需要维护 DSL 源文件,图片会自动生成,再也不会出现“文档里的图和实际系统对不上”的问题。
我还试过用 D2 的 Markdown 插件,直接在 Markdown 里嵌入 D2 代码块,渲染时自动替换成图。这个方案更优雅,但依赖特定的文档平台支持。如果你的平台不支持,还是用 SVG 引用的方式最稳妥。
4.5 版本管理:图也要做 Code Review
图源文件纳入 Git 管理后,自然就可以做 Code Review 了。我们团队的规矩是:任何架构图的修改,都必须提 PR,至少一个人审核通过才能合并。审核的重点不是语法对不对,而是图表达的内容是否准确、是否符合当前的系统设计。
这个流程刚开始推行的时候,有人觉得麻烦。但后来有一次,一个开发在改架构图时不小心删掉了一个关键服务节点,Review 的时候被发现了,避免了一次文档事故。从那以后,大家都认可了这个流程。
5. 常见问题与排查技巧实录:踩过的坑和总结的经验
5.1 渲染失败:常见错误与解决方法
代码化图表最让人头疼的就是渲染失败。我把遇到过的问题整理成了一张速查表:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| D2 渲染报语法错误 | 节点名包含特殊字符 | 用引号包裹节点名,如"用户服务" |
| Mermaid 图显示不全 | 节点太多超出画布 | 调整方向或拆分成多张图 |
| SVG 在文档里显示模糊 | 导出的是 PNG 而非 SVG | 改用 SVG 格式导出 |
| 中文显示为方框 | 字体缺失 | 在主题里指定支持中文的字体 |
| 连线交叉严重 | 布局引擎选择不当 | 切换 layout 参数或手动调整节点顺序 |
| CI 渲染结果与本地不一致 | 工具版本不同 | 用 Docker 固定工具版本 |
其中中文显示问题我遇到过好几次。D2 默认字体在某些系统上不支持中文,渲染出来全是方框。解决方法是在主题文件里指定字体:
style: { font-family: "PingFang SC, Microsoft YaHei, sans-serif" }5.2 布局调优:让图自动排得好看
自动布局是代码化图表的优势,但也是痛点。有时候自动排出来的图就是不如手工拖拽的好看。我的经验是:先接受自动布局,再微调。D2 和 Graphviz 都支持通过节点顺序、方向、间距参数来影响布局。比如把direction从right改成down,整个图的走向就变了。把相关节点放在同一个容器里,布局引擎会自动把它们聚在一起。
如果自动布局实在调不好,还有一个终极方案:用 D2 的near关键字手动指定节点位置。比如用户服务.near: 网关,这样布局引擎会尽量把这两个节点放在一起。这个技巧在处理复杂架构图时特别有用。
5.3 协作冲突:多人同时改图怎么办
多人协作时,图源文件的冲突比代码冲突更麻烦,因为 DSL 的结构性更强,merge 的时候容易出错。我们的做法是:按模块拆分文件。每个人负责自己的模块图,互不干扰。如果确实需要改同一张图,就约定好时间,避免同时编辑。
另外,我建议在 PR 里附上渲染后的图片预览。这样 Review 的人不用本地渲染就能看到效果,大大提高了审核效率。GitHub 和 GitLab 都支持在 PR 里直接预览 SVG,非常方便。
5.4 性能问题:大图渲染慢怎么优化
当图特别大时,渲染时间会明显变长。我遇到过一张包含 200 多个节点的架构图,D2 渲染要十几秒。优化方法有几个:第一,拆图。把一张大图拆成多张小图,用超链接关联。第二,换布局引擎。elk 在处理大图时比 dagre 快不少。第三,缓存。如果图源文件没变,就不要重复渲染。我在渲染脚本里加了一个简单的哈希校验,只有文件内容变了才重新渲染。
注意:不要为了追求“一张图搞定所有事”而牺牲可读性。我见过一张包含 500 多个节点的“全景图”,渲染出来密密麻麻,根本没法看。这种图除了炫耀技术,没有任何实际价值。
5.5 工具链维护:版本升级的注意事项
工具链的版本管理很容易被忽视。我建议在项目里加一个.tool-versions文件,记录所有工具的版本号:
d2 0.6.3 nodejs 20.11.0 graphviz 9.0.0这样新成员加入时,照着版本号安装就行,不会出现“在我机器上能渲染,在你机器上报错”的问题。另外,升级工具版本时一定要先在小范围测试,确认渲染结果没有意外变化再全面推广。我就因为一次 D2 升级导致所有图的默认主题变了,不得不回滚重来。
6. 进阶技巧:让 diagram-design 发挥更大价值
6.1 从图到代码:用图表驱动开发
diagram-design 的进阶玩法是用图驱动开发。具体来说,就是先画架构图,再根据图来生成代码骨架。比如用 D2 定义好服务划分后,写一个脚本解析 D2 文件,自动生成每个服务的目录结构和基础配置文件。这样图和代码就是一一对应的,改了图就等于改了代码结构。
我试过用 Python 解析 D2 的 AST,提取节点和连线信息,然后生成 Docker Compose 文件。虽然只实现了最简单的版本,但已经能节省不少手工配置的时间。如果你对这方面感兴趣,可以从解析 Mermaid 或 D2 的文本格式入手,逐步构建自己的代码生成器。
6.2 动态图表:让图随数据变化
静态图只能展示某一时刻的状态,而动态图可以展示数据变化。比如用 D3.js 读取实时监控数据,动态更新架构图上的节点颜色和连线粗细,直观展示系统负载。这个方案适合做监控大屏或者实时演示,技术门槛较高,但效果非常震撼。
我的建议是:先用 DSL 把静态图做好,确保结构和样式都满意,再考虑动态化。不要一上来就搞动态图,那样很容易陷入技术细节而忽略了图本身要表达的内容。
6.3 图表即文档:构建可交互的架构文档
最后分享一个我觉得最有价值的方向:把图表变成可交互的文档。传统的架构文档是静态的,读者只能看,不能探索。但如果用 SVG 加 JavaScript,就可以实现点击节点展开详情、悬停显示说明、搜索定位节点等功能。
我做过一个实验:把系统架构图导出为 SVG,然后用 JavaScript 给每个节点绑定点击事件,点击后弹出该服务的详细说明和 API 文档链接。这样一张图就变成了一个导航入口,读者可以从宏观到微观逐步深入。这个方案的技术难点在于 SVG 节点的定位和事件绑定,但一旦跑通,体验提升非常明显。
6.4 团队推广:如何让大家都用起来
再好的工具,如果团队不用,也是白搭。我推广 diagram-design 的经验是:先做样板,再定规范,最后自动化。先自己用这套方法画几张图,在评审会上展示效果,让大家看到好处。然后制定简单的规范,比如“所有架构图必须用 D2 描述”“图源文件必须提交到仓库”。最后把渲染流程自动化,让大家感觉不到额外负担。
最关键的一点是:不要强制。我见过有团队强制所有人用某款工具,结果怨声载道,最后不了了之。正确的做法是提供便利,让大家自愿选择。当大家发现用代码画图确实比手工拖拽更高效时,自然会迁移过来。
7. 我个人的实操体会与后续扩展思路
这套 diagram-design 的方法论,我从最初尝试到现在,前后迭代了差不多两年。最大的体会是:图的本质是信息压缩。一张好的图,能把几百字才能说清楚的关系,用几秒钟就传达给读者。而代码化图表的价值,在于让这种信息压缩过程变得可重复、可维护、可协作。
如果你刚开始接触这个方向,我的建议是从一张图开始。选一个你熟悉的系统,用 D2 或 Mermaid 把它描述出来,渲染成 SVG,放进文档里。感受一下从“拖拽”到“编写”的转变。然后逐步把团队里的图都迁移过来,建立主题文件和渲染脚本。最后把图源文件纳入版本管理,形成规范。
后续还可以往几个方向扩展:一是做图表模板库,把常见的架构模式(比如微服务、事件驱动、分层架构)做成可复用的模板,新项目直接套用;二是做图表质量检查,写脚本自动检测图中是否存在孤立节点、循环依赖、命名不规范等问题;三是做图表搜索引擎,把所有图源文件索引起来,支持按关键词搜索节点和关系。
踩过几次坑之后,我越来越觉得:图不是画给别人看的,是画给未来的自己看的。半年后你回头看自己画的图,如果能一眼看懂,说明这套 diagram-design 的方法论就真正落地了。