
先交代一下背景。我接手团队文档体系整理时发现一个很典型的问题架构图、流程图、数据模型散落在各种绘图软件和个人电脑里版本对不上注释在邮件里最新版永远在某个同事的本地文件里。讨论方案时打开三台电脑三张图互相冲突谁也说服不了谁。后来我基于diagram-design的思路搭了一套完整的“图表即代码”工作流把画图从手工拖拽变成写文本、跑命令、看产物彻底解决了维护和协作的问题。这篇文章就是把整套方案的选型过程、落地细节、踩过的坑都梳理一遍适合后端开发、架构设计、DevOps、技术文档维护者也适合被流程图版本管理折腾过的人参考。1. 为什么我最终放弃了“画图”改用代码定义图表1.1 传统绘图方式最要命的问题不是“难看”而是“不可维护”用绘图软件画图人人都会问题出在图的“生命周期”上。一张架构图手动画完只能以图片文件的形式存在图片是二进制文件无法用常规的文本 diff 去比较两个版本的差异。同事改了一处连接线你根本看不出来他改了哪。想评论一句“这个模块不应该直连数据库”只能在聊天工具里发消息图和评论是分离的过两周再翻这条上下文就丢了。更麻烦的是复用。今天画微服务架构图明天画部署拓扑图很多节点是重复的但手绘工具里没有“引用”的概念只能重新画一遍。画完之后如果某个中间件版本升级所有涉及它的图都要手动改一遍。团队人少的时候这事还能靠自觉维持团队一扩大图就会从“信息载体”退化成“装饰品”——没人敢信图大家只看代码。1.2 把“图”当成一种代码资产diagram-design这个思路的核心是改变对图的认知图不是一次性的交付物而是跟源代码一样需要被版本管理、评审、持续更新的工程资产。既然图是资产那它就必须满足几个硬指标可 diff、可评审、可复用、可自动化生成。文本化的图表定义天然满足这些条件。用一段结构化的文本描述节点、连线、分组、样式工具负责把文本渲染成视觉图形。文本可以放进 Git 仓库每一次修改都能看到清晰的 diff文本可以拆成公共片段多个图表引用同一份节点定义文本可以丢进 CI 流程语法错误直接在构建阶段暴露根本不会流到文档网站上。1.3 这套方案适合谁不适合谁坦白说不是所有人都需要这套流程。如果是临时画一张给客户看的汇报图直接打开绘图软件拖两下效率更高。但如果你属于下面这几类人我强烈建议试试文本化图表后端开发画系统架构、服务依赖、数据模型图要跟代码一起提交。架构师需要维护一份长期有效的顶层设计图经常要对比不同版本的演进。DevOps画部署拓扑、链路监控、网络分区图要跟着基础设施配置走。技术文档维护者文档里的图非常多大家来更新文档时不能指望每个人都精通绘图软件。反过来如果你只是偶尔画一张思维导图或者需要高度自由的艺术化视觉设计那文本化工具反而会限制你。工具选型不能只看“强大”要看“合适”。2. 工具链选型与整体方案设计2.1 主流的“图表即代码”方案到底怎么选定下“图跟代码走”的大方向之后首先要选的就是文本到图形的渲染引擎。市面上成熟的方案不算少但各自侧重点差异很大。我把主流的几类放在一起做过一轮对比最终结论其实挺反直觉的。方案一声明式 DSL典型代表Mermaid语法设计贴近自然语言一个流程图写起来就像在描述一段话“A 指向 BB 指向 C”。上手成本极低团队里非技术背景的产品经理也能看懂源文件。渲染效果默认走简洁风格不需要手动调布局算法。缺点在于复杂图形的控制力弱不好做精细的坐标控制特殊布局得靠技巧绕过去。方案二传统自动布局引擎典型代表Graphviz基于点线图模型布局算法非常强处理几百个节点的复杂网络图时自动排布效果依然可读。但它的语法是为老派工具设计的描述方式偏底层学习曲线陡峭而且中文渲染、样式定制都容易踩坑。方案三工程化 UML 工具典型代表PlantUML在时序图、用例图、状态机这些标准 UML 场景下非常顺手写起来也快。缺点是视觉风格偏老气高度定制化的时候语法不够直观团队维护起来新鲜感一过就容易排斥。方案四通用绘图软件好处是灵活、所见即所得坏处前面说过不可 diff、不可脚本化、版本管理全靠人工自觉。作为补充工具没问题作为团队统一的主链路不推荐。2.2 为什么“团队都能看懂”的语法是第一选择我最后选了 Mermaid 这条路线核心原因只有一个阅读成本。图表源文件不只是给机器渲染用的它首先是一份给人阅读的文档。Graphviz 和 PlantUML 功能很强但让后端同事看一眼源文件就能直接上手改不现实。Mermaid 的语法接近描述性语言就算你不看任何文档看到A--B也能猜出是“A 指向 B”。这种低门槛带来的最大价值是团队里每个人都愿意去更新图而不是把维护图的职责推给某一个人。另外Mermaid 的生态完整度在声明式 DSL 里是最高的。官方提供的命令行工具支持从文本直接渲染 PNG、SVG也支持把图嵌入到主流文档系统里还能在浏览器脚本里动态控制。既有静态产物也有运行时渲染足以覆盖从架构设计到文档展示的整个链路。2.3 整体工作流怎么搭三个层级的组合diagram-design不只包含一个渲染工具我把它理解成三条链路的组合源文件层目录里用.puml、.mmd这样的文本文件存放图表定义按业务模块分目录管理跟代码仓库放一起。渲染层用命令行工具把源文件批量转换成图片或 SVG 文件输出到统一的资产目录。这里需要维护一个简单的脚本或任务配置把“渲染什么格式、输出到哪里”固定下来。发布层文档系统、静态站点、代码仓库自动引用渲染产物读文档的人看到的是最终图维护的人只需要改源文件。单看任何一个环节都不稀奇但把三层串起来之后维护图的流程就变成了“改文本 → 跑脚本 → 自动更新”和“改代码 → 跑测试 → 自动部署”是一个模式。图就不再是游离在开发流程外的东西了。3. 核心实操从一段需求到一张能用的图3.1 先用一个真实需求来说明整个流程在模拟项目X里我需要给一个跨平台系统画一张核心架构图。需求是这样的系统有 Web 端、移动端、桌面端三类客户端统一经过一个 API 网关网关再把请求分发到三个核心业务服务分别是用户服务、订单服务、支付服务用户服务依赖一个缓存订单服务依赖数据库支付服务需要跟外部支付网关通信。面对这样的需求手绘的第一反应是“左边放客户端中间放网关右边放服务”然后开始拖线。在文本化工作流里我一般按三步走。3.2 第一步先把节点和连线写出来完全不考虑样式最初始的 Mermaid 流程图长这样graph LR Web[Web客户端] Mobile[移动客户端] Desktop[桌面端] Gateway[API网关] UserService[用户服务] OrderService[订单服务] PayService[支付服务] Cache[(缓存)] DB[(数据库)] ExternalGateway[外部支付网关] Web -- Gateway Mobile -- Gateway Desktop -- Gateway Gateway -- UserService Gateway -- OrderService Gateway -- PayService UserService -- Cache OrderService -- DB PayService -- ExternalGateway这段文本已经完整表达了系统的拓扑关系和调用链路渲染出来就是一张可读的图。这一步的关键是“先连通再美化”。很多人在这一步就开始纠结线的颜色、节点的形状、间距这是新手最常犯的错误。文本化图表的修改成本低但也不是越低越好先确认结构和信息无误再进行下一轮加工效率最高。3.3 第二步用子图做视觉分组信息层级立刻清晰第一步的图能看但所有节点摊在一张大平面上阅读时需要自己分辨“哪些节点属于前端哪些属于后端”。这时候就该引入subgraph了它的作用相当于在画布上画一个大的虚线框把相关的节点归拢到访真一个容器里。改进后的源文件graph LR subgraph Clients[客户端层] Web[Web客户端] Mobile[移动客户端] Desktop[桌面端] end subgraph Edge[接入层] Gateway[API网关] end subgraph Services[业务服务层] UserService[用户服务] OrderService[订单服务] PayService[支付服务] end subgraph Storage[数据层] Cache[(缓存)] DB[(数据库)] ExternalGateway[外部支付网关] end Web -- Gateway Mobile -- Gateway Desktop -- Gateway Gateway -- UserService Gateway -- OrderService Gateway -- PayService UserService -- Cache OrderService -- DB PayService -- ExternalGateway加了子图之后读者能直接从大结构上定位客户端层在左边数据层在右边业务服务在中间。这比单纯靠连线来传达“系统分层”要直观得多。子图这个语法点值得多提一句它不只是视觉上的分组还能在渲染时影响布局算法让同组节点优先靠近排列减少组间连线交叉的概率。3.4 第三步给连接线加语义让图“会说话”一张架构图如果只有箭头读者只能看出调用了谁看不出调用类型和依赖方向信息密度是不够的。Mermaid 的连接线支持多种线型和文本标签我在这张图里把“同步调用”“异步事件”“数据读写”区分开Web --|HTTPS 请求| Gateway Mobile --|HTTPS 请求| Gateway Desktop --|HTTPS 请求| Gateway Gateway --|RPC 同步调用| UserService Gateway --|RPC 同步调用| OrderService Gateway --|RPC 同步调用| PayService UserService -.-|缓存读写| Cache OrderService -.-|SQL 读写| DB PayService o|支付回调| ExternalGateway这里的线型细节值得展开说实线箭头表示同步调用虚线带点表示异步或弱依赖双线加空心圆表示事件回调或长连接。这些符号不是随便定的而是沿用了我自己在团队里定的“图例规范”。建议每个项目仓库里都维护一个小的 README写下这些约定否则别人看你图的时候线型含义全靠猜图的可读性就打折扣了。这一步做完再把图渲染出来从三层结构到调用语义都清清楚楚。整个改图过程没有碰过一次鼠标拖拽全部是对文本的增删改。3.5 文档集成让图直接长在文档里对纯文本工作流来说渲染成 PNG 只是个中间产物更理想的落地方式是把图表定义嵌进文档。例如在 Markdown 文档中Mermaid 代码块可以在构建文档站点时被脚本识别并自动渲染这样读者在网页上看到的是图维护者在源文件里看到的是文本。文档和图的修改可以出现在同一次提交里评审者在同一个 PR 中既能看文字说明又能看到图的变化。我实际用的静态站点构建流程是这样的Markdown 文件里写图表文本构建脚本把所有图表代码块提取出来渲染成 SVG 文件同时替换原文引用位置。每次文档发布图都会自动是最新版本不需要任何人手动去同步图片。4. 生命周期管理让图表跟着代码一起走4.1 渲染流程的工程化不要在本地手动双击执行刚开始用文本化图表时我犯过一个低效的错误每次改完源文件都在本地手动执行一遍命令行渲染把图片输出到 git 目录然后连同图片一起提交。结果就是源文件改了十个字图片文件也变了一次仓库里充满了二进制差异。后来我把渲染流程脚本化放进统一的命令行任务里全部自动化。脚本的核心逻辑是一个循环遍历指定目录下的.mmd文件调用渲染工具输出 SVG 和 PNG 到构建目录如果渲染失败则打印出错文件名和行号。关键点是“不把二进制产物提交到代码仓库”而是让 CI 在构建阶段渲染发布到文档站点。这样仓库里只有文本文本永远干净图片永远由最新文本生成不会出现源文件和产物对不上的问题。4.2 自动校验把“画错连了线”拦截在构建之前图表这么多光靠肉眼审查语法错误不现实。我加了两个层级的自动校验第一层是语法的机器校验渲染失败直接返回非零退出码构建终止第二层是连线逻辑校验用脚本解析出所有节点和边检查是否有“引用了不存在的节点”“孤立节点没有连接”“一个服务被两个上层节点同时直连”这类结构问题。第二层校验我写了一个很小的解析脚本不一定用复杂框架几个正则加数据比对就行。它的逻辑简单但非常实用先把节点地址收集成集合然后逐一核对每条连线两端的名字是否在集合里。检查结果直接打印到控制台有错误就退出失败防止带病图发布上线。4.3 版本管理与审查机制图也要走“代码评审”有了文本化图之后代码审查的模式可以直接套用。开发者在分支里修改.mmd文件提交 PR评审者看 diff 时能精确看到“某条线从实线变成了虚线”“某个节点从订单服务改成了支付服务”这样的评审粒度在传统图片时代完全做不到。而且文本 diff 也能看出来图的结构演变趋势新增了一个子图说明某块能力被拆分出来了删除了一条连接线说明两个服务之间的直接依赖被降级了。4.4 更新图的时机跟着代码提交而不是跟着回忆团队协作中最常见的图腐烂方式是“等有空再更新图”。我定下的规则很简单只要这一次提交变更了任何服务的接口、依赖、部署关系就必须在同一个 PR 里同步更新对应的图表源文件。这个规则跟“改了接口必须改测试”是一个逻辑。可以由 CI 做一次粗略的强制校验如果代码目录的某类文件变更而图表源目录没有任何改动就给出警告提醒。虽然不能完全解决问题但至少能让图腐烂的速度下降一个量级。5. 踩坑记录与排查技巧速查表5.1 中文乱码渲染出来全是方块Mermaid 本身支持中文文本但命令行渲染时依赖系统中的字体库。我在一个精简版服务器上遇到过全部中文显示成方块的怪问题排查到最后发现是系统没有安装中文字体。解决方式也不复杂在构建环境中提前把中文字体安装进去或者渲染脚本里指定一个明确的字体路径。这里提示一点用docker容器工作流时镜像里往往默认不带中文字体别在本地渲染正常就以为线上也正常。5.2 布局失控加了大量子图之后节点乱飞子图能分组但也不是万能的。小组多、组间连接多的时候底层布局引擎偶尔会把自己排“乱”出现长线跨图甚至节点完全重叠的现象。我的经验是不要试图把几十个子图全部塞进一张大图里如果一张图有超过三十个节点优先考虑拆图。一张职责明确的图比一张“全家福”有价值得多。5.3 子图嵌套的层级限制声明式 DSL 对子图嵌套支持是有一定限制的在一个子图里再套子图很多版本下会直接渲染失败或忽略结构。遇到分层特别多的场景我目前的方案是改画多张图每张图表现一层视图再通过页面索引或链接互相跳转。这也倒逼设计者控制层级数量——如果一张图需要嵌套四层结构说明这张图本身承载了太多内容考虑拆分更合理。5.4 大量节点渲染性能慢节点超过二百个的图命令行渲染可能达到数十秒这在 CI 中是不可接受的性能问题。拆图优先其次可以调整渲染引擎的布局参数减少不必要的计算量。总之图和函数一样过长过大就该拆。5.5 长文本节点会被截断或换行混乱节点标签太长默认样式下会被裁切或者换行位置极不自然。解决方案是在标签中显式使用换行转义符或缩小字号、放大节点固定尺寸。不过从设计角度来说标签超过二十个字本身就该精简图里的文字应该指的是语义名称详细说明写进文档别塞在图里。5.6 链接失效了但没人发现文献里嵌入的图表如果有跳转链接时间久了很容易变成死链。我在脚本里加了链接检查提取图中所有外部链接逐个请求有效性响应不正常的直接告警。这不是图表渲染能做出来的功能但却是长期信任这张图的重要保障。我把遇到频率最高的几个问题整理成了速查表现象原因处理方式中文渲染成方块系统缺中文字体构建环境装字体指定字体路径节点重叠严重子图太多且连接交叉复杂拆图单图节点控制在三十个以内子图嵌套失效嵌套层级超过引擎限制改多层视图一层一图CI 渲染极慢单图节点过多拆分大图调整布局参数渲染成功但链接点不了外部链接未配置手动补全链接加自动检查本地与构建结果不一致构建环境缺依赖统一构建镜像与本地工具版本6. 从架构图到更大的图景以及我的实际体会6.1 这套思路的迁移能力diagram-design解决的不只是架构图的维护问题。这个模式完全可以平移推广环境拓扑图、部署架构图、定时任务依赖图、数据血缘图只要本质上是“一堆节点加一堆连线”都可以用同一种文本化工作流来管理。我后来把数据库表关系图、服务调用链图也统一纳管了还额外实现了一个自动生成脚本直接从接口定义文件生成服务依赖图图不再是人工产物而是构建产物。6.2 图是文档不是装饰说一点个人的真实体会。执行这个方案半年之后团队对图表的信任度回来后反而反过来提了很多改进需求要调整写法要增加自动生成要直接嵌入故障手册。图一旦变成真实反映系统状态的资产它就开始产生架构治理层面的价值这不只是一个画图工具升级而是文档思路、协作模式、工程习惯的整体变化。如果你正在被图版本混乱、更新不及时、评审困难这些问题困扰可以尝试用最小成本起步找一个仓库新建一个目录放一张当前系统架构图的文本源文件加上官方命令行渲染工具然后要求接下来所有图更新都在这个文件里完成。用不了一个月你会发现自己再也不想回到“打开软件手动拖线”的日子。