十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

图表即代码:用 diagram-design 构建可维护的技术图工作流

图表即代码:用 diagram-design 构建可维护的技术图工作流 虽然过去两年我一直在帮团队维护技术文档但真正让我决定把图表体系化重建的是一次让我印象深刻的架构评审。会上同事指着一张已经过时的网络拓扑图问了句“这个节点现在还部署在这台机器上吗”我当时没法立刻回答因为那张图是三个月前用在线画板拖出来的之后代码改了十几轮图压根没同步。那次之后我彻底想明白了一件事架构图、流程图、时序图必须和代码一样进入版本管理用一套可控、可复用、能自动生成的工作流来维护。这就是我搭 diagram-design 这个项目的起因。diagram-design 本质上不是某个单一软件而是一套“把图表当代码来设计和交付”的完整工作流。它解决了三类最常见的问题图表内容与真实系统脱节、多人协作时改图没有历史记录、以及图表复用性差导致每次都要从头画。这套方案适合后端工程师、系统架构师、运维同学以及任何需要长期维护技术文档的技术团队使用。你不需要很强的设计能力只要会用 Markdown 或者写简单的 DSL就能把架构图、流程图、时序图管理起来。1. 项目总览diagram-design 到底在解决什么问题很多人听到“diagram-design”第一反应是“又一个画图工具”。但实际上我在设计这个项目时最大的着力点不是“怎么画出来”而是“怎么让图活起来”。所谓活起来就是让每张图和代码仓库里的真实状态保持同步让每次修改都有迹可循让团队成员拿到一张新图时能立刻看懂它想表达什么。先说最扎心的现状。大部分团队画技术图用的是在线白板或者桌面画图软件画完导出一张 PNG 或者 PDF然后丢到 Wiki 或文档站点里。问题在哪儿一旦系统架构发生变化没有多少人会想起来去改那张图因为改图成本很高要找原文件、要重新排版、要保证连线不交叉、要调颜色。久而久之文档里的图就成了一张张“历史遗迹”只有新同学入职时会被翻出来看看而其中的信息早已失真。diagram-design 的核心思路是让图成为代码仓库的“一等公民”。所有图都用纯文本格式定义存进 Git 仓库随着代码变更一起走评审、走 CI、走发布。改图和改代码一样有 diff、有历史、有 review 记录。图不再是一张静态图片而是系统设计的一部分可以被自动化流程检查、渲染、甚至生成多种格式。第二个痛点是复用性。用鼠标拖拽画图每次都从空白画布开始连边框颜色、箭头样式、字体大小都要重新调一遍。而在 diagram-design 里我把公共图元、配色方案、布局参数做成了共享配置新图画起来只是“拼积木”。一套基础设施图的风格能延续到流程图和时序图上读者看多了之后会形成肌肉记忆架构师画图也会更快。最后一个痛点是内容组织。之前的文档站点里图是散落在页面里的匿名附件没有名字、没有来源、没有版本。而在我的实践里每张图都有唯一的源文件路径、有导出格式规定、有配套的文字说明。图和文字在同一份文档里维护时一起改不会出现“图写的是 A正文写的是 B”的情况。2. 核心设计思路为什么一定要走“图表即代码”路线2.1 对比拖拽绘图代码化到底强在哪里拖拽式绘图工具不是不能用而是不适合“需要长期维护”的图表。在我看来技术图表有两类一类是抛砖引玉的草稿画完讨论完就可以丢掉另一类是沉淀下来的系统说明文档需要跟随项目长期演进。后者如果不用代码化方式管理迟早会烂掉。代码化第一个直接的好处是文本可 diff。团队里任何一个人把节点从“订单服务”改成“交易服务”提交以后其他人通过代码评审就能看到差异。如果是图片格式除非一张一张肉眼比对否则没人会发现修改在哪里。这一点在多人维护同一个架构图时尤其重要Git 的 blame 功能还能帮你定位到底是谁在什么时候改了哪个节点。代码化第二个好处是自动化。图定义好以后可以用命令行工具批量渲染成 PNG、SVG、PDF也可以嵌入到 CI 流程里。比如我后来给项目加了“架构图校验”步骤每次提交时自动检查是否有节点定义了但没有连线的“孤立节点”。这在拖拽绘图里是不可能做到的因为你拿到的是一张合并后的像素图。第三是可编程。因为图源文件是文本你可以用脚本批量修改。比如想统一调整所有图的颜色主题只需写一个小脚本替换变量想在每张图上自动加“日期”和“版本号”也是在模板里加一行的事。这种批量能力在手动绘图工具里完全无法想象。2.2 工具选型Mermaid、Graphviz、PlantUML 到底怎么选代码化绘图这个方向其实有不只一种方案我在 diagram-design 里面同时用到了多个引擎但各自分工非常明确。这里我把自己实际对比的结果整理成一张表方便大家按项目情况选型。引擎适用场景语法体验布局能力输出质量我的评价Mermaid流程图、时序图、状态图、甘特图最接近 Markdown入门门槛最低自动布局一般复杂图容易交叉浏览器渲染效果好但高精度排版弱团队协作首选写文档场景最舒服Graphviz系统架构图、依赖关系图、树状图类似 C 语言的属性定义稍显繁琐布局算法强悍图论模型成熟默认输出较“理工男”需调样式复杂关系图首选布局交给算法PlantUMLUML 专业图类图、时序图、用例图专门为 UML 设计序列图极其顺手中规中矩遵循 UML 规范学科味浓适合软件工程文档做 UML 图时没有对手我见过不少团队非要用某一个引擎解决所有问题结果就是绕了很多弯子。比如用 Mermaid 画一张上百节点的系统架构图布局会乱到怀疑人生反过来用 Graphviz 画一个简单的业务流程光调节点的 shape 和 style 就得磨半天。diagram-design 的做法是不锁定唯一引擎而是建一层统一的目录结构和输出约定具体用哪种工具由图的类型决定。章节、源码和成品图都放在仓库以后我还利用了“约定优于配置”的思路。每张图都放在以图名命名的目录里源文件、样式文件、导出配置和关系说明放在一起任何人打开仓库都能快速定位。对比之下之前很多团队把图源文件放在各自本地电脑里文档里只会看到一个“图片已过期”的占位符这种痛苦我想很多同学都懂。2.3 设计规范布局、样式与图例的统一约定代码化解决了“图能不能保存、能不能复用”的问题但光有这些还不够。如果你画出来的图别人看不懂或者一张图的风格和另一张格格不入那这套工作流依然没有完成闭环。所以在 diagram-design 里我还沉淀了一套轻量级的设计规范不强迫所有人必须按像素级设计稿执行但统一了几个关键约束。首先是逻辑分层。一张系统架构图至少要区分用户层、接入层、应用层、数据层不同层用浅色背景分区表示而不是把几十个节点平铺在同一个平面上。这样看图的人第一眼就能感知到数据的流向不会迷失在细节里。这个习惯来自一次痛苦的线上故障排查当时拓扑图没有分层排查链路时硬是从一堆节点里找入口费了很长时间。其次是配色规则。我把节点配色做成变量状态色只保留四类稳定服务用蓝色系新增模块用绿色系废弃模块用灰色有风险或待整改的模块用橙色。这套规则直接用主题文件定义无论谁来画图只要引用同一份配置出来的图风格就能保持基本一致。很多工具都支持主题变量但真正用起来的人并不多。还有一个容易忽略的细节是图例。我要求每张图右下角必须带图例哪怕只是三行字也要把“实线代表请求链路、虚线代表异步消息、点线代表配置读取”讲清楚。这个习惯在一次跨团队协作中帮了大忙数据组同学第一次看我画的链路图尽管很多系统名字不认识但通过图例几分钟就搞懂了整体结构。3. 实操从零搭建一套 diagram-design 工作流3.1 目录结构设计和初始化如果你也想在自己项目里落地这套工作流建议先做一个最小实现不要一上来就追求大而全。我自己初始的目录设计非常克制diagram-design/ ├── themes/ │ ├── main.json # 全局配色、字体、层级底色 │ └── uml.json # UML 专用样式覆盖 ├── src/ │ ├── system/ │ │ ├── order-flow.mmd # Mermaid 流程图 │ │ ├── infra-topology.dot # Graphviz 架构图 │ │ └── payment-seq.puml # PlantUML 时序图 │ ├── business/ │ │ └── campaign-flow.mmd ├── scripts/ │ ├── render-all.sh # 批量渲染脚本 │ └── check-diagram.sh # 基本校验脚本 ├── dist/ │ ├── svg/ # 渲染产物 │ ├── png/ │ └── pdf/ └── README.md这个结构实际上是慢慢长出来的。一开始我只有 src 和 dist后来发现很多图的样式配置是重复的于是抽出了 themes再后来引入了校验脚本因为只靠人工 review 还是会漏掉孤立节点。如果你是从零起步可以先不管 themes直接把所有图源文件放 src 里能用命令行渲染出来就算迈出第一步。初始化环境很简单核心依赖只有三样Node.js跑 Mermaid CLI、Graphviz 本体、PlantUML需要本地 Java 环境。装好以后单独跑一个命令验证版本即可。这里我踩过一个坑Graphviz 在 macOS 上直接 brew install 装出的版本一般没问题但在某些 Linux 发行版上可能需要额外安装字体包否则导出 PNG 中文会变方框后面我会在问题排查章节详细说。3.2 需求拆解从一段简单描述到成品图选好了引擎和目录结构接下来我用一个真实例子走一遍完整过程。当时我在梳理一个订单系统的异步链路需求描述只有一句话“用户下单以后订单服务发消息给积分服务和库存服务然后回执给前端。”用 Mermaid 画出来源文件非常简单sequenceDiagram participant UI as 前端页面 participant Order as 订单服务 participant Stock as 库存服务 participant Points as 积分服务 UI-Order: 创建订单 Order-Order: 校验库存状态 Order--Stock: 扣减库存消息 Order--Points: 增加积分消息 Order--UI: 下单结果回执这段文本保存成order-flow.mmd然后在命令行里执行npx -y mermaid-js/mermaid-cli -i src/system/order-flow.mmd -o dist/svg/order-flow.svg渲染出来后你会发现Mermaid 自动把参与者和消息箭头都排得比较整齐基本不用手动调整。这个过程完美体现了“文本定义、命令行输出”的爽快感。如果再细一点我会在序列图下方用一段 Markdown 文字补充说明“库存扣减为异步、前端轮询最终结果”让图和文档成为一体的设计文档。之前在博客和文档站上发布时我一般再导出 PNG 版本并配合文字说明整个过程不超过三十秒。这比打开画板、拖矩形、拉箭头、导出图片要快太多更关键的是这个.mmd文件进了 Git任何改动都有迹可循。3.3 进阶实操用 Graphviz 画一张分层架构图Mermaid 适合快速表达但当图里的节点超过二十个或者节点之间关系比较复杂的时候我会换用 Graphviz。下面这张是一张精简版的订单系统模块依赖图只有六个节点但已经可以看出 Graphviz 的布局风格。digraph order_system { rankdirLR; node [shapebox, stylerounded,filled, fillcolor#E8F1FF]; edge [color#404040, arrowheadvee]; subgraph cluster_client { label客户端层; stylerounded; Web [labelWeb 端]; App [label移动端]; } subgraph cluster_gateway { label接入层; stylerounded; Gateway [labelAPI 网关]; } subgraph cluster_service { label应用层; stylerounded; Order [label订单服务]; Stock [label库存服务]; } subgraph cluster_db { label数据层; stylerounded; DB [labelMySQL 集群]; } Web - Gateway; App - Gateway; Gateway - Order; Order - Stock; Order - DB; }保存成infra-topology.dot后用命令渲染dot -Tsvg src/system/infra-topology.dot -o dist/svg/infra-topology.svgGraphviz 最强大的地方在于自动布局算法。我在这张图里只声明了节点和边并通过rankdirLR告诉它从左往右布局所有子图和层级排列都是算法自己完成的。如果你用手拖六个节点可能也要反复对齐十分钟但用 Graphviz 基本秒出。不过说实话Graphviz 默认样式确实不太好看所以经验是定义全局节点样式就是上面代码里的node [shapebox, stylerounded,filled, fillcolor#E8F1FF]。你可以把这行改成自己公司的品牌色。为了在多个图之间保持样式一致我把这些公共属性放到了themes/main.json里由渲染脚本统一“注入”这样就不会每张图复制粘贴了。3.4 自动渲染与基础校验落地人工在命令行渲染已经很方便但真正的质变是把它接入自动化流程。我的做法很简单写一个批量渲染脚本把源目录下所有类型的图文件识别出来调用对应的引擎渲染到 dist 目录同时做一次基础校验。#!/usr/bin/env bash set -euo pipefail cd $(dirname $0)/.. for file in src/**/*.mmd; do [ -e $file ] || continue outdist/svg/$(basename ${file%.mmd}).svg npx -y mermaid-js/mermaid-cli -i $file -o $out done for file in src/**/*.dot; do [ -e $file ] || continue outdist/svg/$(basename ${file%.dot}).svg dot -Tsvg $file -o $out done for file in src/**/*.puml; do [ -e $file ] || continue outdist/svg/$(basename ${file%.puml}).svg plantuml -tsvg $file -o $out done echo render done.校验脚本则稍微做了一点有意思的事。Mermaid 支持 JavaScript 接口我用mermaid-js/parser把图定义解析成语法树检查节点和边的定义数量看看有没有“只声明但从未被引用”的孤立节点。第一次跑校验时我发现自己画的一张存量架构图里居然有两个已经下线但忘记删除的节点这正好说明了自动检查的价值。我把渲染和校验都封装到 npm scripts 里然后在 CI 流程的分支构建阶段执行。只要提交时图语法有错误或者校验不通过流水线会直接失败开发者必须修好之后才能合并。这样做的好处是再也没出现过文档站点上挂着一张破损图的情况。4. 常见问题与排查技巧实录4.1 中文乱码的三种成因和解决办法代码化绘图最常踩的坑之一就是中文乱码我在 Graphviz 和 PlantUML 上都栽过跟头。乱码一般分三种情况一种比一种隐蔽。第一种是字体缺失。Graphviz 默认字体在 Linux 服务器上往往不支持中文导出的 PNG 里中文变成了一个个方框。解决办法是给渲染脚本指定字体参数比如在 dot 文件里加入fontnamePingFang SC或者用-GfontnameMicrosoft YaHei覆盖默认设置。服务器上如果提示找不到字体还要先安装相应的字体包。第二种是文件编码问题。Windows 环境下写出的源文件默认可能是 GBK 编码而命令行工具按 UTF-8 解析于是中文直接变成乱码。这个最直观的解决办法是统一把源文件存成 UTF-8并建议团队用同样的编辑器配置。第三种最坑Mermaid CLI 在部分环境下需要额外的 Puppeteer 沙箱参数否则渲染出的 SVG 里中文字体被跳过。我在 CI 容器里跑渲染时遇到过这个最终是在启动参数里加了--no-sandbox选项并安装了完整的系统字体库。建议遇到问题时先调字体、再查编码、最后看渲染进程日志按这个顺序排查最省时间。4.2 布局溢出与连线交叉问题自动布局算法并不总是完美。Mermaid 画的节点一多或者文字太长容易出现“节点重叠”或“连线交叉严重”的问题。遇到这种情况我的第一反应不是手动挪坐标而是检查是否选错引擎。如果你用 Mermaid 画的是超过 30 个节点的依赖图那它自动布局的短板就会暴露得很彻底。此时应该考虑切到 Graphviz用它的层级布局算法跑一遍通常能把交叉控制得很好。而如果业务上还是想用 Mermaid 出图可以调整几个参数比如给节点设置width和height约束或者在 Mermaid 的配置里开启flowchart的useMaxWidth选项。但坦白说这些都是打补丁根源上还是要把大图拆成多个子图。我在项目里定了两个规则单张 Mermaid 图的节点不超过 15 个超过 15 个就拆分或改用 Graphviz。这个规则看起来很粗暴但执行以后图的可读性提升非常明显。读者看一张图的时间一般只有几十秒信息密度过大只会劝退人。4.3 大图渲染卡顿与“拆分再组合”策略当图的规模真的大到一定程度比如上百个节点即使改用 Graphviz渲染时间也会明显增加而且生成的 SVG 文件体积会膨胀到几 MB浏览器打开都有点吃力。我的建议是不要奢望一张图表达所有信息而是把大图拆成“总览图 局部详图”两层结构。总览图只保留核心模块和模块间的粗粒度依赖局部详图再对每个模块内部展开。这样每张图的信息量都在合理范围渲染快、加载快、读者也更容易理解。这几年画技术图最大的经验就是好的架构图不是把所有细节塞进一张画布里而是让读者能在三十秒内建立整体认知然后按需往下钻取。另外如果是给外部文档系统用尽量优先输出 SVG 而不是 PNG。SVG 是矢量格式缩放不糊文件体积通常比同等品质的 PNG 更小。除非有特殊要求比如微信公众号编辑器对 SVG 支持较差否则我默认都输出 SVG 作为主格式。4.4 团队协作中的冲突处理图和代码一样进 Git 之后最常见的协作问题就是“同一行被多人改了”。如果两个人同时修改同一个.mmd文件的节点定义Git 合并在文本层面其实很容易处理大多能自动合并。但问题是即使文本合并成功语义上可能有冲突比如两个人往同一张架构图里分别加了两个名称很像的新服务从 Git 角度看不冲突但业务上可能重复了。我建议团队把“图源文件评审”纳入代码评审流程指定一位同学作为“图管理员”合并前重点关注命名规范、层级归属和是否真有这个模块。另外每次比较大的结构调整尽量单独开 commit不要把图画修改混在业务代码提交里否则 review 的时候基本没人会注意到图的变更。如果多人高频并发改图确实多也可以把一个大的system.dot按模块拆成多个子文件然后用聚合脚本在渲染时include。这样不同模块的同学各改各的文件冲突概率会大大下降。5. 一些个人体会这件事最值得投入的地方整个 diagram-design 项目前后大概迭代了三个版本第一个版本只是把大家画的图手工转换成 Mermaid第二个版本加上了渲染脚本和 CI第三个版本才形成主题规范、目录约定和自动校验。回头看最值得投入的地方并不是某个绘图工具有多好用而是把“图表”从一种静态产物变成了可以和代码同步演进的活资产。我后来在另一个项目里推广了类似的实践团队里几个之前从不画图的同学也能快速上手。因为他们只需要记住“写 Markdown 段落 定义文本图”这一种心智模型不需要再学一套复杂的画板操作。流程图、时序图、状态图在排障时尤其好用把一次线上问题的调用链用代码化时序图画出来贴到复盘文档里既清楚又能反复迭代。如果你也想尝试我建议从一张经常要维护的图开始。比如选一张你最近更新频率最高的流程图或者系统链路图把它改写成代码化定义配上渲染脚本。一旦感受到“改一行文本、半秒出图、提交入库”的节奏你就会明白为什么我后来再也不想用拖拽方式画技术图了。对了最后再分享一个小技巧Mermaid 和 Graphviz 的源文件其实可以直接放进 Markdown 的代码块里很多文档系统支持内置渲染。这意味着你在写一套文档的时候图和正文能保持完全同步不需要单独维护图片文件。这个组合拳打通以后技术文档的维护成本会显著下降这也是 diagram-design 给我带来的最大收益。
返回列表