Diagram as Code:把架构图当代码管理,构建可版本化、可自动校验的工程化图表体系
在方案评审会上被问“这张图表达的重点是什么”在维护老项目时对着三张互相矛盾的架构图无从下手在写技术文档时为了调整一个箭头位置花了四十分钟——如果你对这些场景很熟那diagram-design这套思路大概率能帮你走出来。我做了很多年架构设计和技术写作最近几年把“画图”这件事从前期的临时输出沉淀成了一整套可以复用、可以版本管理、可以自动校验的工程化流程。这篇文章就围绕diagram-design这个主题把我实际落地的工具链、命名规范、渲染管线以及踩过的坑完整拆给你看。无论你是后端开发、前端工程师、架构师还是负责写技术方案的研发同学只要平时需要输出流程图、时序图、架构图或依赖关系图这篇文章都值得你花十分钟认真读一遍。1. diagaram-design的定位图表不只是文档的配图而是工程产物1.1 先把图当代码看待很多问题就迎刃而解大多数团队对“画图”的认知还停留在“打开画图工具、拖几个框、导出图片、粘贴到文档”的阶段。这种方式最大的问题不是图丑而是图的生命力在你关闭编辑器的瞬间就结束了。画完的图是一张静态图片它无法被检索、无法被diff、无法被评审、无法随代码一起走版本管理更没法在架构演进时自动提醒你“这张图已经过期了”。diagram-design这个思路的核心转变就是把图当成一种结构化文本资产跟源代码一样对待。我自己的习惯是任何一张需要出现在正式文档里的图必须有一个对应的源文件放在仓库里源文件用纯文本或DSL描述图的结构和关系。渲染成PNG或SVG只是构建产物源文件才是唯一事实来源。这样做的直接好处是每次修改图都是一次可追踪的代码提交评审人能清楚看到这次改动改了什么边、加了什么节点而不是“你给我截个图我看下”。1.2 这个项目到底要解决哪些问题diagram-design并不是指某个单一软件而是一套从需求拆解、工具选型、规范约定到自动化产出的完整流程。我总结下来这套方案主要解决四个高频痛点第一图散落各处。有的图在Notion里有的在本地画图工具里有的直接截进了IM聊天记录真正需要它的时候找不到最新版。第二版本不可追溯。架构调整了一版、两版、三版但文档里的图可能还停留在最初设计稿没人记得更新。第三风格不一致。一个人画出来的时序图是横版的另一个人画的是竖版的这个人用红色表异常那个人用红色表核心链路。看图的人每次都要重新学习图例。第四重复劳动。每次画相似的系统依赖图、相似的部署拓扑图都要从头拖框连线效率极低。这套项目把所有图表纳入统一的目录管理、统一的工具链渲染、统一的风格约束再通过持续集成把图自动同步到文档站点或知识库。本质上就是把画图从“一次性手艺活”升级成“可持续交付的基础设施”。1.3 适合谁读以及你能获得什么如果你是团队里的技术负责人看完之后可以直接在自己团队推行这套方案把图表的维护成本降下来。如果你是经常写技术文档的研发工程师能从第五节的实操案例里拿到可以直接用的模板和命令。如果你只是偶尔画一两张流程图那至少工具选型这一节能帮你避开不少坑。我不打算只讲“用哪个工具好”因为工具只是整个链路里的一环。更关键的是你如何决定一张图应该画到什么粒度、哪些关系必须表达、哪些细节可以省略、以及如何让图在团队协作中持续保持有效。这些才是diagram-design真正值钱的地方。2. 工具选型解析三条主流技术路线的选型看这一篇就够了2.1 纯文本派Mermaid与PlantUML各自擅长什么“diagram as code”是现在最主流的方式。它最大的优势是文本可diff、可复用、可嵌入Markdown文档我们可以用任意文本编辑器维护也可以直接在文档仓库里和代码一起评审。Mermaid是目前社区热度很高的方案语法简单到几乎不需要学习成本。一个基本流程图只需四行就能画出来这使它天然适合嵌入式文档比如README里的快速向导、接口文档里的调用流程图。尤其值得一提的是它对时序图和甘特图的支持我觉得在中小规模场景下完全够用。尽管PlantUML的语法稍微重一些但它的领域覆盖面很广组件图、状态图、用例图、时序图、活动图都有专用语法。如果团队需要输出正式架构设计文档我建议优先考虑PlantUML。它的一个隐藏优势是支持通过PlantUML server或者在本地配置PlantUML插件现在也支持包括C4模型在内的高级扩展复杂架构表达更强。很多人纠结Mermaid和PlantUML应该选哪个。我的判断标准很直接文档内嵌的轻量图示用Mermaid正式架构设计文档用PlantUML。两者并不冲突也可以同时存在于同一套工程里只是对应不同场景。2.2 数据驱动派Graphviz与D2解决更复杂的图结构当图的节点达到几十个甚至上百个比如服务间调用关系、模块依赖关系、数据血缘关系时手工排版基本不现实。这时候需要用自动布局算法Graphviz就是这一类工具中的元老。它的dot语言用声明式描述节点和边由引擎自动计算位置对树形、层次化、环形等结构都有专门算法。我常用Graphviz的场景是依赖分析。比如代码仓库里某个模块到底影响了多少下游依赖我可以直接脚本扫描依赖关系生成dot文件然后交给Graphviz渲染。整个过程完全不需要“画”只要数据准确图就是准确的。D2是近年出现的后起之秀它把“表达能力”和“易读性”做了更好的平衡。相比dot语言D2的语法更接近人类自然语言的直觉像“x - y: label”这种表达几乎不需要查文档。从实际体验来说它生成的布局风格也比较现代用过一次之后很容易喜欢上。2.3 手绘风格与协作画布适合实时头脑风暴的补充工具链里还留一类“自由派”Excalidraw、Clay、云雀白板等在线协作工具。它们最大的价值是零门槛和实时协作。方案讨论初期几个人凑在一起快速画一版粗糙的草图比用DSL边写边想快得多。不过这类工具生成的文件往往是JSON格式diff体验较差。所以我把它们定位成“草稿区产物”。草稿一旦达成共识我会在当天之内把最终结构誊写成文本DSL放进正式文档库。这样既能快速响应讨论节奏又不牺牲版本可追溯性。2.4 工具选型速查表工具适用场景学习成本版本控制友好度主要缺点MermaidMarkdown文档内嵌图、时序流程、快速草图低高复杂布局控制较弱PlantUML架构文档、组件图、状态机、C4模型中高语法细节相对繁琐Graphviz大节点数的依赖关系、自动布局中高高控制力高但上手不友好D2声明式架构图、拓扑图低高生态相对年轻Excalidraw头脑风暴、手绘风草图极低一般文件为JSONdiff困难单纯选定工具还不够你是不是已经开始关心不同工具之间的流转下一节我会专门讲从需求拆解到最终成图的工作流设计那才是让工具真正发挥价值的前置条件。3. 核心细节解析一张好图不是画出来的是设计出来的3.1 先问自己这张图到底是给谁看的很多图之所以被评审人反复质疑通常不是因为画得不好而是因为“视角”不统一。画图前不是先打开工具而是先回答三个问题看图的人是谁他关心什么他希望从图里获得什么结论。给技术决策者看的图可能要突出风险点和取舍点给新成员看的模块图要展示上下文和入口出口给合作方看的对接图要突出双方系统边界和依赖协议。同一套系统至少需要三四种不同粒度的视图。我见过很多团队试图用一张大而全的“系统全景图”打天下结果就是所有人都看不清。diagram-design的第一步就是把“一图多用”的思维切换成“一图一义”。3.2 命名规范和目录结构的实践建议图源文件也是代码资产那它的命名和组织方式就应该有纪律。我目前推荐的结构是docs/ diagrams/ project-a/ 01-overview.puml 02-sync-sequence.mmd 03-dependency.dot project-b/ ... images/ project-a/ 01-overview.svg ...文件名使用编号前缀来控制阅读顺序中间用业务模块名后缀用工具类型。这样在文件管理器里排序在IDE里搜索在自动构建脚本里遍历都很方便。同时渲染出的图片统一输出到images目录文档里引用相对路径整个文档库的依赖关系就清晰了。3.3 模板沉淀把重复劳动收敛进一个文件里我平时会维护一个diagrams-template目录存放不同图型的“骨架模板”。例如架构分层图模板会预先定义好从上到下五层的公共样式时序图模板会预先定义好参与者和容器的主题颜色C4组件的模板会预置好边界框和描述字段。这样当新项目需要画架构图时我只需要复制模板替换成实际模块名再修改关系连线十几分钟就能产出一张规范图。省下来的不是一次半小时而是一个长期的效率提升。模板仓和实际项目仓分开管理模板更新后通过脚本批量同步到引用目录也是一套可行的做法。3.4 样式规范颜色、字体、线条的约定俗成样式不一致是多人协作画图时最普遍的问题。我给团队的约定是颜色只代表状态或层次不代表个人偏好。例如主链路用深色实线备链路用灰色虚线异常路径用红色分层架构各层统一用同一浅色底。节点文字统一用系统无衬线字体避免在部分环境出现字体退化。每个图在右下角必须标注“业务模块-图名-版本号”这样上下文信息就不会丢失。3.5 一个重要的守恒原则内容粒度我常打比方图本质上是一份带位置的摘要而不是数据库导出报表。画图时最忌讳的是把所有服务器、所有接口、所有字段都塞进去结果整张图成了一面密不透风的墙。设计图的关键是在“信息完整”和“可读性”之间找到平衡。我的习惯是如果图上的节点超过15个且不是特意做依赖全景大概率需要拆图。每一张图只表达一个核心主题那它就是好图。4. 实操过程与核心环节实现4.1 业务时序图的完整落地过程先看一个我用Mermaid落地业务时序图的场景订单状态机在“支付成功”后触发库存锁定、积分发放、通知三个下游动作我需要把这套交互逻辑画出来给后端联调用。我最终采用的文本示例是sequenceDiagram participant Client participant OrderService participant InventoryService participant BonusService participant MQ Client-OrderService: 支付成功回调 OrderService-InventoryService: 锁定库存 InventoryService--OrderService: 锁定结果 alt 锁定成功 OrderService-BonusService: 发放积分 OrderService-MQ: 发送订单已支付事件 else 锁定失败 OrderService--Client: 返回失败 end这里有一个很容易忽略的细节参与者定义顺序决定了时序图的左右排列顺序要按真实调用方向从前往后写不要想到谁写谁。调试过程中我发现MSO 展示中文默认字体没问题但一旦部署到某些CI容器里就出现乱码。后面我会在问题排查一节单独讲字体问题。4.2 用Graphviz表达复杂的服务依赖关系某个中台项目里服务数量到了30个以上我需要让架构评审会的人一眼看出核心依赖与共享依赖。我用dot语言描述服务与依赖关系示例类似于digraph G { rankdirLR; api-gateway - user-service; api-gateway - order-service; user-service - user-db; order-service - order-db; order-service - inventory-service [colorred, penwidth2]; inventory-service - inventory-db; }Graphviz会根据节点关系自动布局但我通常会加上rankdirLR来告诉引擎我偏好横向延伸因为横向图在会议室的宽屏投屏上更容易看清。如果节点过多导致边交叉严重我会分成“入口层-业务层-存储层”三张子图而不是强行塞进一张图。依赖和分层是两个维度表达时不要混在一起。4.3 用PlantUML画架构组件分层图在交付这个项目时我保存了一个PlantUML组件图源文件核心结构是startuml package 接入层 { [API Gateway] [消息消费端] } package 业务核心 { [订单引擎] [库存核心] [优惠计算] } package 存储 { database OrderDB database InventoryDB } [API Gateway] -- [订单引擎] [订单引擎] -- [库存核心] [订单引擎] -- [优惠计算] [库存核心] -- [InventoryDB] [订单引擎] -- [OrderDB] endumlPlantUML的组件图表达的是模块边界所以我在命名时特别注意避免“类名”思维要多用名词短语表明它承担的职责边界。另外组件图里尽量少画Builder、Utils这一类工具类组件它们会扰乱边界感。评审时最能抓住注意力的永远是那些被标成红色、加粗的跨层依赖这是我在实践中反复得到正反馈的做法。4.4 构建脚本与持续集成让图永不落伍源文件写得再规范如果每次更新都要手工输出图片很快又会回归“改了源文件忘了更新图”的老路。所以我引入了CI流水线每当docs/diagrams目录发生变更自动执行对应工具的渲染命令把所有图源文件编译成SVG再copy到docs/images目录提交后触发文档站部署。我的脚本核心逻辑可以简化成两段伪代码# 批量渲染Mermaid find docs/diagrams -name *.mmd -exec mmdc -i {} -o docs/images/{}.svg \; # 批量渲染PlantUML java -jar plantuml.jar -tsvg -o ../../images docs/diagrams/**/*.puml这里注意mmdc是Mermaid CLI的全局命令需要先在CI环境安装对应npm包。渲染失败不应该让整个流水线变红而是先通知到图表负责人否则开发者的正常提交会被无关的图渲染阻塞。我在实际落地时把“图渲染”单独作为一个stage并用continue-on-error托管只有文档站打包失败才阻断。5. 常见问题与排查技巧实录5.1 渲染乱版、布局飘忽不定有些DSL图源文件在本地渲染正常换到另一台机器或CI环境就乱了。这种情况大概率出在自动布局引擎的版本差异上。我踩过的坑是Graphviz版本从2.40升到2.49之后老图的边走向变了。解决方案是在源文件顶部标记引擎版本或使用明确的rank约束同时把常用工具的版本锁定在CI配置文件里。5.2 中文字体与乱码问题渲染图在本地正常、CI上全乱码这是国内团队最容易踩的坑。PlantUML组件名、Mermaid节点文字里直接写中文默认依赖系统字体而CI容器往往没有中文字体包。我的处理方法是预先在CI镜像里安装fonts-noto-cjk或等价字体并在Mermaid渲染时显式指定fontFamily。Graphviz的中文显示则需要在dot文件里加node [fontnamePingFang SC]或对应字体名。此刻我还会在本地和CI之间保持一套完全相同的字体清单。5.3 大图性能问题当Graphviz的节点超过200个渲染时间会指数级上升生成图片的阅读体验也会变差。我给团队的规则是依赖全景图必须有数据支撑但不直接作为评审图评审使用的图必须按域或按层裁剪。另外如果确实需要看全局Mermaid有一种“收缩”技巧用subgraph把不关心的节点放进折叠区块避免全部展开。5.4 团队协作中的冲突与过期问题多人同时修改同一份图源文件时合并冲突无法避免。DIFF成本高的一个根源是大家总是喜欢手动调整局部坐标。我的建议是尽量使用自动布局减少显式坐标唯一的坐标例外是特殊强调的场景但也要在注释中写明原因。版本过期问题则需要靠“图与代码同仓库同评审”这个制度保障架构图变更必须走MR评审而不是私下改完直接发到群里。5.5 补充一个隐蔽问题图片引用路径文档站部署后图片加载失败一半情况是路径大小写或深度出错。因为MAC默认文件系统大小写不敏感而Linux的CI环境是敏感的。我后来统一了规范所有图片引用都使用小写连字符命名且在文档中明确使用.github相对路径彻底规避了这个问题。6. 从几个真实项目中复盘的经验graph-design这套方案我先后在早期技术文档库、后续新业务架构设计阶段两种项目形态里用过过程中的复盘心得很有价值。在早期技术文档库项目里最大的收益是追踪成本大幅降低。以前评审会前要花半小时问“谁有这个图的原文件”现在所有图直接在仓库里任何人checkout最新代码就能看到当前真实状态。另一个收益是复用性新同学画类似流程图时直接参考已有源文件不到一天就能产出符合规范的图。在新业务架构设计的早期我明显感受到图走版本管理反而让大家更愿意改图了因为每一次修改都有迹可循评审和讨论都能落在具体diff上。图不再是一张一次性产出而是持续演进的设计记录这在以前是不可想象的。如果让我说一个踩过最深的坑那就是“过度设计图规范”。一开始我规定每个图必须包含图例、版本号、作者、更新时间等一系列元数据结果团队成员的产出意愿明显下降。后来我简化成“图上必须能看出业务模块和图名”其余元信息放到文件头注释里配合仓库的提交历史就够了。人永远是先愿意画才谈得上规范。7. 给你的迁移指南如何低成本地把diagram-design引入团队如果你团队还在用截图式画图方式切勿直接全量铺开那会引来反弹。我最推荐的做法是三步走第一步先在一个新项目里跑通“DSL源文件CI渲染”的最小闭环。不需要推全量规范只要文档仓库里多一个diagrams目录新写的图全部用文本DSL表达。第二步把团队常用的图型做成模板放进统一仓库发起一次专项分享让大家看到复制模板的产出效率自然会产生正反馈。第三步等常用路径稳定后再补充样式规范、命名规范、评审规则。规范要尽量来自实际协作中的摩擦而不是空想出来的条条框框。我个人还有一个经验是不要试图用一个工具解决所有问题。哪怕团队统一了主推工具也要保留白板类工具作为头脑风暴的入口同时做好草稿到正式DSL之间的“誊写”仪式。这不仅仅是方便溯源更是一次强制性的逻辑整理。你在白板上画的时候可能很随意但誊写成DSL会逼着你把“这个模块到底叫什么”“这条边到底代表调用还是数据流”想清楚。另外我建议定期做一次“图资产体检”。每季度抽出一天遍历diagrams目录把那些已经没人在更新、内容与代码现状脱节的图标记出来或直接删除。图资产跟代码资产一样除了“增加”以外“删除”也是健康的维护动作。这个东西只要你坚持下来团队文档的可靠性和维护效率都会有实质改变。