拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Diagram-as-Code实践:用代码定义架构图,彻底告别文档与代码脱节

先说我为什么突然要聊diagram-design这个话题。前段日子接手一个维护了两年的老项目文档里的系统架构图还停留在三年前的版本画图的人早离职了源文件不知道在谁电脑里只剩一张模糊的PNG。我照着图去核代码发现服务拆分、调用关系、数据流向全变了那张图唯一的作用就是误导新人。后来我用Visio重新画了一版花了大半天。结果第二周需求一变要加一个中间层我又花了一下午拖动线条、重新对齐改完总觉得哪里不对但又说不上来。连续改了三次之后我彻底烦了决定把项目里所有架构图、流程图、时序图全部迁移到代码化图表方案上去。diagram-design本质上是用代码定义图表这一整套实践简单说就是图是文本的渲染产物而不是一张孤立的图片文件。文本能进Git能被Diff能被Review能写注释能自动在流水线里重新生成。这些能力放在传统拖拽画图工具里几乎每一项都是奢望。这篇文章不是要吹某个工具多厉害而是完整记录我这次迁移过程中做的事情、踩过的坑以及最后沉淀下来的一套可以直接照搬的工作流。如果你也在维护技术文档、梳理系统架构、或者被文档里的图和代码永远对不上折磨过这篇文章应该能帮到你。1. 从拖拽绘图到代码定义关系Diagram-as-Code到底解决了什么问题1.1 传统画图工具真正让人抓狂的点不在画本身很多人觉得画架构图麻烦第一反应是工具不好用。实际上Visio、ProcessOn、draw.io这些工具的画布能力已经很强了真正的痛点根本不在拖拽这个动作上而是图的可演进性和可追溯性完全缺失。拿我那个老项目举例。架构图保存在个人电脑里意味着团队其他人拿不到源文件只能看导出的PNG。图一旦生成就停止了呼吸没人敢改因为改一张图的成本实在太高了。一个服务改名你要改节点文字还要调整所有连到它上面的线线一拉其他节点位置全乱了又得重新排版。这种操作重复几次谁都会对更新文档这件事产生强烈的抵触情绪。更隐蔽的问题是图的版本无法比对。代码有Git一个commit能看出改了哪一行。图呢两张PNG摆在一起除非肉眼非常仔细地对比否则根本说不清差别在哪。项目里经常出现这张图好像过期了的模糊状态但又没人能准确指出哪里过期了。还有一点容易被忽略手绘图的样式规范是完全失控的。五个人画五张架构图画出来是五种风格。有的人用红色代表告警连接有的人用红色代表已下线模块看的人还得先猜颜色语义。这不是工具的问题是流程和载体的问题。1.2 代码化图表的三个核心价值文本化、自动化、规范化diagram-design这套思路之所以能成立第一个原因是文本化。图变成了一个.mmd文件或者.dot文件本质上是几行文本。文本天然支持版本管理能进Git仓库能写commit message能和代码一起走Code Review流程。同事改了一个节点提交记录里看得清清楚楚。两个版本之间有什么差异Git Diff直接给你标出来。这对团队协作的提升是碾压性的。第二个价值是自动化。文本文件可以批量生成可以放在CI流水线里自动渲染、自动校验结构完整性甚至可以写成脚本定时抓取线上的服务注册信息自动更新架构图里的节点状态。传统画图工具想都不敢想这种玩法。第三个价值是规范化。样式是由代码模板统一控制的颜色、字体、线条、间距都是预设好的。你不需要每次画图都重新调整一遍视觉细节也不会有每个人画出来风格都不一样的问题。这就像从手工绘图进入到了组件化设计图的设计系统是集中定义的。1.3 不是所有图都适合代码化先搞清楚边界我也得泼一盆冷水。diagram-design不是万能的有些图用代码画反而很痛苦。适合代码化的图有这些系统架构图、业务流程图、时序图、ER图、状态机图。它们的共同特征是结构性强节点和关系清晰逻辑比视觉更重要文本描述基本能覆盖所有信息。不太适合代码化的图也有不少。比如UI高保真原型图那种图的价值就在视觉细节上用代码写出来的效率和精度都远远不够。再比如营销宣传用的信息图讲究排版艺术和视觉冲击力代码很难做出这种自由度。还有那种需要大量自由手绘注释的示意图也不适合——代码化给你不了一定范围内的自由反而会限制你。我的建议是diagram-design覆盖你团队80%的技术图表需求就足够了剩下那20%该用专业工具还是用专业工具。别为了统一而统一工具只是手段。2. 主流代码化图表工具的横向实测不是选最强的而是选嵌入成本最低的2.1 四款工具的参数对比与定位分析我这次调研了四款主流工具Mermaid、PlantUML、Graphviz、D2。它们的底层思路相似都是文本进图出但各有侧重点实际用下来差异很大。工具语法复杂度布局引擎学习曲线生态与集成适合场景Mermaid低内置自动布局较弱平缓几小时上手Markdown自带支持平台插件多流程图、时序图、架构图、简单甘特图PlantUML低到中内置布局一般依赖Graphviz平缓Java生态成熟文档工具集成多时序图、UML各类图、部署图Graphviz中布局引擎非常强较陡峭控制项多老牌稳定被广泛嵌入复杂层次图、状态机、大型关系图D2中布局引擎较强且现代中语法比Mermaid略严格较新生态正在增长工程化程度高的架构图、复杂的可视化从表格能看出Mermaid和PlantUML的定位是快速记录——语法足够简单用户能很快画出一张能看的图。Graphviz的杀手锏是布局算法节点多了以后它能自动整理出相对整洁的层级关系这是其他工具很难比的。D2则是后起之秀在工程化意识上很强支持变量、主题、布局方向的精细控制但目前社区生态还比不上前三个。2.2 我做选择时真正看重的东西我先说结论我最终选了Mermaid作为主力工具但Graphviz作为补充方案保留。为什么不是Graphviz它的布局能力确实最强但语法相对复杂团队成员的接受成本高。我们的团队里不是所有人都喜欢抠布局参数很多人要的只是快速画一张能表达清楚意思的图。Graphviz的dot语法里节点形状、边方向、rank约束、cluster分组这些概念对新手来说有一道门槛而且它的渲染风格偏技术感不太适合直接放进给业务方看的文档里。为什么不是D2它的设计理念我很喜欢尤其是主题变量、多文件引用这些现代特性做大型架构图非常有用。但问题在于生态我们文档系统是基于Markdown的各平台的Markdown预览对D2的支持还比较弱团队协作时其他人没有安装D2的CLI工具打开文档就看不到图。这个协作成本会抵消掉工具本身的优势。Mermaid胜在嵌入成本最低。几乎所有Markdown渲染器都原生支持MermaidGitHub能渲染、各大笔记软件能渲染、文档站点生成工具也能渲染。团队成员不需要安装任何额外工具只要打开文档就能看到图。上手成本也低语法非常接近自然语言基本上看几个示例就能开始写。对于大多数技术文档场景它的布局能力够用偶尔布局乱了也能通过调整写代码的方式绕过去。2.3 工具切换不是零成本算清楚这笔账再动手可能有读者要问了既然Mermaid的布局能力不如Graphviz为什么不两个都用这个问题的答案让我花了不少时间才想明白。切换工具是有隐性成本的。一个20节点的架构图用Mermaid重写大概需要半小时用Graphviz可能需要一小时起步因为要理解dot的布局约束机制。但更重要的是维护成本——团队里不是每个人都能熟练使用两套工具文档里有Mermaid又有Graphviz等于要求每个协作者掌握两套语法体系。这个认知负担在团队规模放大后非常可观。所以我最后定了这样一个原则默认用Mermaid当图的节点数量超过40个并且层级关系特别复杂时才考虑Graphviz单独渲染并导出成图片放进文档。这样既保证了日常写作的顺畅又在极端场景保留了强布局能力作为后备。优先级是协作顺畅 渲染美观 布局强大。3. 一套可落地的diagram-design工作流目录、规范、渲染、自检3.1 目录结构图表文件不止是图更是文档资产代码化图表搬进项目之后首先碰到的问题就是文件放哪。很多人随手把.mmd文件和文档放在一起结果是图散落在各层目录没有人知道总共有多少张图、哪些图还在用、哪些图已经废弃了。我最后沉淀下来的目录结构是这样的docs/ ├── diagrams/ │ ├── overview/ # 总览级架构图 │ │ ├── system.mmd │ │ └── network.mmd │ ├── service/ # 服务级详图 │ │ ├── order-service.mmd │ │ └── user-service.mmd │ ├── sequence/ # 时序图 │ │ └── checkout-flow.mmd │ └── assets/ # 导出的SVG/PNG给不支持渲染的场合使用 │ ├── system.svg │ └── checkout-flow.png └── README.md这个结构有两个设计用意。第一按图表类型划分子目录而不是按团队组织划分子目录。因为图表类型决定了它的更新频率和生命周期overview级别的架构图更新频率低而时序图可能每周都在变分开放能避免不同节奏的文件互相干扰。第二assets目录专门存放导出的静态图片这些图片是给不支持Mermaid的场合用的比如某些外部系统或者打印文档。静态图片必须由脚本从源文件生成不允许手动画完放进去否则就违背了单一事实来源的原则。3.2 统一样式规范让所有图的观感像出自一个人之手代码化图表的优势之一是样式可控但前提是你真的去控制它。Mermaid默认主题其实偏花哨节点颜色五花八门直接放进正式文档里显得不够专业。我用themeVariables统一调整了一套适合团队风格的参数。以流程图为例我通常在文件头部固定这一段配置%%{init: {theme: base, themeVariables: { primaryColor: #E8F0FE, primaryBorderColor: #4285F4, primaryTextColor: #1A1A1A, lineColor: #5F6368, fontSize: 14px, fontFamily: Arial, Microsoft YaHei, sans-serif }}}%%这套配置的作用是节点背景用浅蓝色边框用主蓝色文字颜色是深灰线条用中灰色字体统一为适合中英文混排的字体栈。所有图表都共用一个init模板保证任何一张图拿出来视觉风格都是一致的。可能有人觉得这是形式主义。但实际测试下来评审会上图表的观感直接影响别人对内容的信任度。一张配色混乱、字体大小不一的图第一印象就是这个系统不太靠谱。规范统一之后团队的新人照着模板画一张图至少观感上不会拖后腿。3.3 渲染与自检脚本没有校验的代码化图表会退化代码化图表如果只停留在能渲染层面时间一长也会出问题。最常见的状况是有人改了.mmd文件但语法有误文档里的图直接渲染失败留下一堆报错信息。或者是改了图的代码却忘了同步更新文档里引用的PNG文档就出现了图文不一致。我在项目中做了一个非常简单的自检脚本每次提交前跑一遍包含这几项检查语法检查。用mermaid-cli逐文件解析能成功生成SVG的文件才算通过。图片同步检查。遍历所有引用SVG/PNG的文档对比源图和assets目录里文件的修改时间源文件更新了就提示需要重新导出。链接指向检查。检查图里出现在方括号中的节点名是否都有定义防止有人把节点名写错还看不出来。这个脚本不复杂我用的核心命令也就一句话mmdc -i docs/diagrams/overview/system.mmd -o docs/diagrams/assets/system.svg -b white -w 1600-b white指定背景色是白色避免导出图带透明背景在部分场合显示异常-w 1600指定导出宽度是1600像素保证在Retina屏上不模糊。这套自检在多人协作项目里的价值非常大它把图能不能用这个主观问题变成了脚本能不能过的客观标准。3.4 一个完整范例从零画一张系统架构图为了演示整个工作流的实际手感我用Mermaid画一个简单的电商系统架构图。看到代码你就明白这类图的本质就是描述有哪些系统、系统之间怎么连接。flowchart LR subgraph Client[客户端层] UI[Web端] APP[移动端] end subgraph Gateway[接入层] Nginx[Nginx网关] end subgraph Service[服务层] Order[订单服务] User[用户服务] Pay[支付服务] end subgraph Data[数据层] MySQL[订单数据库] Redis((缓存集群)) MQ[消息队列] end UI -- Nginx APP -- Nginx Nginx -- Order Nginx -- User Order -- Pay Order -- MySQL Order -- Redis Order -- MQ User -- MySQL这里有几个细节值得说一下。flowchart LR声明了这是从左到右的流程图LR是布局方向Start是left到right还有TB从上到下、RL、BT等方向可选。技术架构图一般用LR因为系统分层关系是左到右更自然业务流程时序描述则常用TB方便表达从上到下的执行步骤。subgraph用于分组把同一层级的系统放在一个虚线框里视觉上形成分层的效果。节点名字可以写字面标识然后在中括号里写展示文案比如Order[订单服务]这是Mermaid的语法技巧内部名和显示名分离避免显示名里出现空格或特殊字符时报错。((缓存集群))这种双圆括号表示圆形节点一般用来代表数据库、缓存这类基础组件和普通服务节点的矩形形状区分开。形状也是一种语义看的人不需要读文字就能抓住这里是一个存储组件。4. 把diagram-design真正嵌进团队的日常协作机制4.1 图和代码作为一个整体进行版本管理工具选好、目录理清只是项目层面的准备工作。真正让diagram-design发挥价值的关键是它和代码一样走版本管理的完整生命周期。我的做法是把.mmd源文件和文档放在同一个Git仓库里。之前的做法是图片提交到文档仓库图是静态的改起来很麻烦。现在源文件在仓库里每次改动都伴随着一条commit记录比如优化用户服务的调用链描述这个图为什么改、谁改的、什么时候改的全部有迹可循。代码Review流程同样适用于图表。有人提交了一个架构图的修改评审人看Diff时能清楚地看到原来订单服务直接连数据库现在中间加了一个消息队列这个改动是否合理一目了然。如果是一个PNG评审人是无法看出这种结构性变化的关键信息的。这一点在我看来是diagram-design相对于传统画图工具最大的碾压性优势。4.2 CI流水线里的自动渲染与一致性校验提交到Git之后我建议在CI流水线里加一步图表渲染任务。步骤很简单拉代码执行mermaid-cli渲染所有.mmd到assets目录然后比对生成文件与仓库中已有文件是否有差异。如果有差异说明有人改了图源但忘记同步导出静态图片了CI直接失败提醒提交者处理。这个流程的关键在于让渲染成为每次代码变更的默认动作而不是靠人记得手动导出。我经历过太多次因为忘了导出图片导致文档链接失效的情况有了CI兜底之后这个低级错误基本不再出现了。还有一步是可选的但很有价值在CI里执行一个语法校验脚本检查Mermaid源码中的节点引用是否完整有没有拼写错误。这个脚本可以拿到所有图表文件里定义的节点名再扫描引用这些节点的连线把不一致的列举出来。我第一次跑这个脚本果然在项目里抓出了两处服务名拼写错误而且这两处错误已经在文档里存在两个月了一直没有人发现。4.3 定义团队约定节点命名、颜色语义、注释语言代码化图表工程化的最后一块拼图是约定。没有约定的文本依然会变成一锅粥。我推动团队在文档规范里加了几条硬性约定这里挑几条有代表性的分享。节点命名必须使用领域术语不允许用自定义简称。比如订单服务就是order-service不允许写订单svc或者OS。这样做的原因是图要被反复Diff和Review统一命名是Diff有意义的前提。颜色语义全局统一。红色表示异常链路或待下线模块蓝色表示正常的同步调用绿色表示异步消息传递灰色表示预留但未上线的部分。这个语义要写进团队的文档协作规范里新人画图前先读这一节。如果没有全局统一每个人用红色表达的含义都可能不同图的认知成本就会飙升。图内的注释要求写中文。我们团队有一个阶段出现过中英混注的情况有人用英文注释有人用中文注释看起来非常割裂。定下所有注释用中文这条约定之后图的阅读门槛大幅降低。代码里为了国际化用英文注释没问题但技术图是给人看的不是给编译器处理的清晰是第一优先级。这些约定看起来琐碎但它们的价值是让团队里的图有相同的语言环境。图不只是一张图它本质上是团队对系统结构的共同理解这个理解必须建立在统一的表达规则之上。5. 实操中的意外状况与排查思路那几个让我挠头的坑5.1 升级mermaid-cli之后图突然全都渲染不出来了某次版本升级后CI流水线里的渲染任务突然大面积报错错误信息指向某个字体相关依赖无法加载。第一反应是环境问题但重新安装依赖之后依然报错这就排除了安装不完整的情况。我开始翻变更记录发现升级是主因新版本mermaid-cli调整了Chromium沙箱的启动参数在容器环境下默认的沙箱模式不可用。定位到这个方向后解决方案是在调用命令时会增加一个参数禁用沙箱。这个坑的典型特征是根据报错信息去搜索往往会得到很多繁杂的结果但如果跟着升级时间点和容器环境这两个线索排查路径其实非常清晰。这个经历的启发是代码化图表工具链和普通前端工具链一样要当作正经依赖来锁定版本。不要用最新版这种模糊的版本约束要在配置里锁死具体版本号。尤其是在团队协作中在我电脑上能跑是最糟糕的答案锁版本才能保证每个人拿到的是同一个行为。5.2 中文字体带来的渲染错位和内容截断Mermaid默认字体在某些Linux服务器上不包含中文字形渲染出的图里中文直接变成方块或者错位。这个问题在本地Mac上根本不会暴露因为本机自带中文字体但一上CI就现原形。解决方式是两种。第一种是给mermaid-cli指定系统字体在服务器上安装中文字体包字体问题会迎刃而解。第二种是在文档里引用在线字体但这依赖外网访问内网环境不适用。我给项目的建议是把中文字体文件直接放到项目的资源目录里并在mermaid-cli的配置文件中指定字体路径这样CI环境不依赖系统字体库依然能渲染出正确的结果。这个坑提醒我一个道理图表是视觉产物它的正确性不仅取决于逻辑结构是否正确还取决于渲染环境是否完整。任何一套代码化方案都要把跨环境渲染一致性当作硬性验收标准来对待。5.3 节点一多布局就乱硬调参数是最差的解法有一张整体架构图涉及了三十几个服务我用Mermaid画完第一版线的交叉之多简直惨不忍睹。我尝试硬调graph TD还是graph LR的布局方向效果不大。后来我开始给相关节点加subgraph分组让同层级的系统先聚合成组组与组之间的连线数量自然就下降了。第二版明显改善但还谈不上整洁。最终能用的版本其实是我拆分图之后得到的——我把那张巨型架构图拆成了三张一张整体分层骨架图、一张核心链路详图、一张部署环境拓补图。三张图各司其职各自控制在20个节点以内渲染出来的效果都干净许多。这个经验我想单独强调一下当一张图开始需要花大量时间去调布局的时候很可能不是布局参数的问题而是这张图的粒度太大了。人的短时记忆容量有限一张图表达的信息超载无论布局引擎怎么优化阅读体验都不会好。正确的做法永远是拆分而不是硬调。5.4 箭头方向画反了方向是阅读者习惯的延伸有一次给业务方评审支付时序图对方盯着图看了半天说了一句这个支付成功回调的方向反了吧。我当即意识到问题出在我画箭头时下意识采用了代码执行视角——服务端经验让我习惯从后端视角画调用链但业务方阅读时序图时用的是时间顺序视角。对他们来说用户先做了什么、系统后做了什么方向应该是从上到下的自然时间流。我原图里的一部分箭头确实画得违反了他们的阅读预期。方向这个问题本质上不是语法问题而是图终归要服务于读者的问题。从那以后我在画任何一张图之前都会先问一句这张图的第一读者是谁如果是给开发同事看的技术结构图可以采用组件调用视角如果是给业务产品或测试同学看的流程图一定要用时间顺序视角。这个习惯让我避免了大量在评审会上被现场指正的尴尬。6. 从diagram-design里沉淀出来的几点长期体会这几个月实践下来我最深的一个体会是画图的本质是建模不是描形。以前用拖拽工具画架构图眼睛盯的是节点位置、线条颜色、间距对齐。现在写代码式图表被迫把系统拆解成实体和关系这一步逼着你把模糊的认知结构化画图的过程本身就是一次系统梳理。很多时候图画完了我才发现自己对某个模块的理解其实是有漏洞的——图把这些漏洞暴露出来了。还有一点别把图表代码化的价值局限在省事这个层面。它真正的价值是让图变成一种可以迭代、可以评审、可以讨论的资产而不是一次性交付的展板。图的演进过程如实地记录在Git历史里团队的架构决策因此有了可回溯的依据。最后说一个个人建议无论你选什么工具都先小范围试点拿一个真实业务场景练手跑通整条工作流之后再做团队推广。工具本身的上手难度不是最大的难点最大的难点在于团队有没有把图是一等文档这个观念建立起来。只要观念到位了Mermaid也好、PlantUML也好甚至D2和Graphviz都行你都能做出清晰漂亮的图表来。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门