ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

把架构图当代码写:代码化图表设计与Mermaid实战

2026/9/8 12:48:37 拓冰建站 浏览量
把架构图当代码写:代码化图表设计与Mermaid实战 很多开发者的diagram-design之旅都是从架构评审前夜对着画布拉扯对齐开始的。我自己也不例外经历过用拖拽工具画完架构图后发现服务已经拆分重构了三轮的尴尬经历过文件保存为架构图_final_v3_最终版_new.png的绝望也经历过新同事指着流程图问这块逻辑线上真的还在跑吗的沉默。后来我彻底转向了代码化图表设计的思路把图当成代码来写、来管、来Review。这篇文章就是这套工作流的完整复盘适合后端架构师、前端开发、技术文档工程师也适合任何需要靠图来说清楚复杂逻辑的人。1. 我为什么从手动画图转向代码化图表设计1.1 拖拽式画图的三个典型痛点先说第一个痛点版本管理。用draw.io这类的工具画图时保存的是一个完整的工程文件如果我在这个文件里调整了布局同事在另一个副本里改了业务内容两个人想合并几乎要重画一遍因为XML里记录的坐标信息冲突起来根本不是人能读的。最后的结果通常是你那边改动小我直接用你的吧——这种话背后往往意味着信息的丢失某些更新过的节点在合并中被悄悄覆盖了。第二个痛点图和代码的同步。代码仓库里每天都有新PR某个模块被拆成了两个独立服务缓存的选型改了消息队列换掉了系统的行为已经和三个月前完全不同。但那张架构图还安静地躺在文档目录里保持着原来的样子。手动画图的问题在于图的更新永远滞后于代码的演进而且没有任何机制会提醒你该更新图了。时间一长图成了摆设大家嘴上说看文档实际上只会跑到代码里自己翻。第三个痛点复用成本高。一个标准的互联网项目架构里有大量相似的地方——网关、认证、业务服务、数据库、缓存、消息队列搭积木一样反复出现。但是拖拽工具里没有模板思维每次画新系统都是重头再来节点之间的间距、连线的颜色、块的大小全凭手感。同样是画订单服务依赖Redis张三画的和李四画的完全是两种风格放在同一个文档里非常跳戏。1.2 从画图到写图diagram-as-code的思路把diagram-design从画布操作变成代码编写之后上面说的几个痛点几乎迎刃而解。图完全由文本描述生成它天然可以像代码一样纳入Git版本管理Review的时候直接看diff就知道是哪个节点改了名字、哪个依赖关系被删掉了可以在脚本里批量生成一批图再统一替换模板还可以把渲染过程塞进CI流水线每次仓库更新后自动重新出图。这个思路的核心是把图当作文档的一部分来管理而不是当一张图片来管理。图片是不能再编辑的成品而源代码格式的图是一个可以持续迭代的资产。你在仓库里维护的不应该是一堆.png文件而是一堆.mmd或者.puml源文件图片只是它们的构建产物。1.3 主流代码化图表工具的横向对比代码化图表设计并不是只有一种选择目前比较主流的方案有Mermaid、PlantUML、Graphviz、D2这几类它们各有各的脾气。这里先给一个粗粒度的对比工具语法难度适合场景生态成熟度主要不足Mermaid很低可读性强流程图、时序图、甘特图、状态图极高GitHub原生支持Markdown生态丰富复杂布局能力弱手动控制位置能力有限PlantUML较低UML全套、时序图、用例图、组件图高插件遍地都是默认渲染风格偏老气定制需要费一番功夫Graphviz中等但语法反直觉复杂关系图、树结构、依赖图高有几十年工程积累布局引擎不可预测想调优需要理解dot语法D2中等现代架构图、云原生拓扑图新但增长很快生态还在成长社区资料相对少Excalidraw无语法门槛手绘风白板草图、快速草图沟通在线协作强非代码化仍然是手动拖拽不算本篇文章的范畴1.4 我最终选择Mermaid作为主力的理由说实话我不是没有在PlantUML和Graphviz之间反复横跳过但最终日常工作里还是以Mermaid为主。原因有三一是语法门檻最低团队成员不需要专门学习一种DSL就能看懂和修改这对协作非常重要二是生态集成最顺本身就能被GitHub直接在Markdown里渲染出来写README、写技术方案、写内部Wiki几乎零成本嵌入三是社区活跃新的图类型层出不穷比如最近官方也在不断完善架构图类型。Graphviz我也没有放弃它被我保留在关系复杂度远高于流程复杂度的场景里这一点后面会展开讲。2. 从零搭建一套可复用的图表设计工作流2.1 环境准备本地编辑器加CLI渲染如果你只是偶尔画一两张图那最简单的方式是打开一个支持Mermaid的在线编辑器或者VS Code插件。我自己的日常是VS Code加官方Mermaid插件好处是边写边看实时预览改一个标签名马上就能在侧边栏看到效果不需要反复跑命令。如果要对整批图做批量导出或者把渲染接入CI那就必须要装命令行工具。Mermaid的官方CLI是mmdc通过npm安装npm install -g mermaid-js/mermaid-cli安装完成之后渲染单张图的方式很直接mmdc -i docs/diagrams/architecture.mmd -o docs/diagrams/architecture.png -w 2048这里面的-w 2048是输出宽度默认值渲染出来的图分辨率偏低直接贴到PPT或者Word里会发虚建议从一开始就养成指定宽度的习惯。2.2 目录结构让图表像代码一样分模块归置很多团队图少的时候还好图一多就开始乱散落在个人电脑桌面、聊天记录和自己博客里。我的建议是在文档项目里专门规划一个diagrams目录按照业务域和图的类型做两级拆分大致是这个样子docs/ diagrams/ order-center/ flow-payment.mmd sequence-refund.mmd er-order.mmd infra/ arch-overview.mmd deployment.mmd这里有两个关键原则。第一个一图一文件不要在一个.mmd文件里堆好几张图。曾经见过一个同事把整本系统的二十多张图全写在了一个文件里每次打开编辑器都会卡顿更别说维护了。第二个命名要遵循业务域-图类型的规律比如flow-payment一眼就能看出是支付流程er-order是订单域的数据模型。定好这个规范三年之后回来找图还能快速定位。2.3 用Mermaid写出第一张系统架构图拿一个典型的网上书店系统架构来举例第一次写把关键节点和依赖关系都摆出来graph TD A[用户端 Web/App] -- B[API 网关] B -- C[鉴权服务] B -- D[图书服务] B -- E[订单服务] E -- F[(PostgreSQL)] E -- G[(Redis)] D -- H[(搜索索引)] C -- I[(用户库)]这段语法其实不需要额外解释就能看懂大半graph TD表示这是从上到下布局的流程图--表示节点间有连线。方括号是普通节点圆角方括号加()是数据库存储节点。我建议初学者先从这种最朴素的写法入手不要一上来就堆各种花哨的样式。这张图表达了什么一句话用户端流量经过API网关统一接入网关后面挂着三个核心业务服务每个服务的数据状态落在各自的存储组件里。对一份架构文档来说这张图已经有了基本的可读性。但能读和好用之间还有不小的距离视觉上的优化细节我在第4章专门讲。2.4 接入Git和CI让图保持最新代码化图表设计最大的红利就是图表可以和代码同一个仓库、同一套CI流程。我个人的做法是在GitHub Actions里加一个步骤每当main分支上有.mmd文件的变更提交就自动执行渲染命令把生成好的PNG输出到文档站点对应的静态资源目录。一个简化版的CI配置长这样- name: Render Mermaid diagrams run: npx mmdc -i docs/diagrams/ -o docs/static/diagrams/这样做的意义在于文档站点里展示的图片永远是构建产物不依赖任何开发者的本地环境。如果有人改了.mmd源文件但忘记手动导出图片CI会帮他兜底。曾经见过很多图源文件和展示图片对不上的翻车现场一旦启用这个流程这个问题从根本上就不存在了。如果团队短期内连CI都排不上退而求其次的方案是在文档站里直接引用.mmd源码配合前端的渲染库在浏览器端实时渲染。效果几乎一致只是性能上会稍微打点折扣。无论哪种方式核心都是保证读者看到的图和仓库里维护的源文件之间形成强绑定而不是靠某个人手动上传图片。2.5 多人协作的具体操作节奏当图进入代码仓库之后协作方式就彻底向代码看齐了提交变更请求做Review看Diff合并。Mermaid的源码是纯文本Diff是逐行级的哪一行改了什么一目了然Reviewer可以很清楚地判断这次改动是增加了依赖关系还是改了措辞。这一点和以前两个人打开同一个工程文件互相覆盖的体验相比完全是两个时代的东西。另外操作节奏上也建议遵循一个小规范修改图之后必须在同一次提交里更新相关的文字说明。比如架构文档里有一句订单服务依赖Redis做缓存如果你在架构图里把Redis换成了Memcached这段文字也要同步改掉否则图和文字又会出现新的不一致。这种一致性检查在Review阶段应该作为硬性要求提出来。3. 先理清关系再动手画diagram-design的信息架构3.1 从一段需求描述中提取实体与关系画图最大的忌讳是边画边想。我自己早期就是这样打开编辑器画了一个框然后想起一句需求加一个框完全凭直觉堆叠画到一半发现有个重要的实体没位置放整张图变成了一团彩色的毛线。正确的顺序应该是先做信息提取再动手。拿一段非常典型的需求描述来演练用户可以在网页上浏览图书加入购物车后下单支付支付成功后系统扣减库存同时给用户发送确认邮件。这段话里能提取的信息其实很清晰实体用户、图书、购物车、订单、支付记录、库存、邮件通知动作浏览、加入、下单、支付、扣减、发送依赖顺序浏览 - 加入购物车 - 下单 - 支付 - 扣库存 - 发邮件当你把这些列表在纸上列出来再回到画图界面其实已经不需要构思了只需要把列表里的关系翻译成节点和箭头就行。这个流程说起来不值一提但它是diagram-design里最基础也最容易被忽略的一步。很多看似高深的图表拆开来看就是一组实体和关系的排列组合。3.2 图类型的选择先选对再考虑画好流程描述用什么图系统模块关系用什么图数据结构用什么图——很多人的图之所以让人看不懂不是因为画得丑而是因为从一开始就用错了图类型。你想表达的内容推荐图类型对应Mermaid语法业务处理步骤、判断分支流程图flowchart跨系统/对象之间的交互顺序时序图sequenceDiagram系统模块间的调用关系架构图/组件图flowchart或architecture概念归属、分类层级思维导图/树图flowchart LR的树形排布数据库表之间的关系ER图erDiagram对象的状态流转状态图stateDiagram用错图类型最典型的后果用流程图去画系统架构结果画出来全是条件分支和决策菱形模块之间的层次关系完全看不出来反过来用架构图画业务流程每个步骤变成了独立的方框读者搞不清先后顺序主流程被淹没在一堆组件连线里。3.3 分层设计从概览、系统级到模块级一份复杂系统的diagram-design永远不应该追求一张图讲完所有事情。合理的做法是一组图按照三层粒度来组织L0总览图一张图说清楚整个系统有哪些外部边界、哪些入口、哪些核心子系统。通常不超过十个节点目的是让读者在一分钟内建立全局认知。L1系统级图聚焦某个子系统内部的组件划分、交互方式和数据依赖比如订单中心的架构图。L2模块级图下钻到具体的功能模块画的是某个模块的流程图、状态机或关键算法逻辑比如退款状态流转图。这三层图的粒度层层递进每一层的信息量控制在读者的认知负荷之内。没有经验的人通常会犯两个错误要么宏观图里塞满了微观细节要么微观图只画了宏观轮廓两头都不讨好。3.4 图与图之间的引用管理既然已经是一组图了图和图之间就要有能被追踪的引用关系。我见过一个很务实的做法在每张图的顶部注释区维护一个版本块标注创建时间、维护人和关联的上级图。Mermaid支持注释语法%% 关联上级: docs/diagrams/infra/arch-overview.mmd %% 维护人: zhangwei %% 最近更新: 2025-01-12这个习惯在刚养成时确实显得多余但坚持半年以后价值非常大。比如某个业务模块悄然下线了你可以顺着这个引用关系找到所有相关的图逐一检查哪些节点需要删除不会出现主图里已经删了入口、详图里还挂着模块的脱节场景。4. 一张好图的质感藏在这些视觉细节里4.1 布局方向让眼睛顺着一条线走除了纯数据结构类的ER图大多数图的阅读顺序应该是一条主线。Mermaid里graph TD上下布局和graph LR左右布局是最常被用到的两种方向具体选择标准其实很朴素主体流程从上往下读顺眼还是从左往右读顺眼。对于业务流程类图我几乎总是选择垂直布局TD因为从上到下是多数人习惯的阅读方向配合决策分支放在右侧的约定图的叙事感会比较清晰。对于系统架构类图我更喜欢水平布局LR因为可以用它模拟用户入口在左侧、数据存储在右侧的物理分布感视觉上贴近一张拓扑图。4.2 用subgraph管理视觉分组当图里的节点数量超过八个就要有意识地做视觉分组了。Mermaid的subgraph是天然的分组容器它会在渲染时给一组节点套上一个带标题的方框效果非常直观graph LR subgraph 接入层 A[Web 端] -- B[API 网关] end subgraph 业务层 C[订单服务] D[库存服务] end subgraph 存储层 E[(MySQL)] F[(Redis)] end B -- C B -- D C -- E D -- F这个手法解决的是节点太多、线条交叉成蜘蛛网的问题。带标题的分组容器会在视觉上形成潜意识的分区读者一眼就能分出层次不用费力去逐个读标签。分组容器还能嵌套使用比如存储层里再分主存储和缓存清晰度会更高。4.3 颜色语义建立一份自己的配色规范颜色不能随手给一定要有语义。我自己的图库里长期维护着一套简单的配色规范蓝色系外部交互入口、用户端绿色系核心业务服务橙色系外部依赖、第三方服务紫色系数据存储灰色预留、暂停、废弃模块颜色的具体色值可以根据团队审美调整关键是表达的意思要长期稳定。坚持一段时间后当你快速翻过十几张图不需要仔细读标签光凭颜色就能感知每个模块承担的角色这是提升图表信息传达效率非常实用的一招。Mermaid里通过classDef可以轻松给特定节点绑定颜色配合:::className使用即可。4.4 简化策略一张图最多保留20个元素这是我从大量实践中提炼出的硬指标一张图里的节点和连线加起来超过20个信息传递效率会断崖式下降。读者会在密集的线条里迷路找不到哪里是入口、哪里是出口。超过这个阈值不要试图添加更多细节而是拆分。简化有几层具体手法。一是封口把细节折叠进一个更高层的节点比如订单流程里的六个内部步骤可以折叠成一个订单处理节点展开的部分放到下一层图里。二是删边去掉含义模糊的辅助连线只保留对理解核心逻辑最重要的主路径。三是维度分离业务逻辑复杂就拆成流程时序图和状态机图两张不要硬揉在一张图里。4.5 字体、间距与图例管理字体方面图表里的默认字体在中文环境下渲染效果不稳定Mermaid的配置项里可以指定字体族比如配置成PingFang SC, Microsoft YaHei, sans-serif渲染出来的中文会舒服很多。间距问题方面Mermaid没有非常精细的像素级控制能力但可以通过在节点文字里加空格、或者用classDef调整具体样式来缓解。还有一个常被忽略的细节是图例。当图里同时出现实线、虚线既有圆角节点又有直角节点有彩色有灰白时图例不是可选项而是必需项。你可以用一个注释或者额外的节点把图例画出来总之要让读者能明确知道每种视觉元素代表什么否则再漂亮的图也是一堆带颜色的密码。5. 踩坑实录我在diagram-design实践中遇到的真问题5.1 中文乱码和豆腐块字体问题第一个绕不开的坑是中文。Mermaid CLI在部分Linux环境下渲染含中文的图会乱码或者出现方块字。根本原因通常是渲染器所在的系统环境里没有安装中文字体puppeteer渲染时找不到字体只能退化成默认字体。解决办法分两层。最直接的是在运行环境里安装一个开源中文字体Debian系系统的命令大致是apt-get install -y fonts-noto-cjk安装了字体还不够如果你用的是mmdc命令可能还需要在puppeteer的配置里让渲染进程感知到这个字体否则装了也白装。这个坑最烦人的地方是本地开发机上永远复现不出来一上CI就开始出问题所以我在搭建CI任务时会把安装中文字体写进第一步省得每次构建都要花半小时排查字体问题。5.2 大图的性能和内存消耗Mermaid对上千个节点的大图支持度是有限的超过一定规模后渲染时间明显变长内存占用也飙升。有一次我把整个微服务拓扑塞进一张图里结果在Markdown预览里直接白屏了好几秒控制台报了一堆超时的警告当时第一反应是代码写错了后来才意识到是方案的边界问题。这里有两个可行的方向。一个是换引擎关系数据量特别大的图用Graphviz来渲染它基于成熟的布局算法处理大规模节点比Mermaid稳定得多。另一个是拆分把大图按边界切成多张中等级别的图在文档里通过链接串联起来。前者解决的是性能极限后者解决的是可读性极限二选一或者组合使用都可以。5.3 布局引擎不完全可控Mermaid的自动布局大多数时候是够用但不完美而且它没有公开的手动定位接口。我一度想精确微调某几个节点的位置折腾了半天发现能用的手段无非是用direction调整整体方向、用subgraph做分组约束或者想办法在节点标签里加空格来间接影响布局非常别扭。如果你对图上节点的坐标有非常精确的要求比如给客户看的数据中心部署拓扑图我的建议是开工前就想清楚到底是走自动布局还是手动微调的路线。如果确定手动微调就不要用Mermaid了直接上Graphviz或者Excalidraw这类工具。画到一半发现工具不满足需求再迁移才是成本最高的事情。5.4 跨平台渲染不完全一致同一份.mmd文件在VS Code插件里预览、在CI的mmdc里渲染、在GitHub网页端自动渲染三种环境出来的图偶尔会有细微差异。这主要是不同环境里的Mermaid版本和配置不一致导致的。比如GitHub对Mermaid语法的支持版本可能滞后于官方最新版新语法在本地预览正常运行推到远程仓库后直接报语法错误。我的建议是在项目里锁定一个明确的版本约定。如果文档托管在GitHub上写图的时候要先确认GitHub当前支持的Mermaid版本主动避开那些新语法特性。另一个思路是写一个统一的配置文件把theme、fontFamily这些选项固定下来避免各环境各自为政。5.5 导出到文档和PPT的清晰度问题最后是导出质量问题。踩过最典型的一次坑精心调好的一张时序图放到PPT里投到大屏上整张图变花根本看不清文字。排查到最后发现是渲染导出时使用的默认分辨率太低投影仪一拉伸就全糊了。解决方法很简单渲染时显式指定宽度和缩放倍数mmdc -i sequence.mmd -o sequence.png -w 2048 -s 2-w 2048指定输出宽度-s 2表示两倍缩放。如果是要打印出来我建议直接导出SVG矢量格式清晰度无上限。需要说明的是某些旧版PPT对SVG的支持不理想插入前最好确认版本或者改为先导PDF再插页面。画了这么多年图我最深的体会是diagram-design的价值不在于画得漂亮而在于画完能养得活。这里的养得活有三层意思图里的信息和真实系统保持同步图源文件在几年后仍然可以被维护更新新同事拿到手之后不需要过多解释就能看懂。如果你也想转向代码化图表设计不需要一开始就备齐所有工具找一个正在进行的项目用Mermaid把核心架构画出来放进docs目录走一次评审合并流程。等这套循环跑顺了再慢慢加入CI自动渲染、图库沉淀、模板复用这些进阶能力。工具更新迭代的速度很快语法也一直在演进但这套把图当代码来养的思路大概率不会过时。