ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

代码化图表管理:从拖拽绘图到Git版本控制的架构图实践

2026/9/8 13:02:44 拓冰建站 浏览量
代码化图表管理:从拖拽绘图到Git版本控制的架构图实践 1. diagram-design 的起点为什么我决定把所有图表从拖改成写这个项目的起因非常朴素我负责维护一套内部系统的技术文档里面光架构图就有三十多张分布在各个 Wiki 页面、Git 仓库和本地文件里。每次系统一升级、服务一变就得打开绘图工具手动挪框、改线、重新导出。最崩溃的一次我在评审会上用旧图讲新架构被后端同事当场指出这个服务已经拆成两个了图里还是三个挤在一起的老模块。那一刻我终于意识到图表不是画出来的是维护出来的而传统画布工具在这件事上几乎帮不上忙。所以我启动了 diagram-design 这个项目。它的核心思路很简单把图表当作代码来管理用文本描述节点和关系用构建脚本统一渲染成 PNG/SVG 图片让每张图都有 Git 历史、可 diff、可 review、可自动生成。项目上线之后我们团队的架构图更新频率从季度维护一次变成了每次变更随代码走评审歧义大幅减少。这篇文章我会从头复盘整个项目包括工具选型、目录设计、渲染流水线、构图原则以及大量踩坑记录给同样被图表维护折磨的人一个可复现的参考方案。1.1 拖拽式绘图工具的三大痛点先说说为什么 Non-代码 画图方案让我下决心逃离。Visio、draw.io、ProcessOn 这些工具本身并不差但在一个以 Git 为核心协作方式的开发团队里它们的三个短板会被无限放大。第一是变更不可追溯。一张架构图导出的 PNG改过哪根线、哪个框完全没有记录。几个月后再看这张图谁都不知道它和当前代码是否还是一致的。我经历过不止一次图里画了 A 模块代码里已经改成 B 模块的尴尬。第二是评审成本高。代码有 Code Review图没有。团队里任何一个人拖一下框、换一下颜色其他人都没法在合并前直观看到差异。第三是审美不统一。有人喜欢蓝色系有人喜欢灰色系有人喜欢把节点排得密密麻麻有人喜欢留白。最后产出的视觉风格完全取决于谁最后一次打开过文件。这三点互相叠加导致图表从资产变成了负债。一旦接手旧系统最怕的不是代码烂而是文档里的图不可信任。1.2 代码化图表解决的核心问题diagram-design 选择把所有图表的源文件用文本格式保存本质上是在解决可追溯和可评审这两个根问题。文本格式天然适合版本控制。每一条边的增删、每一个节点的改名在 diff 页面里都是一行一行的变更任何人都能快速判断这次改动的影响范围。更重要的是代码化让图表随代码变更有了执行基础服务拆分时顺手改一下对应的图文件新增依赖时画一条箭头就是一行文本。这比打开画布工具里重新排版要轻得多所以团队坚持下来的概率大幅度提升。另一个容易被忽略的收益是图表可以参与自动化流程。diagram-design 里我写了一个构建脚本统一扫描源文件、渲染图片、检查孤立节点、校验连线方向。这意味着架构图不再是一张静态图片而是一条持续集成流水线里的产物。提交图表源文件后CI 自动生成最新图片、提示冲突和错误这体验和写代码几乎没有区别。1.3 明确边界不是所有图都适合写任何方案都有适用边界。diagram-design 立项时我把值得代码化和不要代码化的图做了严格区分这个判断比选工具更重要。适合代码化的是结构化、逻辑化、需要持续更新的图比如系统架构图、流程图、时序图、状态机、网络拓扑。这些图的核心信息是节点之间的语义关系位置的绝对坐标反而不重要交给布局引擎自动排列即可。不适合代码化的是那些强依赖视觉细节的图比如 UI 线框图、海报式的产品路线图、手绘风格的示意图。这些图的价值很大程度在于视觉表现力——留白、对齐、透视、手绘涂鸦用文本描述会非常痛苦写出来的代码比拖拽还慢。我们的原则是需要精确定位、有大量自由曲线的图保留画布工具需要表达关系、需要跟随代码演进的图一律进入 diagram-design。2. 代码绘图工具选型实录Mermaid、Graphviz、PlantUML、D2 怎么选项目第一步是选型。市面上成熟方案不少我用排除法过了一圈。这里直接说我的横向对比结果给后来者省点时间。2.1 Mermaid适合嵌入文档的轻量方案Mermaid 是很多人的第一选择因为它语法极为简单学习成本几乎为零。画流程图基本是graph LR开头节点之间用--连接几分钟就能写出一个关系图。它最大的优势是能在 Markdown 文档中渲染GitHub、各种 Wiki、笔记软件都原生支持不需要额外构建。但在我实际试跑 diagram-design 的第一个版本时它暴露了两个让我难受的问题。第一是复杂布局的可控性弱节点一多自动布局经常把关联密集的模块挤到一起很难通过代码去强制调整某几个节点的相对位置。第二是样式定制能力有限我想统一团队图表的配色、圆角、边框Mermaid 配置项不够细最终产物的视觉风格比较模板化。我的结论是Mermaid 非常适合文档里顺便画一张图的场景但对于一个需要统一品牌、统一规范、统一评审的图表资产库来说它的上限偏低。2.2 Graphviz自动布局和高自由度并存的严肃选项Graphviz 是这个项目最终确认的核心引擎之一它也是很多对图表质量有要求的项目首选。Graphviz 的核心是 dot 语言描述节点和边然后用布局引擎自动计算位置。和多数人的直觉相反Graphviz 虽然看起来老旧但它提供了好几个布局引擎其中dot适合层级图neato适合无向图fdp适合力导向图circo适合环形图。这意味着我可以为不同语义选择不同布局而不是所有图都用同一套排法。它的自由度也非常高。从节点形状、颜色、字体、边框粗细到边的颜色、线型、方向、权重几乎每个视觉要素都能通过属性控制这正好满足 diagram-design 对统一主题的要求。Graphviz 的布局算法虽然有时候直来直去不太花哨但它稳定、可预测适合大量图表批量生成。代价是语法没有那么好读尤其是指定位置和约束时需要花一些时间调优。下面是我在项目里最常用的一段 dot 代码骨架用来画一个三层服务架构digraph service_arch { rankdirTB; node [shapebox, stylerounded,filled, fontnameHelvetica, color#4A90D9, fillcolor#EAF2FB]; edge [color#666666, arrowheadnormal]; subgraph cluster_client { label客户端层; stylerounded,dashed; Web [labelWeb端]; App [label移动端]; } subgraph cluster_service { label服务层; stylerounded,dashed; Gateway [labelAPI Gateway]; Order [label订单服务]; User [label用户服务]; } subgraph cluster_data { label数据层; stylerounded,dashed; DB [labelMySQL, shapecylinder]; Redis [labelRedis, shapecylinder]; } Web - Gateway; App - Gateway; Gateway - Order; Gateway - User; Order - DB; User - DB; Order - Redis; User - Redis; }这段代码渲染出来就是一张层次清晰的架构图。rankdirTB表示从上到下布局cluster用来画分组框shapecylinder表示数据库。最关键的是所有样式集中在 node 和 edge 的默认属性里后续换主题只改几行。2.3 PlantUML面向团队协作的 UML 全家桶PlantUML 在 UML 图上是最省力的选择。时序图、用例图、类图、活动图都有专门的语法而 Graphviz 画这些需要花更多精力去抽象。比如时序图里 Participant 之间的箭头、激活条、注释PlantUML 几乎是一行一个 Participant可读性很强。如果你所在团队主要需求是各种 UML 图PlantUML 的体验远好于拿 Graphviz 硬写。diagram-design 里我最终没有全面采用 PlantUML原因有两个。一是它的安装依赖 Java 环境在部分纯前端构建场景下稍微重一点二是它的高级布局定制依旧不够直觉复杂的类图关系多了以后自动排版会很草率。但它依然是我推荐团队必备的工具之一尤其是文档服务器上部署好 PlantUML 服务端后按需生成很方便。2.4 D2 与 Excalidraw新锐工具和自由画布的互补选型过程中我还试了近年热度很高的 D2。D2 的设计哲学确实是给现代开发者一个更好看的 Graphviz语法简洁、默认样式现代、支持深色主题文档也很规范。我在一个新项目的网络拓扑图里试用了 D2视觉输出让我惊艳。唯一让我谨慎的是它的生态相对年轻复杂场景下能参考的社区案例比 Graphviz 少很多团队沉淀成本更高。Excalidraw 走的路径完全相反它追求的是手绘感、自由度和协作即时性。它的文件格式虽然是 JSON也可以放在 Git 里但本质上还是一个画布工具。我把 Excalidraw 定位为画草图的地方先用它快速构思布局和关系确认无误后再转换成语义清晰的 dot 或 D2 源码放进 diagram-design。这一步看起来多余实际非常高效。2.5 我的选型结论最终 diagram-design 的默认技术栈是Graphviz 处理架构图、流程图和拓扑图PlantUML 处理 UML 类图和时序图D2 用于需要高视觉质量的新图表Mermaid 保留在文档内嵌场景。整个项目以 Graphviz 为主引擎因为它的稳定性、可编程性和样式控制能力最符合图表资产库这个定位。场景推荐工具理由Markdown 文档内嵌流程图Mermaid学习成本低渲染快运行平台多架构图、流程图、拓扑图Graphviz布局引擎丰富样式可控适合自动化UML 类图、时序图PlantUML语义直观团队协作友好高颜值新图表D2默认样式现代语法易读快速构思草图Excalidraw手绘感强即时协作3. diagram-design 项目结构从零搭建图表代码资产库工具确定后我花了一天时间设计整个项目的目录、命令和规范。这一节讲目录结构和渲染流水线这是所有代码化图表项目的地基。3.1 目录设计源码、模板、产物三级分离diagram-design 的目录遵循三个原则源码和产物严格隔离、公共样式集中管理、图表之间不互相依赖。避免了都放在一个目录里跑完构建后分不清谁是谁的混乱。diagram-design/ ├── src/ │ ├── architecture/ │ │ ├── service-overview.dot │ │ ├── deployment.dot │ │ └── network-topology.d2 │ ├── sequence/ │ │ ├── order-flow.puml │ │ └── payment-flow.puml │ └── docs/ │ └── ... ├── themes/ │ ├── light.gvstyle │ └── dark.gvstyle ├── scripts/ │ ├── render.py │ └── check.py ├── dist/ │ ├── architecture/ │ ├── sequence/ │ └── ... ├── Makefile └── README.mdsrc存放所有图表源文件按类型分子目录themes存放公共样式主题避免每个文件重复写一堆颜色scripts放渲染和检查脚本dist是构建产物由脚本自动生成不进入手写流程。Makefile暴露常用命令让不熟悉脚本细节的人也能一键运行。3.2 渲染流水线一条命令重生成所有图表渲染流水线是整个项目体验的关键。我写了一个render.py它会递归扫描src下的所有源文件根据扩展名分配合适的渲染工具并把生成图片输出到dist对应的目录。核心思路是把渲染逻辑集中到一个脚本里而不是让每个人在自己的电脑上手动装插件。下面是我简化后的渲染脚本核心部分import subprocess from pathlib import Path def render(source: Path, output: Path): ext source.suffix output.parent.mkdir(parentsTrue, exist_okTrue) if ext .dot: subprocess.run([dot, -Tsvg, str(source), -o, str(output)], checkTrue) elif ext .d2: subprocess.run([d2, str(source), str(output.with_suffix(.svg))], checkTrue) elif ext .puml: subprocess.run([plantuml, -tsvg, str(source), -o, str(output.parent)], checkTrue) def main(root: Path): for src in (root / src).rglob(*): if src.suffix not in {.dot, .d2, .puml}: continue relative src.relative_to(root / src) out root / dist / relative.with_suffix(.svg) render(src, out) print(frendered: {relative})这段脚本不复杂但它把团队从必须安装某个桌面工具才能改图的束缚里解放出来。任何人只要拉下仓库、运行make render就能得到最新图片。配合持续集成后每次源文件变更都会在合并前自动更新产物避免出现代码更新了但图还留在本地没导出的情况。3.3 基于 Git 的图表评审流程图表进入 Git 后最大的好处是能走和代码一样的评审流程。我在团队里推了一套简单的规范任何涉及架构的变更必须同时提交对应的.dot或.puml文件评审人在合并请求里查看图表源码 diff而不是只看最终 PNG。这个流程早期遇到了一个阻力很多同事说我看 dot 源码看不出布局效果还是得本地渲染一遍。为此我写了一个check.py它不仅能渲染图片还会解析源文件里的节点和边对比 Git 上一次版本输出一个文本版的图表变更摘要。比如新增节点: 支付服务 删除节点: 支付中台 新增边: 订单服务 - 支付服务 删除边: 订单服务 - 支付中台这段摘要让评审人不需要预测渲染结果也能快速判断变更是否符合预期。这是我个人非常推荐的一个细节代码化图表不是终点可读性才是。4. 图表设计原则让一张架构图不只是画得出来项目初期我们产出的图虽然能渲染但观感非常业余。后来我总结了几条设计原则全部沉淀在了团队文档里。这些原则和用什么工具无关是通用的。4.1 先定义语义再决定形状画任何一张图之前先回答三个问题这张图要说明什么关系谁是读者希望读者第一眼看到什么判断一组节点是上下级还是依赖关系是调用链还是数据流会决定你用什么布局而不是画完再靠位置硬凑。比如画微服务调用关系用有向图画组织结构可以用树形布局画系统依赖可以考虑力导向布局。diagram-design 的推荐做法是在源文件顶部写注释标注这张图的中心语义和目标读者例如// 语义订单创建链路中各服务的调用关系 // 读者后端开发、运维 // 关键信息订单服务是链路起点支付服务是核心依赖这段注释会让后来维护图的人少猜很多心思。4.2 布局方向决定阅读顺序很多人画图不关心方向觉得能看明白就行但这个想法大错特错。人类阅读图表默认存在顺序从上到下是从宏观到微观的分解从左到右是时间或调用顺序。如果一张图一会儿从左往右一会儿从下往上阅读体验会非常割裂。Graphviz 里用rankdir控制整体方向TB适合层级架构LR适合调用链和时间线。我一般约定架构图统一TB数据流/时序图统一LR。这种一致性让团队里所有图都建立在同一个阅读习惯上新成员上手快跨图对比也容易。4.3 颜色只承担一种约定我见过很多图表用四五种颜色分别表示无关紧要的分类最后读者根本记不住颜色含义。diagram-design 里我立了一个规矩颜色必须承担明确的语义区分且全项目统一。比如蓝色表示核心服务灰色表示基础设施橙色表示外部系统红色只在故障或风险节点用。这个规范通过主题文件统一实现。在 Graphviz 里可以把公共属性放在一个.gvstyle文件里集中管理修改一次所有图都能生效。颜色数量控制在三种主色加一种强调色以内超过这个范围就要考虑是不是多画了一张图。4.4 文字是图表的一部分不是附属品节点标签、边标签、图例、标题每一个文字元素都要认真对待。节点标签必须和代码里的服务名、模块名保持一致不要用缩写或同义替换边长标签要精简到核心动作比如调用订阅读。字号也是一门学问。Graphviz 默认字号经常偏小导出 PNG 后放到 PPT 里很容易看不清。我在主题里统一设置fontsize14作为最小值标题和图例使用 16 到 18。另外id 尽量用英文保持可读性label 用中文或团队约定语言避免代码文件里出现编码问题。4.5 图例不是凑数的它决定图的可维护边界图例很多人觉得不重要但它是图表自解释的关键。diagram-design 规定任何超过十种节点类型或三种以上关系的图必须带图例图例不解释颜色含义的图评审不能通过。Graphviz 中可以用一个带边框的 cluster 放置图例节点虽然代码会多几行但长期维护收益非常高。4.6 控制复杂度一张图的极限是 20 个节点这是我最想强调的一条。图表的目的是让复杂系统变简单不是把系统原样复刻。diagram-design 的实践发现一旦一张图超过 20 个节点它的可读性会断崖式下跌读者会陷在找节点里而不是理解关系。遇到大型系统正确的做法是拆分先画一张全景总图隐藏细节节点再按模块画局部详图用链接互相引用。总图里 10 个左右节点即可局部详图控制在 15 个节点以内。必要时用子图subgraph做逻辑分组但子图的总数也不要超过 5 个。5. 实战案例用 diagram-design 重构一张微服务架构图选型、结构、原则都定了之后我们需要一个真实的验证。这里用一张我重构过的微服务架构图作为完整示例展示从问题到代码到最终效果的全过程。5.1 原始图的问题清单这是一张我接手前就存在的架构图用画布工具画的分发方式是截图放在文档里。当时盘点出的问题有服务名称和代码仓库名不一致比如图中写用户中心代码仓库实际叫identity-service。缺少统一方向部分组件从左到右排列数据库又从下往上画阅读顺序混乱。颜色混乱六个服务用了八种颜色标注毫无规律。没有标注外部系统和内部服务读者不知道边界在哪。图中没有图例关键符号只靠画的人自己懂。版本信息缺失无法判断这图是哪个时期的状态。这类问题在长期无人维护的架构图里非常典型。我应该不需要再多解释了只要是接手过旧系统的人都会心一笑。5.2 用 dot 代码重建架构图在 diagram-design 里我按照前面说的设计原则把这张图用 dot 语言重写。图结构分四层客户端、接入层、业务服务、数据存储整体使用TB方向内部服务和外部系统通过颜色区分数据存储统一用圆柱体形状。下面是核心代码digraph microservices { rankdirTB; node [fontnameHelvetica, fontsize14, stylerounded,filled, color#4A90D9, fillcolor#EAF2FB]; edge [color#666666, arrowheadnormal, fontsize12]; subgraph cluster_client { label客户端; stylerounded,dashed; Web [labelWeb 端]; App [label移动端]; } subgraph cluster_gateway { label接入层; stylerounded,dashed; Gateway [labelAPI Gateway , shapebox]; } subgraph cluster_services { label业务服务; stylerounded,dashed; Identity [labelidentity-service]; Order [labelorder-service]; Payment [labelpayment-service]; Notification [labelnotification-service]; } subgraph cluster_data { label数据存储; stylerounded,dashed; MySQL [labelMySQL, shapecylinder, color#7B8D9A, fillcolor#F5F7FA]; Redis [labelRedis, shapecylinder, color#7B8D9A, fillcolor#F5F7FA]; } External [label外部支付渠道, shapebox, color#E6A23C, fillcolor#FDF6E3]; Web - Gateway; App - Gateway; Gateway - Identity; Gateway - Order; Gateway - Payment; Gateway - Notification; Order - Identity; Order - Payment; Payment - External; Payment - MySQL; Order - MySQL; Order - Redis; Identity - MySQL; Notification - Redis; }重写的过程遵循了几条原则所有节点标签改成真实的服务名外部系统单独用橙色系和内部服务区分数据库用圆柱体依赖关系只画核心调用不画琐碎的配置依赖。从视觉上读者一眼就能确认入口是 Gateway出口是外部支付渠道数据流方向非常明确。5.3 校验、评审与量化对比图写好后我跑了check.py检查孤立节点和边的方向。这个脚本之前救了不止一次有一次我发现一个服务在代码里已经下线但图里还孤零零挂着因为染色属性的节点没有连接到任何边检查脚本直接报 WARNING。这种错误靠肉眼看渲染图很难发现但脚本可以秒级定位。重构后的图和旧图对比最明显的变化有三个一是节点数从 17 个精简到 13 个把不重要的内部组件收进了子图二是阅读顺序统一从上到下依次是客户端、接入层、业务服务、数据存储三是颜色语义统一蓝灰是内部服务橙色是外部系统红色警示信息被删除。评审时团队只花了 12 分钟就达成一致而旧图第一次评审时半小时过去了大家还在争论边界画在哪。6. 踩坑记录渲染失败之外的隐形问题代码化图表项目最折磨人的不是写图而是各种照说不该有问题的细节。我把高频踩的坑整理在这里给同路人节省几个晚上。6.1 中文乱码与字体选择Graphviz 和 PlantUML 在中文环境下最经典的问题就是乱码或者渲染出来是全方块。很多人第一反应是编码没设对但实际根源是系统缺少对应字体的 fontconfig 配置。我的解决方法是三步首先确认源文件使用 UTF-8 编码其次在主题里显式指定中文字体名比如fontnamePingFang SC或fontnameMicrosoft YaHei最后检查系统有没有安装对应字体文件。在 Linux 构建机上如果缺字体可以直接用fc-list查看可用字体列表再把可用字体的名字写进主题。这一步看着基础但几乎所有第一次跑 CI 渲染的人都栽在上面。6.2 特殊字符与换行的隐藏炸弹dot 语法里节点标签中的引号、反斜杠、HTML 尖括号在特定情况下都会引发解析异常。最坑的是换行直接用\n在 label 里换行不一定生效在 Graphviz 里往往要用\n转义但如果 label 使用了 HTML 标签模式就要换成BR/。我建议尽量不用 HTML label除非确实需要表格化节点。如果必须换行先在小图上试渲染再批量应用。D2 则对引号和反斜杠的处理更严格有时一个#注释符号出现在字符串里也会被误判。我的经验是所有用户输入的文本都做一遍转义测试不要相信明明在编辑器里看是好的。6.3 自动布局好看但不合理的陷阱Graphviz 的优势是自动布局但它毕竟不知道你的业务优先级。默认布局经常会把核心服务放到边缘区域或者把依赖关系画得交叉而复杂。这时候需要手动干预rank约束可以让某些节点固定在同一层级weight属性可以让某条边优先走直连甚至可以用invisible边来推节点到期望方向。但我要泼一盆冷水不要在一个布局问题上死磕超过半小时。如果实在调不出理想效果可能不是布局参数的问题而是图本身结构太复杂需要拆分。这也是我反复强调 20 节点极限的原因——超过这个数字后所有布局引擎都很难同时兼顾美观和语义。6.4 大型图的性能与渲染超时当图里节点数超过五十个Graphviz 的处理时间会明显上升某些复杂布局甚至会卡顿数十秒。PlantUML 的类图如果关系和继承特别多也容易渲染超时。我采用的策略是分层渲染总图只渲染一级模块每个模块内部再单独画详图通过文件命名约定关联。比如service-overview.dot是总览图service-order.dot是订单域详图。这样单张图始终保持在红线之内渲染速度稳定review 时也能逐张理解。如果你明明可以并行渲染却还要串行请看一眼我的脚本改成基于并发遍历的批处理会快很多。7. 后续可以怎么扩展从架构图到整个文档体系的自动化diagram-design 做到这里已经稳定运行了大半年。最近我在做两件延伸的事分享出来供参考。第一件是把图表构建接入文档发布流水线让技术文档站点在每次构建时自动拉取dist里的最新 SVG替代原来的图片引用链接。这样文档里展示的架构图和代码永远保持同一版本不存在文章更新了图没更新的割裂。第二件是用脚本自动生成图与代码的一致性检查比如通过解析 dot 文件里的服务名去扫描 Kubernetes 部署清单或服务注册中心里的真实服务列表一旦发现图里有不存在的服务就报 ERROR 阻断发布。这等于把图表从事后记录升级成了事前约束价值提升非常明显。在实际操作中最让我欣慰的不是省了多少手工时间而是团队开始主动在改代码的时候打开对应图表文档顺手更新节点关系。图表从一个每次评审都需要解释的附属品变成了真正能被所有人信任的沟通工具。如果你也要启动类似项目记住一件事工具只是起点真正的门槛在于你愿意为图表建立多少规则以及能否让这些规则通过自动化落地。diagram-design 源码和目录结构全部开源放在仓库里有需要的直接拿去改就行。