1. 项目概述与核心思路1.1 这个diagram-design想解决什么问题工作里经常有这样一个场景一张好不容易画好的架构图、流程图或时序图过了一个月再打开原始文件发现里面的逻辑已经改了三四轮图里却还是旧版本。要么是画图的人懒得更新要么是原始工程文件早就不知道丢在哪个共享盘角落。更头疼的是一张图往往只有一个人能改其他人想提个意见只能截图加红圈最后图越来越乱。我这次的diagram-design项目就是想用一套“把图表当成代码来管理”的方式把这些麻烦一次性解决掉。diagram-design本质上是一套图表设计规范加配套的工程化实践先选定一种文本化图表语法作为统一底层格式再把所有图表文件纳入版本管理工具配合自动渲染、自动校验和在线预览让图表像代码一样有版本、有历史、可评审、可协作。它并不神秘核心就一句话你不再用鼠标拖拽画图而是用几行结构化的文本描述出图的节点、连线、分组和样式然后再由一个渲染引擎把它变成你想要的图片或网页。我最早尝试这套思路是因为团队里的架构文档实在维护不下去了。每季度评审时大家拿着旧图讨论新方案产生了不少误解。后来我花了一个周末把所有核心图表改成文本化描述用上自动渲染之后效果立竿见影图改了哪里、谁改的、为什么改全部清清楚楚。这个项目适合谁呢比如需要长期维护接口调用关系图的后端开发负责组织级技术方案沉淀的架构师喜欢把流程规范文档化的技术管理者以及所有被“画图两小时、改图一整天”折磨过的人。哪怕你只是个人写笔记这套思路也一样能省下大量重复劳动。1.2 为什么选择代码化图表这条路线很多人一听到“代码画图”就下意识觉得麻烦。但我要先掰扯一下这里面的关键问题传统拖拽画图工具真的更好用吗短平快的场景下比如临时画个示意图给同事讲思路鼠标拖拽确实效率很高。可一旦图进入了“需要长期维护”的生命周期传统工具的两个致命弱点就暴露了。第一是版本追溯困难。大部分桌面画图工具的工程文件是私有的二进制格式存进Git之后哪怕只挪动了一个方块diff也是一堆没有意义的乱码。你根本没法在代码评审里看出这次改动到底动了什么逻辑。第二是协作门槛高。传统工具基本是“谁的文件谁说了算”其他人要么等文件传来传去要么只能截图提意见。就算用了在线协作画布多人同时改动时也常常出现位置被乱拖、连线被弄断之类的混乱。而代码化图表把“图的本质”抽离成纯文本的逻辑结构节点是什么从哪里到哪里在哪个分组里。这些信息本身就是人类可读的。于是Git能逐行比较、能标注评论、能实现多个分支并行修改协作方式一下子回到了程序员最熟悉的节奏。我还看中一点文本化让“图的生成”可以自动化。你的架构图可以直接从Kubernetes资源清单生成调用链路图可以根据接口定义文件自动绘制网络拓扑图可以扫一圈云厂商的API自动拼出来。这基本是传统画图工具不可能实现的能力但对diagram-design来说只是水到渠成。2. 工具选型与基础规范2.1 主流图表引擎选型对比选工具是diagram-design项目的第一步也是决定后续体验的关键一步。我试过市面上主流的几种文本化图表方案各有优劣这里直接给出一份我实测后的对比省得你再踩一遍坑。工具适用场景语法难度渲染质量协作能力我的评价Mermaid文档内嵌图、流程图时序图甘特图低中上极好GitHub原生支持首选适合90%的场景PlantUMLUML专项类图、时序图、用例图中低中较好传统UML场景依然能打但更新偏慢Graphviz / DOT复杂拓扑、自动布局、大规模节点图中高中一般布局算法最强大但语法比较反直觉D2强调可读性的现代图表低高较好新起之秀布局比Mermaid干净diagrams.net开发模式需要精确像素级控制低高一般可以存成XML文本但diff不友好我自己最终主要用Mermaid因为它贴合Markdown生态GitHub、GitLab、很多笔记软件都原生渲染零额外成本。但如果你的项目里需要大量类图PlantUML在某些细节上仍然更规范。如果图非常复杂、节点之间有大量交叉连线Graphviz的自动布局算法往往能救你一条命。这里不多评价D2它确实漂亮但生态还不够成熟团队推广成本偏高。我给一个选型心法先看“图的受众在哪里”。如果图最终要放在GitHub或企业Wiki上Mermaid几乎没对手如果图主要放进技术文档的PDF里PlantUML对UML的标准支持会少很多麻烦如果图是自动化生成的中间产物Graphviz的DOT格式反而更适合被程序拼装。2.2 一套能长期用的目录与命名规范很多教程会让你直接开始写语法但把diagram-design落到团队里真正决定成败的是规范。没有规范三个月后就是一堆graph-1.md、graph-v2.md到处乱扔跟之前用Word存的图也没有本质区别。我建议项目目录按“领域/图名/版本”三层来组织。领域对应图所属的业务模块或系统边界图名只保留“要表达的内容”版本则完全交给Git去管理不需要在文件名里体现。比如我的一个实际项目里目录长这样diagrams/ ├── user-service/ │ ├── login-flow.md │ ├── order-state.md │ └── internal-api.md ├── payment-service/ │ ├── transaction-flow.md │ └── settlement-state.md └── infra/ ├── network-topology.md └── deployment-architecture.md文件名统一小写英文加中划线关键词能一眼看懂。每个文件头部加一个简短的元信息块写清楚这张图的作者、维护人、对应代码仓库地址、以及最近一次更新时间。更新时间这里要解释一下为什么重要diagram-design的好处之一是能自动渲染但渲染不会告诉你图是否过期。我在每个文件顶部固定维护一行last-updated一旦逻辑变了就顺手改掉配合CI检查能提醒团队里的每个人。命名这块还有一个容易踩的坑不要用“最终版”这类形容词做文件名。Git的意义就在于“没有最终版只有历史版本”。如果你发现团队里每个人都在文件名后面加v3、v4说明还没建立代码化图表的习惯先把这条立起来效果立竿见影。3. 实操全流程用Mermaid画一张系统架构图3.1 第一步把图画拆成需求别急着打开编辑器写语法。拿到任何一张图表需求我习惯先花几分钟做一次“图的拆解”明确三个问题主体对象是什么、主体之间是什么关系、围绕主体有哪些附属信息。以项目里常见的“订单服务架构图”为例。主体对象是订单服务本身、它依赖的数据库、缓存、消息队列以及上游的调用方。主体之间的关系是调用、读写、订阅和发布。附属信息包括协议类型、端口、数据流向、故障隔离方式等等。把这些写在纸上比直接打开编辑器边想边画高效得多。我通常用一个简单的表格来约束拆解结果对象类型关系备注前端订单页上游调用方通过HTTPS调用订单服务网关统一鉴权订单服务核心服务接收并处理下单请求无状态多副本部署MySQL订单库数据库服务通过JDBC读写主从架构读写分离Redis缓存缓存服务通过Redis客户端操作缓存订单状态消息队列异步通道服务发布订单事件下游支付、积分监听这个表格画完图的基本信息就已经齐了。Mermaid里的每个节点都来自“对象”列每条连线都来自“关系”列分组方式要么按系统边界、要么按部署环境。值得强调的是先拆对象再画图能让你的图天然具备清晰的分层结构而不是一团乱麻。很多人画图难看的根因不是语法不懂而是脑子里根本没想清楚图里应该出现哪些东西。3.2 第二步用代码把草图画出来拆解完成后就能进入实际的diagram-design编码环节。我在Mermaid里最常用的是flowchart因为它表达能力最强、直觉也最好。一张订单服务架构图的初始版本长这样flowchart LR A[前端订单页] --|HTTPS下单| B[订单服务] B -- C[(MySQL订单库)] B -- D[(Redis缓存)] B --|发布订单事件| E[消息队列] E -- F[支付服务] E -- G[积分服务]这五行已经建立起基本的图结构从左到右前端进来订单服务落库、更新缓存、发布事件消费方继续往下走。接下来需要做的是分组合格式化。真实系统里通常有多个服务、多个中间件如果不分组图会横向拉得极长。Mermaid的subgraph是这里的主角flowchart TB subgraph client[客户端层] A[前端订单页] end subgraph app[应用服务层] B[订单服务] end subgraph infra[基础设施层] C[(MySQL订单库)] D[(Redis缓存)] E[消息队列] end subgraph downstream[下游消费方] F[支付服务] G[积分服务] end A --|HTTPS下单| B B -- C B -- D B --|发布订单事件| E E -- F E -- G不要小看这个简单的分组它直接定义了图的视觉语言横向上分出了清晰的层次纵向上每个层次内部形成聚合。读者第一眼就能理解“订单服务处在中间位置上游是前端下游是基础设施和消费方”。我给这个项目定的一条铁律是一旦节点数超过六个就必须用subgraph分组否则可读性会断崖式下降。Mermaid对节点形状也有一些约定俗称的经验。方括号表示模块圆括号表示内部函数或接口花括号表示判断双括号表示数据库。我自己的规范是服务用方括号中间件用数据库符号外部依赖用圆角矩形。这样不用看图例读者也能凭形状快速识别节点类型。写到这里还有一个细节连线上的标签一定要写“动作”或者“协议”不要写“依赖”这种没有信息量的词。A --|依赖| B这样的图等于白画改成A --|HTTPS调用| B之后信息量立刻提升。这是我在这类项目里抓review时最常提的一条意见。3.3 第三步评审、合并与版本管理diagram-design项目的核心收益在评审环节体现得最明显。以前大家看架构图只能在会议上投屏讨论。现在有了文本化描述所有人可以像看代码一样逐行提意见。我在项目里规定任何图表文件的改动必须走分支合并流程流程非常简单新建分支、修改图文件、推送远程、发起Pull Request。评审时重点看三块。第一逻辑是否与技术方案一致。比如订单服务是否少画了一个依赖方或者消息队列的订阅关系有没有画反。第二命名和分组是否符合规范。第三有没有不必要的复杂度。这里有个常见的坏味道为了“让图看起来很完整”把很多无关节点塞进来。我的原则是一张图只回答一个问题多余的信息全部拆出去。如果评审中有人问“这个服务为什么出现在这里”八成就是多余节点。合并之后就需要设置自动渲染。最轻量的做法是直接在GitHub上依赖它原生的Mermaid渲染能力.md文件打开就能看图。如果想要更定制化的效果可以用脚本把Mermaid渲染成SVG或PNG再作为构建产物输出到文档站点。我在项目里用的是后者因为团队文档站里经常需要引用图片渲染成SVG之后可以高清嵌进任何页面。版本管理的细节上有一条我一直坚持图表文件必须和对应的代码放同一个仓库别单独建一个“文档仓库”。理由也很实际图表是为了描述系统的某一部分静态结构它应该紧随代码变动。代码改了同一个Pull Request里就该有对应的图改动这样才能保证图和实现永远同步。如果放在独立仓库哪怕有自动化同步也很容易产生几个合并请求之间的时间差图照样会过期。4. 进阶玩法与稳定性保障4.1 大图拆分的三种姿势把diagram-design真正用起来后你迟早会碰到一个瓶颈某张图越来越庞大几百个节点堆在一屏上渲染出来后密密麻麻像电路板谁也看不懂。我的原则是一张图超过十五个节点就属于“危险信号”超过二十个节点基本一定需要拆分。拆分不是随意的通常有三种姿势。第一种是按边界拆分。比如“整体架构图”太庞大了就拆成“下单链路图”“结算链路图”“对账链路图”每条链路只画跟自己相关的部分公共组件用注释符号或者虚线框简单示意不重复展开。第二种是按层次拆分。先画一张一级分层图把应用、中间件、外部系统画清楚然后在另一个文件里单独展开“应用服务内部的结构图”。一级图是地图二级图是街道用标签互相引用在文档里放上链接跳转。这种方式对新人理解系统最友好。第三种是按交互流程拆分。针对那些状态特别多的对象画“状态图”而不是把所有流程塞在一张图里。比如订单的状态机可以画一个只有状态和迁移条件的图把每个迁移条件里的调用细节拆到另一张“下单时序图”里。我之前遇到过一个最极端的案例团队里有一张800多个节点的网络拓扑图是从配置自动生成的。这种情况下任何人工拆分都不现实只能依赖第二种方式里讲的“按层次”先按机房聚合每个机房内部再按交换机层级递归聚合最终每一层图片控制在几十个节点以内。4.2 从结构化数据自动生成图表diagram-design最大的想象空间在于图表可以不再是“画”出来的而是“算”出来的。我在这套项目里做的一个实践是从数据库表结构自动逆向生成ER图。以前维护一份数据库关系图非常痛苦表一多、字段一变图就要手动改半天。现在只需要在CI里跑一个脚本读取数据库当前的信息架构拼装成Mermaid语法然后提交到渲染流程。这种“数据驱动图表”的思路有几个典型的落地方向。第一个是接口调用关系图扫描微服务代码里所有REST调用生成服务间的依赖图。第二个是Kubernetes资源拓扑图读取集群里的Deployment、Service、Ingress对象自动生成部署结构图。第三个是消息流图扫描代码里所有发布和订阅的Topic画出事件流转关系。脚本的核心并不复杂无非是“把结构化数据映射成节点和连线”。但有几个细节值得提一下。生成结果必须强制排序否则节点顺序每次都不一样Git diff会充满噪音。给每个节点加稳定IDID要基于对象的唯一标识不要用自增序号。生成频率不要太高否则图会像监控大屏一样不停变化反而无法作为稳定文档沉淀。我还试过用前后两次生成的图文件做diff让代码评审只看到实际变化。这个体验相当不错服务A增加了一个对服务B的调用Pull Request里就只有一条新增连线。相比传统的架构评审这种“有依据的图”可信度高了不止一个档次。4.3 文档流水线里让图表永远不过期图表过期是几乎所有技术文档系统的通病。diagram-design给这个问题提供了一个还不错的解法把图表生成放到文档流水线里让它与代码构建同步进行。我在项目中配置了一套简单的CI流程代码push后构建脚本先跑一遍测试和打包然后再跑一次图表渲染流程。渲染流程分两步先检查所有.md图表文件是否能语法解析通过不通过就直接构建失败再执行生成脚本把需要导出的图渲染成SVG和PNG复制到文档站点目录。这套流程跑通之后文档里出现“这张图已过期”的概率会大大降低。因为你的代码合并且构建成功的同时图一定已经被重新渲染了。还有一条更严格的实践在图文件里嵌入一个“标签”记录它关联的源代码目录哈希。CI脚本会比较当前代码哈希与图里记录的哈希不一致时打印警告。这个功能相当于给图表上了“保鲜期”再也没人敢拿三个月前的架构图去汇报了。当然任何自动化都不是银弹。数据驱动生成的图永远只能描述“现状”画不出“目标架构”。真正需要体现规划和演进的图依然要依靠人来画。所以我的建议是把“描述现状”的图全部自动化把“描述未来”的图交给人工两者各自发挥优势互不干扰。5. 常见问题与排查技巧实录5.1 语法报错排查Mermaid的语法看似简单但实际跑起来还是会遇到不少玄学报错。我整理了几个高频问题和对应的排查思路。第一类是“语法解析直接失败页面只显示错误提示”。这类问题绝大多数出在节点文本里的特殊字符上。比如节点标签里写了一个括号()Mermaid会把括号理解成语法结构导致解析器蒙圈。解决方案是给标签文本加引号比如A[订单服务(核心)]。还有带斜杠的文本比如路径/api/v1有时候不报错但渲染结果异常同样建议用引号包住。第二类是GBK字符集导致的中文乱码。这个在国内项目里尤其常见。检查你的文件编码是不是UTF-8几乎所有现代编辑工具默认就是UTF-8但如果文件是从Windows老版本复制过来的可能带着BOM头某些渲染器会对BOM处理不稳。我遇过一次比较刁钻的场景在Windows上用记事本编辑了图表文件提交到Linux的CI上构建渲染出来第一行多了一个奇怪的字符。排查了很久才发现是BOM后来给渲染脚本里加了一步去BOM的处理问题彻底消失。第三类是节点ID重复导致的连线错乱。Mermaid里节点ID是唯一标识符同名ID会被合并成同一个节点。如果你复制了一段代码忘了改ID两个逻辑上不同的节点会被画成同一个。这个最容易在“增加一个新节点”时发生我的习惯是给每个节点ID加上有语义的前缀比如srv_、db_、mq_从源头上降低重名的概率。5.2 中文字体和样式问题diagram-design渲染出来的中文在默认情况下往往不太美观。西文字体里中文字形适配不好会显得发虚或者大小不统一。Mermaid本身不直接提供字体配置但可以把配置项写进主题里。我在项目里是把字体设置为系统中文字体的标准序列渲染到SVG之后在网页上显示正常导出PNG时需要确保服务器上安装了对应的中文字体包。还有一个细节是“行高”和“字符宽度”。中文字符的全角宽度会比英文字符宽如果节点边框是固定宽度中文多一点就把形状撑破或者文字溢出。解决方法通常是在设计时给中文字符预留足够的空间或者通过配置把节点文本的wrap打开允许自动换行。这里的经验值是8个中文字符建议设计宽度对应16个英文字符的尺寸按这个比例预留就不会出大问题。如果你要导出高清PNG建议把渲染尺寸定大一些比如scale: 3然后再压缩。这样可以避免图片在文档里放大后出现锯齿。我曾经为了图省事直接按1倍导出结果PPT投屏时图边缘全是毛刺后来统一改成3倍导出视觉质量明显提升。5.3 布局与可读性优化很多新人在diagram-design项目里画的图逻辑完全正确但看起来就是“不舒服”。问题往往出在布局上。第一个常见问题是一张图里方向混乱。flowchart要么统一LR要么统一TB如果一会儿从左到右、一会儿从上到下读者的视线就来回跳非常累。除非图有天然的层次结构否则我建议全图只使用一种主方向。第二个问题是连线交叉过于密集。交叉最多的地方往往是两个分组之间“多对多”的关系。比如三个服务都操作同一个数据库连线的交叉几乎不可避免。解决办法是引入一个中间节点比如“数据访问层”让服务先连到数据访问层再连数据库。连线数量没有变少但视觉上交叉大幅减少可读性明显提升。第三个问题是子图内部和外部的连线混在一起。Mermaid在给节点归类时有时候会把连线的起点或终点错误地附着在subgraph边界上导致出现了很多指向整个分组的连线。排查方法很简单把连线的起点和终点都明确写成具体节点ID不要用分组ID充当连线端点绝大多数情况都能解决。最后再分享一个我在实际操作里的习惯每次合并图表改动之前先打开渲染后的SVG用“缩小到25%”看一眼整体效果。如果缩小后看不清任何一条业务链路说明这张图还有优化的空间必须拆或改直到它“缩小也能看懂”。这个简单的自我检查帮我挡住了很多次低质量图表的合入。