
做后端这些年我越来越怕两件事一是历史遗留系统的祖传流程图二是刚画完就要改的架构图。前者美术满分但逻辑是一团乱麻后者逻辑清晰可等到下个迭代连作者本人都懒得打开文件去改。diagram-design 在这种时候就成了救命稻草——它不是某个被吹上天的绘图工具而是把架构图、流程图、时序图当成软件工程资产来管理的一套方法。diagram 是图design 是设计合在一起就是“让图可以被设计、被维护、被自动化”。这篇就聊聊我用这套思路重做系统架构图的完整过程从需求拆分、元素抽取到 PlantUML 代码化、CI 自动校验应该对后端开发、架构师和技术文档负责人都有些参考价值。1. diagram-design 的核心思路先当工程问题再谈画图很多人打开画板的第一件事是选配色第二件事是拖色块。但画架构图不是做海报一张图能不能用取决于它能否在 30 秒内把系统边界、依赖方向和关键路径讲清楚。diagram-design 的第一原则很简单在画图之前先定义信息架构再决定表现形式。就像写代码一样先设计领域模型再写抽象接口最后才是具体实现。如果你一上来就纠结某个按钮的颜色大概率是因为还没想清楚这张图到底想让人看懂什么。1.1 图设计的本质是信息架构不是美术表达我见过不少团队画出来的架构图细节特别足各种服务画得跟大富翁地图似的但真要问一句“这个系统核心链路是什么”对方指着图说半天也指不明白。问题不出在画功上而出在信息层次没分。好的图设计应该像一篇文章的目录读者一眼就能找到自己关心的章节然后按需往下钻。比如一张微服务架构图如果读者能靠颜色和位置区分核心服务、支撑服务和基础设施说明信息架构是成立的如果大家只能靠图例反复对照那这图基本等于没画。举例来说同一个订单系统给老板看的图和给开发看的图绝不能是同一张。给老板看只需要“客户端-网关-订单服务-支付/库存”这一条主线再标上流量规模给开发看就得画到数据库分表、消息队列 topic、重试队列和补偿任务。很多人把“画一张全量系统图”当目标结果图画出来业务看不懂开发也嫌太粗。diagram-design 的解法是先定视角再定颗粒度一个视图只解决一类问题。1.2 最常见的两类失败图美术过载和细节过载失败图大致分两种一种是“装饰品”一种是“世界地图”。装饰品图的典型特征是每个节点有渐变、阴影、圆角和图标箭头用曲线还带发光效果但逻辑关系混乱箭头一会儿表示调用一会儿表示数据流一会儿又像单纯的视觉装饰。这类图的维护成本极高原作者换一次配色下游所有插图都要重出。世界地图图则走向另一个极端把系统里所有能想到的组件都堆在一张图里中间还有几十条交叉线看起来非常“专业”实际上没人能看懂。我在 diagram-design 的实践里把这个问题的根源归结为“缺少工程约束”。手绘工具做图本质上跟没有代码规范的项目一样每个人的风格不同改起来全凭感觉最后只能靠情怀维护。代码化工具虽然不能直接帮你设计布局但至少让所有图都变成了可 diff 的文本谁改了哪一行、把哪个服务依赖删掉了在 code review 里看得清清楚楚。这也是为什么我后面会推荐把图当作代码管起来而不是放在某个在线白板里吃灰。2. 从需求到成品一张架构图的四步设计流程把 diagram-design 落地成具体操作我一般会走四步确认读者和视角、抽取核心元素、定义关系类型、选择布局逻辑。这四步做完再动笔通常一版图就能让团队达成共识反过来如果跳过前两步直接开画大概率要返工。下面展开聊聊每个步骤里到底要做什么。2.1 第一步确认读者和视角一句话说清“这张图回答谁的问题”我现在的习惯是画图前必须写下一句话“这张图要帮助谁做什么决策”。如果这句话写不出来那就先别画。这句话也决定了后续所有取舍。比如技术负责人最关心系统边界、依赖耦合和成本分布开发同学最关心调用链、异常路径和超时设置运维同学最关心部署节点、端口和网络策略。你不可能把这三类信息塞进同一张图里硬塞的结果就是每个人都要在图上找半天最终谁也不满意。我经手过一个线上订单链路最初的设计文档里放了一张“全仓图”从 Nginx、Gateway 到每个微服务再到 Redis、MySQL、MQ甚至还有定时任务调度器密密麻麻三十多个节点。评审时后端负责人问“支付超时后的补偿链路在哪”全场的目光在图上扫了三分钟也没找到。后来我们把这张图拆成总览图、同步调用时序图、异步补偿链路图三张每张只回答一个问题评审效率立刻上来了。这就是确认读者和视角的价值。2.2 第二步抽取元素把系统拆成节点、边界、连接器、标注四类所有技术图本质上都逃不过这四类元素节点代表系统里的组件或服务边界代表逻辑或部署上的分组连接器代表节点之间的关系标注负责补充关键信息比如状态码、超时时间、数据量。画图容易失控往往是因为把边界和节点混在一起比如把订单服务写在数据库框里读者第一反应就是“订单服务是数据库的一部分”信息就变了。举个简化的订单场景抽取完成后大概是这样的节点App、网关、订单服务、支付服务、库存服务、订单数据库边界客户端层、接入层、应用层、基础设施层连接器App 调网关、网关调订单服务、订单服务调支付服务、订单服务内异步发事件标注支付超时时间、库存预扣规则、数据库读写分离这一步做完所有元素都是图里必须出现的没有一个是“因为有了更好看”才加的。画图的时候我也会反复提醒自己如果一个节点不能支撑读者做判断就把它拿掉。2.3 第三步定义关系类型用线型建语义规范技术图最容易乱的地方是箭头。很多人画一张图里实线箭头一会儿表示 HTTP 调用一会儿表示数据库连接虚线一会儿表示异步消息一会儿又表示“配置依赖”读者只能靠猜。diagram-design 里比较建议的做法是建立一套线型语义同步调用用实线箭头异步消息用虚线箭头数据流用空心箭头部署依赖用带圆点的直线。这套语义最好全团队统一不能今天这个人用虚线表示事件明天另一个人用虚线表示可选依赖。线型统一之后还要控制关系数量。我记得第一次重画订单链路图时画到一半发现节点只有 12 个但连了 30 多条线看起来跟蜘蛛网一样。后来强制自己只保留“关键路径”上的关系把监控、日志、配置中心这些旁路信息全部放到标注或附注里图才清爽起来。这里也给你一个判断标准如果一条线删掉之后读者仍然能理解系统怎么工作那它就属于噪声。2.4 第四步布局逻辑让依赖方向变成视觉引导线布局不是玄学它是在引导读者的眼睛按顺序阅读。一般技术图最好遵循“从左到右或从上到下”的主导方向让依赖关系顺着这条主线流动。如果图里箭头方向乱成一团左上一条、右下一横读者就得来回扫认知负担直接翻倍。我自己用的经验是入口放左上核心业务放中间数据存储放右下外部依赖靠边。这么做的好处是只要方向感稳定哪怕不看文字读者也能大概感觉到“数据是从这边流向那边的”。还有一条很实用同一层级的节点尽量水平对齐。比如所有应用层服务放在同一行所有基础设施放在下面一层。对齐会让图显得专业也更容易发现层级之间的职责边界。很多自动布局工具并不懂你的业务语义所以布局这步我一般会手工微调。PlantUML 里同样支持用together或横向排列来手动控制位置后面实操部分再细说。3. 工具选型与代码化实操为什么我最后留在 PlantUML工具这个话题在团队里永远能吵起来。其实没有万能的画图工具只有适不适合“被设计和维护”的工具。diagram-design 走到后面最重要的问题是图能不能进 Git 做 diff能不能被脚本校验能不能用模板批量生成基于这个标准我最终把核心图都迁移到了 PlantUMLMermaid 作为文档内嵌时序图的备选。下面说说选择逻辑和具体用法。3.1 主流画图工具横向对比把常见的工具放一起对比结论会清晰很多工具是否文本化协作体验维护成本适合场景draw.io弱本质是图形文件在线协作一般高二进制/XML 不直观临时草图、非技术团队协作Excalidraw弱好实时协作高手绘风格难统一方案讨论、头脑风暴Figma否强设计协作高偏 UI 设计技术图大材小用产品交互图Mermaid是依赖代码托管平台低语法简单Markdown 文档内嵌、CI 生成PlantUML是依赖代码托管平台低语法成熟架构图、时序图、部署图适合工程化画架构图最忌讳的是“工具绑架思路”。我见过一个团队因为喜欢 Excalidraw 的手绘风格硬把所有架构图都迁移过去结果每张图都无法稳定复现同一个组件在不同截图里长得还不一样。工具选型应该由“图的维护方式”决定而不是由“第一眼的颜值”决定。如果你想长期维护文本化几乎是必选项。3.2 PlantUML 从零画一张订单主链路图先说结论PlantUML 语法不难难的是不要一开始就追求全量图。从一个小链路开始练手等关系理清了再扩充。下面是一个订单创建主链路的例子我故意把边界和关系都写得比较完整startuml !theme plain title 订单创建主链路 package 客户端层 { [移动端 App] [Web 端商城] } package 接入层 { [API Gateway] } package 应用层 { [OrderService] [PaymentService] [InventoryService] } package 基础设施 { database 订单数据库 as orderDb queue OrderCreatedEvent as eventQ } [移动端 App] -- [API Gateway] [Web 端商城] -- [API Gateway] [API Gateway] -- [OrderService] [OrderService] -- [PaymentService] [OrderService] -- [InventoryService] [OrderService] -- orderDb OrderService .. eventQ : 发布创建成功事件 enduml这张图的关键信息在最后一行OrderService .. eventQ用虚线箭头表示异步消息和前面的实线同步调用区分开。看到这里你大概就能理解前面定义关系类型的意义了。PlantUML 里package天然适合表达边界组件用方括号表示数据库用 database 关键字阅读起来非常直观。我会建议初学者把这张图存成order-flow.puml然后跑一遍plantuml order-flow.puml看看生成结果。第一次看到自动布局出来的图位置可能和你想的不太一样。比如eventQ可能跑到支付服务旁边你需要在 package 或关系顺序上稍微调整。这很正常自动布局需要一点人工调教后面我会专门讲排障。3.3 团队内部图规范从一个人会画到所有人都会画代码有规范图其实也该有。diagram-design 能落地很大程度上靠“所有图看起来像同一个人画的”。我在团队里推行过一份简短的图规范只有五条边界统一用 package/rectangle命名用“XX层”或“XX域”不要叫“XX模块”这种含糊名字。核心服务和基础设施要有固定边框色颜色总数不超过 3 种。实线箭头表示同步调用虚线箭头表示异步事件/消息禁用其他自定义线型。每张图必须有标题、更新日期和负责人。节点命名用业务名词不要用 IP 或实例编号。这份规范最大的作用是减少评审时的沟通成本。之前大家看图总会先花五分钟解释线型规范出来之后评审直接进入业务逻辑讨论。你可以在代码库里建一个docs/diagrams/CONTRIBUTING.md把这五条写进去再配上示例图新同学来了一样能上手。4. 常见问题与排障实录从布局漂移到 CI 校验文本化图也不是没有坑。最典型的就是自动布局不受控、中文乱码、导出清晰度不够以及团队协作时图被改坏。这里我把自己踩过的坑和排查思路整理出来希望能帮你少走弯路。4.1 布局漂移加一个节点整张图满屏乱跑PlantUML 的自动布局在节点少的时候很省心节点一多就开始“自由发挥”。我遇到过最头疼的一次只是在应用层加了一个CouponService生成出来的图把订单数据库挤到了左上角支付服务跑到了最右侧整张图的箭头绕了好几个弯。原因是自动布局会尽量让关系线变短于是新节点周围的关系全被重新编排了。解决办法有几个按优先级排序第一用together把不可分割的节点组在一起防止它们被拆散第二适当使用direction规划整体方向比如left to right direction第三把关系按主链路顺序从上到下写PlantUML 会一定程度上沿代码顺序布局。还有一个小技巧如果某几个节点必须固定在同一行可以用隐藏关系或者-[hidden]-强制对齐虽然看起来有点 hack但很实用。最稳妥的做法其实是在接近定稿时把图导出成 SVG之后不要轻易改动结构只改文字标注。4.2 中文乱码、字体与导出清晰度问题PlantUML 默认会依赖本机字体渲染在 macOS 和 Linux 上很容易出现中文变成方框或者乱码的情况。解决方式不复杂就是在生成时指定一个支持中文的字体比如在代码里加skinparam defaultFontName Microsoft YaHei或者在命令行里传-charset UTF-8。注意本地测试通过不代表 CI 也通过因为 CI 环境可能没有中文字体所以跑 CI 之前要把字体一起装好。导出格式上我的建议是只要不是打印出来贴墙一律用 SVG不要用 PNG。SVG 是矢量图放大多少倍都不会糊Markdown 和 GitLab wiki 都原生支持。PNG 在 retina 屏幕上经常会显得发虚而且后续你想改个文字还得重新生成非常不方便。PlantUML 导出 SVG 只需要把参数换一下plantuml -tsvg your-file.puml生成的文件可以直接丢进文档里。4.3 把图的校验接入 CI每次提交都验证一次“能不能编译”图最大的问题是“没人知道它坏了”。一份半年没人动的架构图很可能已经和线上系统差了好几个版本但文档目录看起来岁月静好。为了不让这种事发生我用一个很简单的脚本把 diagram 接入了 CI每次代码提交CI 会遍历docs/diagrams/下所有.puml文件先做语法校验再生成 SVG。#!/usr/bin/env bash set -euo pipefail for f in docs/diagrams/*.puml; do echo validating $f plantuml -checkonly $f plantuml -tsvg -charset UTF-8 -o /tmp/diagram-out $f done echo all diagrams compiled successfully这段脚本看着简单但解决了几个很痛的问题。第一语法错误会在合并请求阶段被发现而不是等文档上线之后第二每次提交都能生成最新的 SVG文档页不用手动更新第三强制所有模块负责人养成了“改架构先改图”的习惯。后来我还加了一条校验用 grep 检查.puml文件里是否包含title、公司统一主题和负责人字段防止有人拿半成品图提交。不要小看这些硬性检查维护文档最大的敌人不是技术问题是人会偷懒。4.4 用 PlantUML 的 include 和变量做图模板当图的数量多了以后你会发现很多内容是重复的每个服务的图标、颜色主题、标题栏格式。每次复制粘贴不仅无聊还会导致后续改需求时漏改。PlantUML 支持!include和!define完全可以把公共部分抽成模板文件。比如建一个theme.puml里面统一放配色、字体和通用的 skinparam再建一个components.puml把常用服务的组件定义放进去。!define 订单服务 [OrderService] !define 用户服务 [UserService] skinparam componentStyle rectangle skinparam defaultFontName Microsoft YaHei skinparam backgroundColor #FFFFFF之后每张图只需要!include theme.puml就能继承所有配置。这就是 diagram-design 工程化的第二步先有规范再有模板最后有自动化。到了这一步画图某种程度上已经不是在“画”而是在“装配”了。5. 进阶经验让图真正成为团队的技术资产到这里前面讲的方法已经能帮你画出清楚、规范、可维护的图。但要更进一步把 diagram 从一个“文档文件”变成团队的“技术资产”我觉得还有三件事值得做。5.1 一张图只回答一个问题复杂系统拆成图组认知心理学里有个“7±2”原则人短时间能记住的信息块大约在 5 到 9 个。架构图里节点越多读者需要记忆的块就越多理解成本就越高。我现在给自己立了一个规矩核心节点超过 12 个就强制拆图。比如一个完整的电商系统架构可以拆成总览图系统边界与核心域、用户下单时序图、库存扣减状态图、支付回调异常链路图等。每张图各司其职读者按需阅读比一张超大图高效得多。拆图不是在偷懒而是在控制认知复杂度。文档里有一组图读者可以选择只看总览也可以深入看时序整套资料就像一本书有目录也有章节。反过来如果你只有一张巨型图读者只能被迫接受所有信息效果一定是最差的。5.2 图也要 Review架构图评审的价值代码要 review图同样要 review。且图的 review 比代码更快暴露问题因为一张乱图摆到大家面前几乎每个人都能感觉到“这张图有问题”只是说不清楚哪里不对。我在团队里推了“图随代码变更走”的规则如果这次迭代动了服务依赖合并请求里必须附带对应的.puml更新。Review 时除了看代码还会要求负责人对着新图解释一遍依赖关系。规则刚开始推行有些阻力但跑了两三个迭代之后大家发现图没发霉架构演进也被迫进入文档了。5.3 把静态图变成动态文档从 SVG 到自动发布最后一步是把生成的图和在线文档连起来。我的做法是CI 生成 SVG 之后通过 GitLab Pages 或 GitHub Actions 自动发布到一个内部文档站然后 TOC 自动列出所有.puml对应的图。这样做最大的好处是团队所有人看到的图永远是最近一次构建的版本不存在本地生成完忘记上传的问题。如果你觉得这条链路太重也可以退一步用一个 Makefile 把“编译图”和“启动文档预览”绑定本地一条命令全部搞定diagrams: plantuml -tsvg -charset UTF-8 -o dist docs/diagrams/*.puml serve: python3 -m http.server 8080 --directory dist .PHONY: diagrams serve这个方案我已经跑了近一年最大的感受是图从“画出来大家看一眼”变成了“随时可以用、可以查、可以演进”的活文档。它不会有温度但只要你持续维护它就不会背叛你。我个人在实操中还有一个体会别指望靠一个工具解决团队所有的作图问题也不要指望一份规范一夜之间改变所有人的习惯。diagram-design 真正能跑起来靠的是“把图当代码管”这个动作本身。你越早把第一张图放进 Git、第一次让它过 CI后面就会越轻松。最后再分享一个小技巧给团队统一一份主题文件把字体、配色和标题格式都写死在模板里后面所有人的图风格都会自然统一这就是最划算的投资。