
diagram-design为什么我把画图这件事彻底交给了代码先说个现象。你去看那些架构清晰、文档漂亮的开源项目几乎都有一个共同点图是写出来的而不是画出来的。这就引出了 diagram-design 这个方向的核心理念——把图表变成纯文本描述再由工具自动渲染成可视化图形。我第一次接触这个思路时其实挺抗拒的心想画图还得先写代码这不是绕远路吗直到我被那个经典问题折磨到崩溃类图画到第七版客户说逻辑改了架构图刚拖好连线同事说服务器加了一台。用鼠标拖出来的图好看是好看了但改一次伤筋动骨。后来痛下决心把整个项目的图表全部迁到 diagram-design 的方案上实测下来迁移成本远没有想象中高收益却超出了预期。这篇内容就把我做的技术选型、完整实操方案、踩过的坑和排查思路一次讲透。1. 解构 diagram-design 的核心思路与设计逻辑1.1 为什么用代码画图从一次性交付到持续演进的转变diagram-design 的第一层价值是把图表从成果物变成了可维护的资产。传统拖拽式绘图工具图的本质是二进制文件或专有格式人和人之间协作靠传文件版本管理靠另存为V2结合进 Git 做差异对比几乎不可能。但用文本语法描述图表图就和代码一样拥有了生命可以 diff、可以 review、可以多人并行改。第二层价值是渲染一致性。拖拽工具里十个人画同一个流程图可以出现十种视觉风格有人喜欢圆角矩形有人坚持直角有人连线带箭头有人不带。而 diagram-design 类的方案样式规则集中定义渲染结果由引擎统一输出团队看到的就是同一套风格模板品味的一致性有了兜底。第三层价值是产能的杠杆效应。写文字描述比拖动鼠标快多少我实测的数据是一张中等复杂的 ER 图拖拽画大概 40 分钟用文本写语法只需要 10 分钟左右后期修改更是从重画变成改改文字。一旦图的数量上了规模效率差距会拉得非常夸张。提示diagram-design 不是要替代所有绘图场景。自由排版的视觉稿、情绪板、手绘草图仍然是传统工具的主场。它最适合的是结构化内容频繁迭代的场景比如架构图、ER 图、时序图、流程图。1.2 一个完整的 diagram-design 系统需要具备哪些能力做技术选型前我心里先列了一张愿望清单把理想状态下的能力边界画出来再逐个对照很管用。建议你自己做选型时也这么来一遍。第一优先级是语法的表达力。流程图的分支、循环、并行时序图的嵌套消息、异步回调ER 图的多对多关系、弱实体类图的继承、实现、聚合、组合这些基础建模能力必须覆盖。语法表达力不足图就会画得别扭最后又回到手动拖拽的老路。第二优先级是渲染引擎的输出质量。图渲染出来是用在文档、PPT、网页还是源码注释里不同场景要的格式不一样PNG 用于静态文档SVG 用于网页缩放PlantUML 还能直接输出 ASCII 艺术图做嵌入式注释。看了一下我项目里实际用到的输出格式PNG 和 SVG 占了七成以上。第三优先级是生态和集成能力。命令行是否支持批量导出有没有 CI 集成方案能不能通过脚本批量修改图的内容这些能力决定了一个 diagram-design 方案能不能真正融进已有的工程链路而不是团队里某个人的私人玩具。第四优先级是中文支持。这点太重要了外来的工具对东亚字符的渲染支持参差不齐有的默认字符集根本渲染不出中文需要一堆环境变量配置。凡是涉及到团队内部文档和知识库中文渲染一定是刚需选型时要用真实内容压测千万别只看英文示例图好看。2. 核心工具选型解析PlantUML、Mermaid 与 Graphviz 的取舍2.1 三大主流方案的能力版图与选择逻辑diagram-design 的生态里最常用的三套方案是 PlantUML、Mermaid 和 Graphviz。它们不是互相替代的关系而是定位不同、各有擅长的工具集。做选型时我关注的其实是它们在团队里的角色分工谁负责架构图谁负责流程文档谁负责复杂布局计算。先看 PlantUML。它的强项是对 UML 语义的顶层封装语法最接近自然语言用startuml和enduml包裹内容类图、时序图、用例图、状态图、活动图、组件图、部署图覆盖最全面。它对 UML 的铁杆粉最友好因为你能用一行的class User {}声明一个类用一句User - Order : create表达协作关系。我最早用它画类图和时序图迁移的几个系统架构图用的都是这套语法。它的建图原理是定义一种形式化的图描述语言然后通过解析器转换为中间表示再经图形引擎内置 Graphviz 或 Smetana自动布局。优点是可读性好缺点是对非 UML 的自由图比如仪表盘、信息图支持弱布局输出不如直接调 Graphviz 那么可精细控制。再看 Mermaid。它诞生于 Web 时代设计目标就是嵌入 Markdown 文档快速渲染图表。流程图、时序图、甘特图、饼图、状态图、类图都能画语法比 PlantUML 更紧凑。典型的用法是在 Markdown 里写一个代码块标注mermaid随后由插件渲染成图。实现上Mermaid 基于 JavaScript 解析语法并直接操作浏览器 DOM 的 SVG所以它对 Web 平台的亲和力无与伦比。像我如果要在个人博客或者团队 Wiki 里展示流程基本首选 Mermaid。它的局限也很明显复杂系统架构图的布局优化不如 Graphviz 硬核如果正文段落特别长、节点特别多浏览器端渲染会出现性能瓶颈。最后看 Graphviz。严格来说它是布局引擎而非图描述语言用 Dot 语言描述图结构由引擎自动计算节点坐标和边走向。它的核心竞争力就是布局算法dot适合有向图层次布局neato适合无向图力导向布局fdp适合大规模无向图circo适合环形布局twopi适合放射布局。我把它当作底层的布局计算器当 PlantUML 或 Mermaid 预设的布局不够精细、或者节点规模很大时就会直接写 Dot 来精确控制。但缺点也很明显Dot 语言对图的表达抽象程度低改起来工作量偏大。它的核心实现思路是把图的美学标准约束少的交叉、均匀的边长度、对齐的节点层建模成数值优化问题然后用迭代算法计算坐标。这三者的关系用我自己的比喻来说PlantUML 是高定西装适合正式建模场景Mermaid 是速干T恤适合日常快速记录Graphviz 是缝纫机能缝出任何形状但需要你会踩踏板。2.2 我在实际项目中的组合方案一台打印机两种墨水我的实际选型思路是这样的凡是 UML 语义相关的建模图比如类图、时序图、用例图、部署图一律用 PlantUML它语义贴近、开箱即用。凡是文档内嵌的轻量级流程、状态流转、数据流展示一律用 Mermaid因为它天然适配 Markdown 生态。凡是遇到复杂大图需要精细布局或是自动分层不理想的情况就下钻到 Graphviz 的 Dot 语言手动干预布局参数。这套组合方案运行下来的真实感受是90% 的场景下PlantUML 和 Mermaid 已经足够剩下 10% 的复杂场景Graphviz 帮你兜底。做选型不能有一招鲜心态工具链要像一个工具箱需要什么拿什么。组合方案之外的第二个决策点是渲染链路的搭建。我用的是本地命令行工具构建脚本统一管理.puml文件批量调用plantuml.jar输出 PNG 和 SVG再对 Mermaid 文件走mmdc命令渲染最后集成到文档构建流程里。整个链路跑起来的成本其实很低核心就是一次性写好脚本后面就是改文本-跑命令-出图的循环。3. 从零到一完整实操接管一个遗留系统的图表设计3.1 场景画像与准备动作用文本图表重建系统全景我们接手了一个老系统文档几乎是空白代码里也没人画图。系统有微服务网关、用户服务、订单服务、支付服务、优惠券服务、消息服务服务之间通过 HTTP 和消息队列交互数据库有六张核心表业务状态机也比较复杂。我们规划了三个层面的可视化覆盖架构总览图用 PlantUML 组件图呈现服务边界、依赖方向、通信协议让新同学一眼看懂有哪些服务和谁在调谁。核心交易链路时序图用 PlantUML 时序图从用户下单到支付回调、订单状态流转的完整消息序列用来排查线上问题。数据库 ER 图用 PlantUML 类图模拟实体关系呈现六张表的字段设计、主外键、索引情况和实体间关系。第一步先统一环境。我建议在纯净的 Docker 容器里测试避免系统环境互相污染。PlantUML 需要 JDK 环境Mermaid 需要 Node.js 环境Graphviz 需要原生库。最简单的路子本机装 JDK 和 GraphvizNode 环境用于跑 Mermaid。确认 Java 版本PlantUML 要求 JDK 8 以上常见发行版用 JDK 11 或 17 都没问题。Graphviz 安装时注意版本Windows 上建议安装稳定版Linux 上用包管理器装的版本一般够用。Mermaid 的mmdc命令全局安装即可。注意PlantUML 官方站点可能无法直接访问下载 jar 包时如果卡住可以尝试镜像仓库或直接在 Maven 中心仓库找net.sourceforge.plantuml:plantuml。我用的是 Maven 中心仓库渠道实测速度稳定。3.2 第一张架构图五分钟用 PlantUML 组件图搭出服务全景架构图是团队沟通的第一语言也是所有文档的门面。我来演示用 PlantUML 画微服务架构组件的完整过程。startuml!theme plaintitle 模拟项目X 微服务架构总览component API 网关 as gateway {port 8080 as p_gateway}component 用户服务 as user_svccomponent 订单服务 as order_svccomponent 支付服务 as pay_svccomponent 消息服务 as mq_svcuser_svc -- order_svc : HTTPorder_svc -- pay_svc : HTTPorder_svc -- mq_svc : MQ 事件gateway -- user_svc : 路由gateway -- order_svc : 路由gateway -- pay_svc : 路由enduml一段示例通常渲染出来的图会有清晰的组件边界因为我们给网关定义了内部端口。语法上值得注意的两点其一component 描述 as 别名的别名机制避免反复写中文标签后续引用更简洁。别名的命名规范我用的是xxx_svc或xxx_db的可读形式后续在大型图里维护时不会迷失。其二依赖箭头的方向要刻意约定好一律从调用方指向被调用方。很多团队画架构图是各画各的有人箭头指向下游有人指向上游看着乱其实只是没约定方向。我在团队的绘图规范里明确了一条箭头永远表示调用方向。之后再看到图不用问就能判断出谁依赖谁讨论问题时非常省力。图渲染出来先用肉眼看的关键检查点每个服务是否都出现了边线有没有交叉到影响阅读如果线交叉严重调整声明的顺序或给边添加--控制走向。这套架构图不是画完就封存的一次性产物。服务拆分、新增依赖、接口变更时改的都是几行文本提交到 Git 后图也跟着文档仓库持续演进——这就是 diagram-design 方案最值钱的地方。3.3 第二张核心图时序图理清交易链路的每一次握手时序图对一个订单-支付-通知链路尤其有价值因为链路里异步行为多消息顺序一错就是线上事故。用 PlantUML 的时序图画法startumltitle 下单到支付回调核心时序actor 用户 as Uparticipant API网关 as Gparticipant 订单服务 as Oparticipant 支付服务 as Pparticipant MQ as MU - G : POST /ordersG - O : 路由下单activate OO - P : 发起支付activate PP -- O : 返回支付凭证deactivate PO -- U : 返回pending状态deactivate O... 异步支付回调 ...P - M : 发送支付成功事件M - O : 消费支付成功事件activate OO - O : 更新订单状态O -- U : 推送消息(通知发货)deactivate Oenduml时序图里面最容易被忽略的就是activate和deactivate的配对。有些同学画顺序消息画得飞起但忘了标记生命线激活范围渲染出来的图就失去时间感。激活条的语义是代表某个对象在这段时间内正在处理请求不要随意省略。每画一对消息检查一下激活条是否闭合这是读图的人排查性能问题的重要线索。旁注... 异步支付回调 ...的写法可以让流程更可读。当异步消息穿插在同步调用中强制分段表达阅读者不容易把时间线搅乱。另一个要注意的点是时序图里的箭头类型有实际语义--表示返回--加异步是异步返回-是同步消息。如果团队里有人把返回也画成-时间线表达就会失真。语法只是表象语义准确是底线。3.4 第三张核心图ER 图把数据库关系变成视力可扫的一页纸数据库有六张核心表我直接用 PlantUML 的类图来模拟实体关系。选类图而不是专门的 ER 模块是因为类图对关联语义表达更成熟代码即文档后续维护也不容易走形。示例片段startumltitle 数据库核心实体关系!theme plainhide circleskinparam classAttributeIconSize 0entity 用户表 user as user_t {* user_id : bigint PK--nickname : varcharmobile : varcharcreated_at : datetimestatus : tinyint}entity 订单表 orders as order_t {* order_id : bigint PK--user_id : bigint FKorder_no : varchartotal_amount : decimalstatus : tinyintcreated_at : datetime}entity 支付流水 pay_trans {* trans_id : bigint PK--order_id : bigint FKpay_status : tinyintchannel : varchar}order_t }o-- user_t : 下单人pay_trans }o-- order_t : 支付记录endumlER 图容易踩的坑是关系基数表达模糊。PlantUML 的关联符号有丰富语义}o--是 many-to-one 的 optional 形式||--o{是 one-to-many 的强制端形式}--{则是 many-to-many。我最初偷懒省略基数符号结果看图的人总在猜一个用户到底能有多少订单。建议尽早约定规范基数符号必须出现不能只用一根线描述关系。字段展示我做了降噪处理把不必要的 业务字段 隐藏只列出普适字段和主外键因为全字段铺出来图会熵爆炸。ER 图的受众是人不是数据库导出的字典表达核心关系才是它的职责。真正的字段级细节应该去查数据字典文档图只负责快速建立整体认知。4. 实战中绕过这些坑布局、中文化与自动集成的排查实录4.1 布局之痛节点拥挤、边交叉和渲染失真的解法不管你用哪个 diagram-design 工具布局都是绕不开的用户体验问题。图一旦超过五个节点交叉线就开始出现超过二十个节点默认布局基本就不可读了。我分享几个实测有效的调整手段。PlantUML 方面第一招是调整皮肤参数。skinparam系列选项能改变节点间距、字号、边距最常见的组合是skinparam componentStyle rectangleskinparam defaultFontName Microsoft YaHeiskinparam dpi 150dpi 150可以让导出的位图更细腻排版更充足。第二招是给图拆层。不是所有依赖都必须画在同一张图里我经常把大图拆成主视图 子视图主视图只保留关键枢纽服务子视图里再展开某个服务的内部细节这在设计沟通里叫分层抽象。第三招是使用together关键字把强关联的节点强制邻接减少跨块连线。第四招是翻转节点排列方向top to bottom direction可以调整整张图的流向如果想从左到右阅读用left to right direction。Mermaid 方面流程图可以通过graph TD从上到下或graph LR从左到右控制流向。节点多时建议用子图subgraph把业务域分组比如支付域、订单域、用户域各自独立渲染出来会有区域分隔框直观很多。Graphviz 方面最关键的是选对布局引擎有向依赖图用dot网状结构或者无明确方向关系的用neato环形部署关系用circo分层结构图用twopi。再配合节点排序ranksame可以手动指定同一层的节点水平对齐这招处理层级图是神器。4.2 中文显示与字体战争为什么你的图渲染出来全是方块这是国内团队用 diagram-design 方案最普遍的痛点没有之一。第一次跑 PlantUML 渲染出一堆方框时那种绝望感我现在还记得。注意不是文字乱码而是字体缺失导致字形绘制失败。PlantUML 的解决方案分三步第一步确认系统装了中文字体Windows 自带微软雅黑Linux 需要安装fonts-noto-cjk等字体包。第二步在绘图文件里指定字体名skinparam defaultFontName Microsoft YaHeiWindows或skinparam defaultFontName Noto Sans CJK SCLinux。第三步如果还是在某些环境渲染不出检查一下时序图里的非图形字体是否有单独设置skinparam sequenceMessageAlign center等参数不会影响字体但skinparam ParticipantFontSize和skinparam ParticipantFontName有可能覆盖默认字体这两个参数也需要统一设置为中文字体。Mermaid 的解决方案更偏浏览器它在浏览器端渲染时使用系统字体因此只要系统有中文字体即可但对于 Node 环境的mmdc命令行渲染记得在 Puppeteer 的浏览器配置里加载好中文字体否则同样会变成方框。Graphviz 的话需要给节点设置fontnameMicrosoft YaHei之类的字体属性或者统一用graph [fontnameMicrosoft YaHei]和node [fontnameMicrosoft YaHei]全局指定。注意测试中文字体时不要只测一个中文两个字。建议拿订单支付流程非常流畅这种混合了中文、引号、数字的长句子来压测引号和数字的渲染表现往往才是最容易出问题的区域。4.3 让图表进 GitCI 自动化生成与变更检查链路在团队协作里图上写的和代码实现不一致是比没有图更危险的事情。为了避免文档漂移我把 diagram-design 的生成过程做进了 CI 流水线并且加了一层变更检查。具体做法是这样仓库里有一个diagrams目录存放所有.puml和.mmd源文件每次代码合并前流水线自动执行渲染脚本将所有源文件重新生成 PNG/SVG。如果渲染结果与上一次提交的文件有差异流水线就失败提醒开发者你有没有同步更新架构图。这样图就不再是某次画完就没人管的孤儿而是跟代码一样被 review、被校验的一等公民。脚本层面核心就是一个批量遍历命令。Linux/macOS 下执行find diagrams -name *.puml -exec java -jar plantuml.jar -tpng -o output {} \Mermaid 对应的批量渲染写法for f in docs/diagrams/*.mmd; do mmdc -i $f -o output/$(basename $f .mmd).svg; done把这些命令包进 Makefile 或者 npm scripts再在 CI 里注册一步render-diagrams的 Job。整套链路过一遍后团队的文档更新从等某人手动重画变成了合并代码时自动伴随更新效率提升非常明显。5. 从 0 到 1 搭建你的第一个 diagram-design 项目5.1 目录规范与基础脚本模板先立规矩再画图工具链搭好之后最容易被忽略的是仓库规范。没有规范的 diagram-design 项目一段时间后目录就会变成一堆命名混乱的文件最终版.puml、最新2.puml、真的改好了.puml。我建议一开始就使用一套固定模板diagrams/ ├── source/ # 源文件区 │ ├── arch/ # 架构图 │ ├── sequence/ # 时序图 │ ├── er/ # ER 图 │ └── flow/ # 流程图 ├── output/ # 渲染产物区PNG/SVG ├── render.sh # 批量渲染脚本 └── Makefile # 任务编排入口命名规范上我习惯用小写中划线格式order-payment-sequence.puml避免中文文件名混入命令行时产生编码问题。渲染产物集中到output/目录让 Git 的.gitignore可以控制哪部分文件入库——源文件必须入库产物按需入库避免仓库体积失控。一个精简的render.sh脚本可以长这样先遍历所有 puml 渲染 PNG 和 SVG再遍历 mmd 渲染 SVG最后输出统计信息。第一次跑通这套脚本后以后画完图命令一执行产物就整整齐齐地躺在 output 里那种工程化的秩序感很值得体验。5.2 多格式输出与批量处理的进阶技巧diagram-design 进阶玩法是一次编写多处使用。我用得最勤的几个功能PlantUML 同时输出 PNG、SVG、TXT字符画三件套PNG 放文档SVG 放网页TXT 直接嵌进源码注释或提交说明里非常方便快速预览。Mermaid 配合--scale参数可以生成 2x 甚至 3x 分辨率的图放到大屏或 PPT 里不会糊。Graphviz 可以用-Tsvg和-Tpng批量出图SVG 的好处是文本可选中、可搜索在知识库里建立索引非常友好。另一个实用技巧是批量换肤通过命令行传递-Dskinparam...或者用!theme指令在文件内动态指定主题一套源文件可以输出深色系、浅色系两套图给不同场景用。线上事故复盘 PPT 用深色主题团队文档用浅色主题不必维护两份图。5.3 多人协作时如何处理冲突Merge、Review 与所有权约定diagram-design 方案的文本化特性在多人协作时是优势但也带来新的治理挑战。最常遇到的问题是两个人同时修改同一个.puml文件Git 合并出现冲突于是有人选择把整个文件覆盖掉导致他人的改动丢失。我的经验是分三层解决。第一层约定文件所有权。每个人负责自己所属服务域的图跨域的图由架构负责人维护。尽量从源头上避免两个人同时改一份文件。第二层冲突发生时因为图是文本冲突标记很容易看懂。结合 Git 的 diff把对方的描述段落手动合入再让渲染引擎裁决结果是否美观。第三层善用 Review 文化。所有对.puml的修改都通过代码评审合入评审人重点检查语义有没有变化——比如有没有新增依赖、有没有改变消息顺序。这个环节也是帮团队新人熟悉系统架构的绝佳窗口他一边检查图一边就把系统结构过了一遍。6. 常见问题排查速查这张表帮我省了至少三天时间把我半年多的实战问题沉淀成一张速查表直接抄走就能用。问题现象排查思路解决方案渲染出来全是方框或问号中文字体缺失或未指定安装 Noto CJK 字体在 PlantUML 中skinparam defaultFontName Noto Sans CJK SCGraphviz 节点增加fontname属性图只有一半或者节点溢出边界节点内容过长默认布局宽度溢出增加skinparam maxMessageSize时序图、拆分大图、减少单图节点数CI 里渲染报错找不到命令环境变量或依赖未安装Docker 容器内完整安装 JDK、Node、Graphviz确认 PATH 包含可执行文件输出的 SVG 在浏览器里显示异常SVG 缺失字体或 viewBox 异常检查生成工具的版本尝试升级 render 库必要时改用 PNG 输出布局交叉太多不可读只使用了默认布局切换布局引擎dot/neato/circo/twopi、使用子图分组、手动指定ranksame多人修改同一文件产生冲突缺乏文件所有权约定约定服务域与文件所有权冲突时手动比对语义评审合入表情符号或特殊字形渲染缺失目标字体不含对应字形换用支持该字形的字体给文本加 HTML 转义或 Unicode 转义提示表格里最容易被忽略的是渲染产品与工具版本的兼容性。工具升级往往会带来默认布局或字体的变化所以 CI 环境的依赖版本必须锁定版本号不要用通配符否则今天能渲染、明天可能莫名失败。7. 往后可以往哪些方向延伸实战中一套 diagram-design 流程跑顺之后我发现可以继续挖掘的价值点不少。第一是往文档体系里延伸。将图与文字说明组合编排比如架构决策记录ADR里直接嵌入架构图让图成为决策的佐证材料。由于源文件是文本一旦架构图更新整个文档的上下文也能联动更新。第二是往代码生成方向延伸。利用 PlantUML 的类图定义配合模板生成代码骨架实体类、接口、数据库脚本一定程度上把设计和编码做了对接。虽然代码质量还需要人工打磨但生成的骨架能逼着你把设计先行想清楚。第三是往物理部署环境可视化的方向延伸。通过脚本读取云环境或主机列表、服务发现配置自动生成基础设施依赖图。维护一套图变成维护一套元数据脚本信息更新更及时也不会出现部署拓扑图已过期的尴尬。第四是接入知识管理平台。很多知识库支持任意文本渲染成图团队 Wiki 从粘贴图片截图升级为粘贴代码块渲染后续需要调整时改文本即可重新出图从根源上消灭截完图就看不清楚的老问题。我在实际项目中体会最深的一点是diagram-design 不是把画图的工具换成写代码的工具而是把呈现结果的思维转变成维护模型的思维。你维护的是一份系统关系的描述渲染出来的图只是这份描述的一个投影。当团队放弃改图而习惯改描述时文档维护的负担会骤减图的正确性也会大幅提升。如果这个方案的做法对你有参考价值可以挑一张你最近改得最痛的那张图用文本画一遍试试。先不要追求多复杂把工具链和流程跑通你大概率会发现这可能是今年最值得养成的工程习惯之一。