ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

代码化图表(diagram-design):让架构图像代码一样可版本管理

代码化图表(diagram-design):让架构图像代码一样可版本管理 接手老项目的时候最让人头疼的往往不是代码本身而是那堆早已和实现脱节的文档。架构图还是三年前的版本时序图画的调用链和代码里对不上新来的同事只能对着过期的图形连蒙带猜。我自己就吃过这个亏——有一次排查线上告警照着文档里的依赖关系查了半天最后发现那个节点早就拆分成三个独立服务了。从那以后我开始认真对待 diagram-design 这件事也就是用代码来定义和管理图表让图表像代码一样进入版本控制、可审查、可追溯。这篇内容适合所有需要维护架构文档的开发者、技术文档工程师以及想在团队里把“画图”这件事规范化的朋友。1. 从手绘到代码为什么“可版本管理的图表”才是协作刚需先聊聊我为什么从 Visio、draw.io 这类可视化工具迁移到了代码化图表方案。最早画架构图我习惯用拖拽式工具画完导出 PNG 发到文档里。问题在于这张图过两个月基本就废了。业务一迭代服务拆分一变没人记得去更新那张静态图片就算记得打开源文件改起来也麻烦——你还得找到当初那个.vsdx或.drawio文件存在哪个共享盘里。代码化图表的逻辑完全不同。你把图表的定义写成纯文本文件放进 Git 仓库和代码一起提交。架构变了顺手改一行依赖关系跑一下生成命令新的图就出来了。最重要的是任何人都能通过git diff看到这张图改了什么评审 Pull Request 的时候架构变更一目了然。这不是什么新潮理念跟“文档即代码”“配置即代码”是同一个路子。对团队协作来说代码化图表还有一个隐形好处它是结构化的数据而不是像素。结构化意味着你能对它做静态检查——比如检测出“这里画的调用方在代码里根本不存在”或者“这张时序图里的接口响应字段和实际 DTO 对不上”。拖拽工具给不了这种能力它只能保证你画出来的东西“看起来像那么回事”。所以我的结论很直接如果你的架构图超过一个月还在被团队当作参考依据它就应该被代码化如果你画图是为了交付一次性汇报材料那用什么工具都无所谓怎么快怎么来。diagram-design 的价值不在于“画得好看”而在于“能持续维护、能参与评审、能和代码共同演进”。2. 工具选型Mermaid、PlantUML、Graphviz 到底怎么选代码化图表这个领域主流选择来回就是那三四个Mermaid、PlantUML、Graphviz偶尔还有 D2 和 Structurizr 这样的新玩家。我全部实测过一轮在不同的项目里也分别用过简单说说它们的性格差异。2.1 Mermaid上手最快适合嵌在 Markdown 里Mermaid 是现在 GitHub 和各大文档平台支持得最好的方案最大的优势是零成本起步。你在任何一个.md文件里写一段graph TD开头的文本渲染出来就是一张流程图。它支持流程图、时序图、类图、状态图、甘特图、饼图覆盖面足够日常使用。我对 Mermaid 的定位是“文档图表”适合配合技术方案文档、API 说明、快速画给同事看的逻辑图。语法非常接近自然语言比如A--B就表示 A 指向 B 的箭头A-- 调用 --B可以在箭头上加标签。对于不常画图、偶尔来一笔的开发者Mermaid 几乎没有学习成本。但它有几个硬伤。第一复杂布局不受控。Mermaid 的自动布局引擎比较“倔”节点一多就容易绕线、重叠你想手动调整节点位置选项很少。第二不支持从数据源动态生成——它只能渲染你手写的静态文本没法从代码或数据库元数据里拉信息。第三对 UML 规范的支持是“简化版”画业务流程图绰绰有余画严谨的类图、对象图就力不从心。2.2 PlantUMLUML 场景下的老大哥PlantUML 比 Mermaid 更早出现在代码化图表这个赛道Java 生态的朋友应该很熟悉。它的 UML 支持是最完整的时序图、用例图、组件图、部署图、活动图该有的都有而且语法细节更贴近 UML 规范。最关键的是PlantUML 支持通过!include引入外部文件也支持用预处理器做条件判断这意味着它可以玩出“模板化配置”的花样。拿时序图举例PlantUML 的参与者、激活条、消息序号、注释、组合片段这些元素都能精细控制渲染出来的图也更有“工程图”的味道。相比起来同样一张时序图Mermaid 画出来的更像“示意图”。PlantUML 的缺点是启动重、依赖 Java 运行时、渲染速度偏慢。如果你只是想在 Markdown 里快速画张图为了一个 PlantUML 还得装 JDK确实有点小题大做。另外它的语法虽然强大但记忆负担比 Mermaid 大不少入门曲线陡一些。2.3 Graphviz底层布局引擎适合复杂节点拓扑Graphviz 其实不是专门为“画图”设计的工具它是一套开源图可视化引擎核心是 DOT 语言和布局算法dot、neato、fdp、sfdp 等。Mermaid 的底层渲染某种程度上也借鉴了图布局的思路但 Graphviz 自己走的是纯粹的函数式路线你定义节点、边、属性和子图它用算法计算布局输出 SVG/PNG/PDF。Graphviz 最擅长的是复杂有向图和无向图比如依赖关系图、调用链图、网络拓扑。我自己用 Graphviz 画过一次全链路服务依赖图七十多个服务节点边接近两百条Graphviz 的dot布局算法依然能理出一个清晰的层级结构。换 Mermaid 早就绕成毛线团了。但 Graphviz 的 DOT 语法非常“工程化”写起来像是手写 JSON 一样繁琐可读性远不如 Mermaid。它适合做工具链的一部分不适合人肉维护。2.4 一张表看懂选型工具最佳场景语法友好度UML 支持布局可控性动态生成运行要求MermaidMarkdown 文档流程图、时序图高部分低不支持无PlantUML严谨 UML 图、有 Java 环境中完整中支持 !includeJavaGraphviz大规模节点拓扑、依赖关系低无高支持无D2现代架构图、基础设施图高部分中支持无如果你问我个人偏好我现在的默认组合是日常文档用 Mermaid正式的架构设计评审文档用 PlantUML需要呈现大量服务节点关系时用 Graphviz。选型这件事没有银弹关键是先想清楚图的生命周期——是画完就扔还是要长期维护。3. 让图表跑起来的核心语法与骨架设计三个实战示例工具聊完了直接上干货。这一节我用三个真实场景来拆解 diagram-design 的具体写法每一步都能直接抄走。3.1 场景一用 Mermaid 画服务部署流程图假设你要给现有微服务架构画一张“从用户请求到服务响应”的部署流程图包含负载均衡、网关、两个业务服务和一个数据库。Mermaid 的写法很直白graph TD User[用户] --|HTTPS| LB[负载均衡] LB --|转发| Gateway[API网关] Gateway --|路由| UserService[用户服务] Gateway --|路由| OrderService[订单服务] UserService --|读写| DB[(MySQL)] OrderService --|读写| DB OrderService --|异步消息| MQ[(消息队列)]这段代码渲染出来就是一张带箭头的分层流程图。注意三点节点名后面的方括号是节点显示文本圆括号表示圆角矩形双括号表示数据库圆柱体箭头上的竖线标签是边的描述graph TD里的TD表示从上到下布局改成LR就是从左到右。实际项目中我会把这张图拆成“部署图”和“调用图”两张部署图只关心物理拓扑哪台机器跑哪个服务调用图只关心逻辑依赖谁调谁混在一起会非常乱。画图的第一步不是打开编辑器而是在脑子里先问自己这张图想表达什么层次的抽象。3.2 场景二用 PlantUML 画微服务时序图时序图比流程图更适合描述“一次请求经过多个服务的完整链路”。一个典型的查询订单详情场景涉及网关、订单服务、用户服务、商品服务四个参与者PlantUML 的表示方法是startuml actor 客户端 participant API网关 as gw participant 订单服务 as os participant 用户服务 as us participant 商品服务 as ps 客户端 - gw: GET /order/{id} gw - os: 转发请求 os - us: 批量查询用户信息 us -- os: 返回用户数据 os - ps: 批量查询商品信息 ps -- os: 返回商品数据 os -- gw: 聚合订单详情 gw -- 客户端: 200 OK enduml这个例子最有价值的地方在于as别名。默认情况下 PlantUML 会把显示文本和参与者 ID 绑定但经过as gw改名后正文里所有引用都用短 ID改显示名称只需要改第一处定义。真实项目里参与者名称经常调整这个习惯能省很多事。时序图画完之后我强烈建议再做一步对照代码把消息顺序过一遍。画图时最容易犯的错误是“画的是理想流程”而代码里实际的调用顺序可能多了一次缓存查询、少了一次远程调用。时序图是 DIAGRAM-DESIGN 里与实际代码耦合最紧的一种图画完不验证等于白画。3.3 场景三用 Graphviz 画服务依赖全景图当服务数量超过二三十个想要一张能表现全部依赖关系的全景图我会用 Graphviz 的 DOT 语言。核心结构是digraph dependencies { rankdirLR; node [shapebox, stylerounded, filled, fillcolor#f0f0f0]; 用户服务 - 订单服务 [labelRPC]; 订单服务 - 商品服务 [labelRPC]; 订单服务 - 支付服务 [labelRPC]; 支付服务 - 财务系统 [label异步]; }几个实用技巧rankdirLR设置从左到右的布局长依赖链看横排比竖排舒服label属性标注边类型node统一设置节点样式避免大量重复属性头。真正生产级的全景图节点数通常上百手写 DOT 不现实。正确做法是通过脚本从注册中心或链路追踪系统导出依赖数据再根据数据生成 DOT 文件。Graphviz 在这里的角色更像“渲染器”你只需要关注数据准确性布局的事交给dot算法。4. 团队落地这条路我踩过的坑和总结出的规范工具层面说完了这部分可能是最有价值的。我从单个开发者真正在项目里把 diagram-design 铺开遇到过一堆问题画图本身从来不难难的是让这玩意儿在团队里活下来。4.1 踩坑实录从“图找人”到“人找图”最早我把架构图放在 Confluence 里每张图附上手绘工具的源文件链接。结果不到两个月图就完全失联了——新同事不知道去哪个空间找文档老同事改过代码也懒得回去更新图。后来我做过一次失败的尝试把图放在代码仓库的docs/目录里但因为是手动导出的 PNG每次更新都要“改源文件-重新导出-替换图片-提交”步骤一多慢慢就没人愿意动了。直到我把图表定义文件.puml、.mmd、.dot直接放在代码仓库里并配上自动渲染脚本事情才出现转机。提交代码的人只要保证图表源文件同步更新CI 会自动渲染成 PNG 并附到 PR 描述里。这个流程建立起来后“图过期”的问题才真正被遏制住。图不再是需要专门抽出整块时间去维护的“文档任务”而是和代码变更绑定的日常动作。4.2 接入 CI/CD 流水线让图表也走自动化顺着刚才说的思路来聊聊我在流水线里具体怎么“塞”渲染任务的。以 GitLab CI 为例我在.gitlab-ci.yml里加了一个 jobdiagram-render: stage: docs image: think/plantuml:latest script: - find docs/diagrams -name *.puml | xargs -I{} plantuml -tsvg {} artifacts: paths: - docs/diagrams/**/*.svg expire_in: 30 days这个 job 做的事情很朴素扫描docs/diagrams目录下的所有 PlantUML 文件渲染成 SVG 作为流水线产物。核心价值是“让每次代码变更都顺带检查图表有没有写错语法”——如果 PlantUML 文件本身有语法错误CI 会直接报错相当于给图表加了一道静态检查。如果你用的是 Mermaid渲染检查更简单用npx mermaid-js/mermaid-cli一样能跑批处理。Graphviz 更不用说系统级命令即可搞定。这一步的意义是图表不再是“画完就删的草稿”它获得了和代码同等的工程化待遇。4.3 团队约定我总结的图表现状规范没有规矩不成方圆。代码化图表要稳定运行必须建立几条硬性约定。这里分享我的五条铁律第一源文件和渲染产物必须分离提交源文件进 GitPNG/SVG 产物只出现在发布包或 CDN避免二进制文件膨胀仓库体积。第二每张图必须有一个所有者如果团队变动导致没有人“认领”某张图这张图就应该被删除或归档没人维护的图比没有图更危险。第三图的修改必须走 MR/PR 评审评审者除了看代码也要看图的变化是否与逻辑变更一致。第四图内禁止出现具体 IP、端口、账号等环境敏感信息用占位符如${ENV}代替防止文档泄露内部细节。第五所有架构图必须标注“最后一次验证日期”我带过的团队里这条约定救了不少人。这些规范不是我拍脑袋定的每一条背后都对应一次真实的踩坑。比如最后一条是因为我接手过一个老系统文档里显示的数据库连接串和实际环境完全不同排查问题的人拿着文档去配环境折腾了大半天。5. 更进一步的玩法从图到架构治理diagram-design 做到能维护、能自动渲染之后还可以往前再走一步——让图从“给人看的说明”变成“约束架构的工具”这是架构治理的范畴了。5.1 用“图即代码”做架构规则检查既然图表源文件是结构化文本那么理论上就可以用脚本去检查它的合规性。比如 Mermaid 的类图文件可以解析出各类之间的依赖关系然后跑一条规则“订单模块的类不允许依赖支付模块的类除非通过接口”。一旦有人改了依赖方向CI 可以直接判定失败。我见过一些实践得很好的团队他们把 Deployment 图作为 Kubernetes 集群资源定义的一部分架构文档直接由实际的 YAML 清单生成。也就是说部署图不是画出来的是“长”出来的——从编排清单反向生成图。这就达到了更高的境界图和系统真实状态永远一致因为它就是系统的一部分。PlantUML 配合!include和变量也能实现类似效果。把环境配置抽成独立的文件多个图共享同一份配置改配置时所有图同步更新一致性靠的不再是自觉而是结构设计。5.2 架构决策记录与图的绑定我在团队里推行过 ADRArchitecture Decision Records的实践每次重要的架构决策写一份简短的 ADR 文档内容包含背景、决策、替代方案、后果。那图表呢它和 ADR 是天然的绑定关系——ADR 描述“为什么这么设计”图描述“设计成什么样”。我的做法是在 ADR 文档中用!include引入对应的图表源文件评审一个 ADR 时必须同时看图与描述是否匹配。这样就把“画图”从独立的文档行为变成了架构评审流程的一部分。图作为一种可追踪的工程资产参与到了决策闭环里。5.3 对“画图”这件事的重新定义走到这一步再回头看diagram-design 早就不是一个“怎么画图”的工具问题了。它背后其实是一套工程理念图解文档应该是活的和代码共享同样的生命周期而不是在某个时点的“快照”。用代码定义图表意味着你可以版本化、可审查、可自动化、可校验这是任何拖拽工具都给不了的。在实际操作中我也发现一个值得留意的现象过度依赖图表会成为新的负担。有些团队走火入魔流程图、时序图、状态图画得比代码还多结果图没人看得过来反而成了噪音。我的经验是一张图的存在价值必须是“能让读者省下至少十分钟的代码阅读时间”否则就该删掉。6. 日常使用中的增量细节生成、嵌入、多端适配最后再说一些零碎但实用的细节。这些内容不见得会被写进官方文档但对日常使用体验影响很大。6.1 渲染成 SVG 还是 PNG能选 SVG 就不要用 PNG。SVG 是矢量格式缩放不糊还能嵌入到 HTML 里做交互——比如给节点加超链接点击直接跳到对应服务的代码仓库。这对架构图来说非常实用读者看到“调用订单服务”这个节点点一下就能进入订单服务的源码目录省去到处翻代码的麻烦。Mermaid 的mermaid-cli可以输出 SVGPlantUML 通过-tsvg参数Graphviz 的-Tsvg更不用说。如果必须用 PNG比如文档平台不支持 SVG要把dpi调高一点至少 150否则放大就糊。6.2 在 GitLab / GitHub / Confluence 里的嵌入姿势每个平台的图表渲染支持度不一样提前摸清脾气能少踩很多坑。GitHub 原生支持 Mermaid 渲染在 Markdown 代码块里写mermaid就能直接显示GitLab 从 13.7 版本开始也支持 Mermaid但部分老版本对 PlantUML 需要额外配置一个 PlantUML ServerConfluence 早期主要靠第三方插件后来官方才有 Lucidchart 集成对于纯文本图表最稳妥的做法是提交渲染好的 PNG/SVG 作为附件。所以我的建议是如果你的主要文档平台对某种格式支持不好不要死磕回到“源文件在 Git渲染产物进文档”的老路这是迁移成本最低的方案。6.3 减少重复劳动用脚本批量生成图一个容易被忽略的点diagram-design 可以和脚本结合实现“批量生成”。举个例子你的服务使用了 Nacos 注册中心那么可以写一段 Python 脚本读取 Nacos 上的服务列表和 API 路径自动生成一张 Mermaid 调用关系图。这样这张图永远不会过期因为它每次都是重新生成的。类似思路还能用在数据库表关系上从 MySQL 的 information_schema 读取外键约束自动生成 ER 图实体关系图。这个操作我曾经在建表文档时使用过效果极佳——数据模型一变更重新跑一遍脚本文档里的 ER 图即时更新。真正让图“活了”的不是某一个绘图工具而是这个“从数据到图”的自动化管线。写在最后一次实战中的选择复盘去年做系统重构评审的时候我把整套服务架构同时用 Mermaid 和 PlantUML 各画了一遍复盘一下过程其实挺有意思。Mermaid 版本用来发 PR 描述和评审邀请大家看一眼就明白整体方向PlantUML 版本作为 ADR 的正式附件用来详细标记各个接口的调用方与被调用方。两套图各司其职反而比追求“一张图打通所有场景”要高效得多。这大概是 diagram-design 里最重要的一条心得先搞清楚图给谁看、用来做什么决策再决定用什么工具和什么详细程度。工具永远只是手段让信息以最合适的方式流动才是真正值得花时间琢磨的事情。
返回列表