news 2026/10/21 18:44:32

用D2声明式图表打造团队统一的代码化架构图方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用D2声明式图表打造团队统一的代码化架构图方案

上周刚把团队里最后一张用拖拽工具画出来的部署图替换掉,换成了统一生成的版本。发到群里之后,有个同事回了一句:“这几张图看着像同一个人画的,舒服。”这句话让我挺有成就感,因为折腾了大半个星期的项目总算有了回报。这个项目的代号就叫diagram-design,核心目标不是画某一张具体的图,而是搭建一套让团队所有架构图、流程图、时序图都能保持一致风格、方便维护、能放进代码仓库的图表设计与渲染方案。

如果你也遇到过这些场景:画架构图时每个人配色都不一样、改一个模块名要手动打开几十张图逐个修改、评审时大家盯着图却争论“这个框是不是该靠左一点”……那这篇文章应该能给你一些启发。我会从为什么选择“代码化”的图表方案讲起,把设计图表时需要想清楚的底层要素拆开,再分享一套可以照抄的模板库实现,最后梳理这几个月踩过的坑。适合正在做技术文档规范化、想用声明式工具管理图表的开发者,也适合那些画图永远被吐槽“风格乱”的团队参考。

1. 别急着画图:先想清楚为什么用代码写图

1.1 拖拽式绘图工具的“变更地狱”

以前团队用的都是常见拖拽式绘图工具,画图确实快,框一拉、线一拖就完事,但它带来的维护成本会随着图片数量直线上升。

最痛的是版本管理。这类工具的源文件本质上是一大坨自定义格式的XML,两个版本之间的diff基本看不出来什么,只能看到“某个元素变了”这样的粗粒度信息。想回滚某一次改动,几乎只能重新操作。你没法像看代码提交记录那样,清楚地知道这张图为什么从三层变成了四层。

更麻烦的是批量修改。有一回我们做系统命名统一,要把所有图里的“用户服务”改成“会员服务”。我一个一个打开图,用工具自带的查找替换功能处理,每张图还要重新调整被换掉文字后可能出现的宽度、换行问题。当时团队里大概有四十多张架构图,我整整弄了两天。改完那两天我盯着屏幕都有点恍惚——这些图本来应该在代码仓库里被管理着,怎么变成我手工在维护了。

风格失控也是逃不掉的。每个人对颜色的理解不一样,有人觉得绿色代表正常,有人觉得绿色代表成功,有人觉得绿色就是随便用的。再加上块大小、线型、字体大小全凭个人习惯,最后汇总出来的图集就像一群人各写各的PPT模板拼在一起,放到跨部门评审时还得先解释一遍“这里颜色不同没有特殊含义,只是不同人画的”。

这些问题的根源在于:拖拽式工具把“画图”变成了纯手工操作,而团队需要的其实是一套可以被版本控制、批量修改、统一约束的“图表生产线”。

1.2 声明式图表语言:把布局交给引擎

既然手工画有这么多问题,那就换一条路:用代码描述图的结构和关系,让工具自动完成布局。这类方案统称“声明式图表语言”,现在基本已经成了技术文档领域的主流选择。

你可以把它理解成写HTML和CSS的关系。拖拽式画图像是在用旧版PS给人修图,每个像素都得手动控制;声明式写图则是先声明“这里有一个标题”“这里有一个列表”“这里有一个链接”,剩下的排版、对齐、间距都交给浏览器引擎去处理。你不用关心那个框在画布上精确的x、y坐标是多少,只需要描述谁和谁相连、谁在谁的容器里。

声明式图表的核心逻辑就是:结构是你定义的,布局是引擎算的。比如你写一个“用户服务 -> 订单服务 -> 数据库”,引擎会知道这三个节点之间存在依赖关系,它自己会合理安排上下层级、左右顺序、连线走向。如果你改变了节点之间的连接,重新渲染一次,整张图会自动重新布局,不需要手工把某个框拖到另一侧。

这意味着图表也是“源代码”,可以被diff、被重构、被自动化检查。任何一次结构变化都能在提交记录里清清楚楚地看到。团队里任何人拿到同一份源文件,渲染出来的结果一定是一样的,不会再出现“明明同一个架构,我电脑上和你电脑上长得不一样”的尴尬。

1.3 为什么最终选定了 D2

声明式图表方案其实有几条分支:有老牌的老牌DOT语言方案,有基于JS的渲染库方案,还有近两年比较流行的D2(全称Declarative Diagramming,声明式图表设计语言)。

我在选型时列过一张对比表,核心维度包括语法可读性、布局美观度、内置图形丰富程度、导出格式、以及团队模板化的方便程度:

对比维度D2老牌DOT语言方案基于JS的渲染库方案
语法可读性接近自然语言,a -> b很直观属性多,写复杂图需要记很多关键字依赖Markdown语法,简单图很舒适,复杂图需要额外配置
布局控制内置自动布局,嵌套容器和图层支持好布局能力强但默认效果偏图形学风格,偏“工程感”布局依赖浏览器渲染,输出静态图时效果受限
内置图形库有icon、云、浏览器、圆柱、队列等常用图形基础图形多,但设计感比较朴素图形库相对简单,扩展依赖插件
导出格式SVG、PNG、PDF等多种格式主要输出SVG和图片适合网页内嵌,离线导出需要额外工具链
模板化能力支持变量、主题、草图模式变量能力弱,要靠外部脚本需要配合代码调用实现模板化

最终定D2的原因很实际:它的语法足够简单,整个团队的学习成本低到可以忽略——下午花二十分钟看一下示例,晚上就能画出第一张能看的架构图。同时它支持变量和主题,这意味着我可以把“颜色”“字体”“间距”这些设计决策沉淀到公共文件里,所有人引用同一套变量,既保证了风格统一,又保留了局部修改的灵活性。

尤其是“草图模式”,渲染出来的图带有手绘风格,开评审会时大家对着这种图讨论,气氛明显更放松,不会觉得眼前是一份不可修改的“严肃交付物”。

2. 拆解一张好图的四个底层要素

画图工具选好了,不代表图就能画好。这段时间我最大的感受是:工具只能解决“怎么画”的问题,解决不了“画什么”“画得好不好”的问题。一张能被快速理解的图,背后其实就是四个底层要素。

2.1 先定容器,再画连线:信息层级是第一优先级

人眼看图的时候,第一遍扫的是轮廓,第二遍才看细节。所以一张图最先被感知到的,是大的分区结构。如果一张图没有清晰的分区,所有节点平铺在一个平面上,读者会在找你画的某个重点之前,先花了二十秒搞明白“这张图到底是什么范围”。

容器就是干这个事的。它就是视觉上的边界框,把一群节点归拢到一个逻辑范围内。最常见的是按分层来划分:接入层、应用层、数据层;或者按业务模块来划分:交易域、用户域、营销域。

D2里用嵌套语法就能实现容器:

system: { gateway: { shape: rectangle } orders: { shape: rectangle } users: { shape: rectangle } }

这个嵌套结构渲染出来就是一个叫system的大框,里面包着三个小矩形。读者一眼就能看出“这三个组件都是在同一个系统边界内的”,而不需要靠连线去猜。

我自己定了一条规则:画图的第一步永远是画容器和边界,第二步才是连线和加节点。顺序反了,后面很容易发现节点归属不对,又得挪来挪去。容器设计得好,图的信息层级就立住了一半。

2.2 颜色是语义,不是装饰

颜色是最容易被滥用也最容易被忽视的要素。很多人画图时觉得“多几个颜色显得丰富”,于是一张图里出现了七八种颜色,最后读者记住的只有“这图颜色真多”,完全没记住结构。

我对颜色的要求是:每种颜色必须能解释它的语义,且全图同一语义只能出现一次。比如我常用的语义色只有四类:

  • 主业务组件:一种品牌色/主色
  • 数据存储:一种固定的强调色
  • 外部依赖:一种中性偏暗的灰色
  • 异常/危险节点:一种醒目的红色,且只能用在需要警示的地方

D2的变量功能对这种场景特别友好。我可以在公共变量文件里定义:

vars: { primary: "#0F766E" database: "#7C3AED" external: "#6B7280" danger: "#DC2626" }

然后每一张图都引用这些变量,而不是自己写一个“感觉差不多的颜色”。这样即使某一天要整体换主色调,只需要改一个地方,所有引用这张变量的图都会跟着变。之前那种“一张图上出现8种蓝色”的情况就不存在了。

这里也想给个实操提示:变量命名不建议用color1、color2这种无意义的名字,一定要用语义化命名。看到$database你才知道这个颜色是给数据库用的,换成$green过两周你自己都忘了当初为什么选绿色。

2.3 连线方向:一条线要讲清楚一件事

连线是图表里最容易“随手一画”的元素,但恰恰是最需要较真的。一条线看起来只是连接了两个节点,实际上它承担了三个语义:方向、关系类型、交互内容。

方向决定依赖关系。a -> b和b -> a是两种完全不同的架构表达。评审时,箭头方向经常能暴露出设计问题:比如A服务依赖B服务,但你的图里画的是B指向A,那读者就会误以为B依赖A。

关系类型需要明确。一条线是调用关系还是数据流?是HTTP调用还是消息队列?这些差异如果不用标签标注出来,读者只能靠猜。D2里给连线加标签很简单:

gateway -> orders: POST /create orders -> database: SQL

这样读者不仅知道“订单服务”连到了“数据库”,还知道它是通过SQL访问的。如果同一个系统里既有同步调用又有异步消息,建议明确把这些线区分开,不要混在一起。

我踩过的一个大坑是:一张图里同时画了“服务调用关系”和“数据流向关系”,而且用的是同一种线型。评审会上别人问“这条线到底是调用还是数据流动”,我解释了半天才发现这张图本身设计就不合格。后来的规则是:一张图只表达一种主关系。如果需要表达两种,就拆成两张图,或者用线型、颜色明确区分,并放上图例。

2.4 留白与图例:别把图画得太满

图不是信息越多越好。早期我总想把所有模块画进一张图,导致整张图密密麻麻,连线的交叉点像一团毛线。后来在评审会上,一个经验丰富的架构师给了我一个建议:好的架构图应该让人三十秒内找到核心模块,三分钟内理解整体结构。

留白不只是“空白区域”,它也是信息密度的一部分。如果一个区域已经重叠到没法继续添加节点,那不是布局的问题,而是这张图的职责已经太多了,该拆分了。

图例也是很容易被忽略的。如果你在图中用了不同的颜色、线型去区分语义,那么图例就不是多余装饰,而是必要的阅读辅助。我在D2模板里专门预留了一个legend区域,用来标注“什么颜色代表什么模块、什么线型代表什么关系”。有了图例,任何不熟悉这套规范的人拿到图也能直接看懂,不需要再口口相传。

3. 实操:从零搭一套可复用的图表模板库

理论讲完,进入实操环节。这个项目的核心交付物是一套“模板库加渲染脚本”,任何人都可以基于它快速产出符合规范的图表。下面是我搭这套库的完整过程。

3.1 最小项目结构与环境准备

第一步是准备环境。D2的安装方式在它的官方文档里有详细说明,按系统类型下载对应的二进制包或通过包管理器安装都可以。安装完成后,在终端里执行d2 --version能看到版本号,说明环境就绪了。

项目结构我建议按“源码目录、输出目录、脚本目录、文档”来划分:

diagram-design/ ├── src/ │ ├── vars.d2 │ ├── styles.d2 │ ├── architecture/ │ │ ├── system-overview.d2 │ │ └── order-flow.d2 │ └── process/ │ └── deployment.d2 ├── scripts/ │ └── render.sh ├── out/ │ ├── svg/ │ └── png/ └── README.md

src目录存放所有.d2源文件,按图和流程分目录管理。scripts/render.sh是批量渲染脚本。out保存生成结果,一般会加进.gitignore不需要提交到代码仓库。

然后是“第一条D2代码”。我不建议一上来就搞复杂图,先建一个最简单的文件,确认整个渲染链路能跑通:

user -> api -> database

执行渲染命令:

d2 src/process/first.d2 out/svg/first.svg

打开生成的SVG文件,如果能看到一条直线连接着三个节点,说明环境和工作流已经通了。这一步其实很重要,它能帮你尽早暴露工具链问题,而不是等你画完一张复杂的架构图之后才发现命令行参数用错了。

3.2 用变量与主题统一所有图的观感

统一风格的第一个抓手,是公共变量文件。我建立了一个vars.d2,把全项目需要的语义色、字体、通用参数都放在这里。

vars: { primary: "#0F766E" database: "#7C3AED" external: "#6B7280" danger: "#DC2626" }

然后每个具体的图通过 import 引入这份变量:

@import "vars.d2"

引入之后,图里所有颜色的引用都改成$primary、$database这种变量形式。有的同学可能会问:这不就是给颜色起个别名吗?直接写颜色值不也一样?区别在于:变量能保证“同一个概念永远用同一个值”。如果某天主色调整,你只需要在vars.d2里改一处,全项目所有图自动更新。直接写颜色值的话,你就得像当年我手工改四十多张图那样,一张一张处理。

D2还内置了多套主题编号,通过命令行切换就能给整张图换一套风格。比如:

d2 --theme=200 src/architecture/order-flow.d2 out/svg/order-flow.svg

不同主题对节点配色、边框、连线的处理方式不同。我会在模板项目里固定一个主题编号,确保所有开发者在本地渲染出来的图观感一致。只靠变量控制颜色还不够彻底,主题编号才是兜底方案。

3.3 实战案例:一张系统架构图的完整设计

这里以一个“某跨平台系统”的订单模块为例,完整走一遍用D2画架构图的流程。

需求场景是:外部客户端通过网关访问后端服务,后端服务依赖订单服务和用户服务,订单服务本身读写数据库。这张图要表达的核心关系是“系统内部模块划分与调用路径”。

先创建src/architecture/order-system.d2,代码如下:

@import "vars.d2" external: { shape: cloud label: 外部客户端 } gateway: { shape: rectangle label: API网关 } orders: { shape: rectangle label: 订单服务 } users: { shape: rectangle label: 用户服务 } database: { shape: cylinder label: 订单数据库 } external -> gateway: HTTPS gateway -> orders: gRPC gateway -> users: gRPC orders -> database: SQL

这里有几个细节值得解释一下。external为什么用云朵形状?因为它是外部实体,不是我们系统内部能控制的部分,使用云朵形状能第一时间告诉读者“这是一个外部依赖”。database用圆柱形是因为圆柱已经是数据库的通用视觉语言,符合大多数读者的阅读直觉。orders和users用普通矩形,表示它们是内部的普通服务节点。

接口标签我用了HTTPS、gRPC、SQL,而不是简单写调用。这样一张图里,读者能同时看到调用协议和数据访问方式,这对排查性能问题、理解网络边界都很有帮助。

渲染命令和查看效果:

d2 --theme=200 src/architecture/order-system.d2 out/svg/order-system.svg

生成之后先自己看一遍:信息层级是否清楚?视线是否能按“外部->网关->服务->数据库”的顺序顺利走下来?如果不流畅,优先调整容器的嵌套层次,而不是手动去挪节点坐标。D2的布局引擎会根据节点连接关系自动重新排列,有时候只是调整一下节点的加入顺序,布局效果就会好很多,没必要和坐标较劲。

3.4 把渲染接入文档与工作流

单张图能画了,接下来要把这套流程固化下来。我做了一个批量渲染脚本,放在scripts/render.sh里:

#!/usr/bin/env bash set -euo pipefail INPUT_DIR="./src" OUTPUT_DIR="./out/svg" mkdir -p "$OUTPUT_DIR" for file in $(find "$INPUT_DIR" -name "*.d2" | sort); do base=$(basename "$file" .d2) d2 --theme=200 "$file" "$OUTPUT_DIR/$base.svg" echo "rendered: $base" done

这个脚本做的事情很简单:遍历src目录下所有.d2文件,逐个渲染成同名的SVG文件,输出到out/svg。每次改完图,只要跑一下bash scripts/render.sh,所有图都会按统一主题重新生成。

把SVG嵌入技术文档也很方便,直接在Markdown/HTML里引用文件路径即可。这样文档里展示的图始终是脚本渲染出来的最新版本,不会出现“文档里贴的图已经过时两周”的情况。

更进一步的做法,是在项目的CI流水线里加一个渲染检查任务:当发现.d2文件有变更时,自动执行渲染脚本,如果渲染失败或者生成结果和上一版本不一致,就阻断合并。这套机制可以保证仓库里永远只有“能跑通、有最新效果图”的图表源文件。这步不是必须的,但如果有CI/CD条件,强烈建议加上,能省下不少“图更新了但忘了重新导出”的破事。

4. 常见问题与排查实录

方案落地过程中,我遇到了不少问题。有些问题很典型,网上资料讲得少,这里把它们整理成一份避坑记录。

4.1 布局错乱、文字重叠怎么办

布局是声明式方案里最让人纠结的一块。节点少的时候引擎排得很漂亮,节点一多,就可能出现连线交叉、文字重叠、节点挤在一起的情况。

先说结论:不要试图手动去调坐标。D2里虽然可以给节点设置相对位置或坐标,但引擎会自动布局,你手工指定的位置很可能在下一轮渲染时被覆盖,甚至导致更乱的布局。我见过有人为了把一个节点放到另一个节点的左边,写了半天坐标属性,结果升级一次工具版本后全丢了。

正确的做法是拆图。如果一张图已经复杂到需要手动调坐标才能看清,那它已经超过了单张图的合理信息量。把核心流程图保留,把次要细节拆到子图里,通过链接互相引用。D2生成的SVG是支持链接跳转的,可以在节点上挂链接指向子图的SVG文件,实现“主图看概览、子图看细节”的阅读体验。

如果只是轻微的文字重叠,可以先试试调整两个参数:渲染时的画布边距,以及是否开启了更宽松的节点间距。还有一种情况是标签太长导致的连线重叠,这时候优先简化标签文案,比如把“获取用户订单列表接口”压缩成“查订单列表”,而不是拉长画布。

4.2 中文字体和导出相关的坑

D2默认会调用系统里的中文字体来渲染文字。如果服务器环境里没有安装中文字体包,渲染出来的中文就会变成方块乱码。这个问题在本地Mac上不太容易遇到,但在纯命令行的Linux构建机里非常常见。

解决方案是在构建环境里提前安装中文字体。常见的开源中文字体都可以,装上之后还要执行字体缓存刷新命令,确认D2能扫描到新字体。如果渲染结果里的中文还是不对,可以先执行一个简单的测试文件,只放一个中文节点,看到输出正常了再继续。

另外,SVG是矢量格式,适合放在网页里,但在一些旧的文档系统或IM里直接预览时可能会出现兼容性问题。所以我的模板库里既生成SVG也生成PNG:网页文档用SVG,日常沟通发PNG。两种格式一步到位,避免临时去转格式的麻烦。

4.3 什么情况下该拆图

我总结了三个“必须拆图”的信号:

第一,单个源文件的代码行数超过两百行。这个数字不是硬标准,但超过这个量,说明图里的节点和连线数量已经多到很难在一屏内看全了。

第二,评审时需要不断放大才能看清某个区域的细节。正常的架构评审应该能在一张图里完成“整体结构讲解”,如果拿着放大镜找节点,那这张图对读者太不友好了。

第三,有人开始在一张图里画“子图套子图”。简单图里嵌套一层容器,还能增加信息层次;嵌套到三层以上,读者基本就迷路了。

拆图遵循“主图加子图”策略:主图保留最核心的链路和模块边界,子图负责补充某个模块的内部实现细节。主图和子图之间通过链接互相打通,阅读时点击跳转,比把所有细节塞进一张图里清晰得多。

4.4 协作规范:从个人作品到团队资产

图表模板库能不能在团队里长期运转下去,靠的不是技术,而是协作规范。我后来把下面几条规则写进了项目的README里,效果很好。

命名统一用kebab-case。文件名比如order-system.d2,不推荐订单系统.d2或者OrderSystem_2024.d2。节点ID尽量和代码里的服务名保持对应关系,方便阅读图的人直接从图跳到代码工程。

文件内的书写顺序要一致。我的习惯是:外部实体写在最上面,然后是接入层、应用层、数据层。这样的顺序并不是D2的强制要求,但对后续维护者更友好,读文件时视线是自上而下的,和阅读代码的习惯保持一致。

注释要写意图,不写过程。比如在某个节点上方加注释# 这里承接外部流量,后续可能扩容,比写# 放一个矩形在这里有意义得多。注释是给未来的维护者看的,尤其是你自己,过三个月再回来看图,意图注释能帮你快速回忆起当初为什么这么设计。

提交信息也要描述清楚图表变更。不要用update diagram这种谁都看不懂的提交信息,试试进入网关到订单服务的链路,改用 gRPC这种带语义的描述。把图表升级到代码库同等地位,它就不再是“画完就结束”的一次性产物,而是值得认真维护的技术资产。

这套模板库落地之后,我最直观的感受是:图表评审的讨论焦点终于从“这个颜色不对”“这个框是不是该再大一点”变成了“这个依赖关系是不是绕了”“这个模块边界划分是否合理”。这大概就是图表设计规范化带来的最大价值。

最后再分享一个小技巧:在评审会上,我偶尔会用D2的草图模式重新渲染同一张架构图,即添加草图参数让线条变成手绘风格。不知道是心理作用还是视觉习惯,团队对着手绘风讨论时表达意见明显更放得开,不像对着正式彩色图那样拘谨。如果你也遇到评审会上大家太客气不怎么提意见的情况,不妨试试这个“降低正式感”的小手段。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 18:44:21

欠驱动AUV水平轨迹跟踪的全局积分滑模控制设计与仿真

1. 项目拆解:一个看似基础但细节很多的控制问题 1.1 为什么欠驱动AUV是工程主流 做水下机器人控制这些年,我接触过不少实际项目方案,一个感受特别明显:真正下水跑的AUV,九成以上都是欠驱动构型。所谓欠驱动&#xff0…

作者头像 李华
网站建设 2026/10/21 18:42:59

电网不平衡跌落下并网逆变器正负序分离与负序电流抑制策略

1. 这个项目到底在解决什么问题并网装置做久了,对“电网电压跌落”这四个字特别敏感。不管你做的是光伏逆变器、储能PCS还是回馈式电子负载,前端只要并着公用电网,就绕不开这个问题:电网电压不是理想的三相正弦。尤其是不平衡跌落…

作者头像 李华
网站建设 2026/10/21 18:41:30

区块链项目商业计划书技术评估与最小可验证版本开发指南

简介:这份区块链项目商业计划书PDF面向区块链创业者、项目负责人及投资分析人员,用于梳理项目从技术方案到商业落地的完整逻辑,解决立项论证、融资路演与内部汇报时缺乏系统框架的问题。资源包内仅含1个PDF文件,压缩包整体约2.98M…

作者头像 李华
网站建设 2026/10/21 18:41:10

AI Agent上下文工程:从失效到性能倍增的五个优化方案

你有没有遇到过这种情况:自家AI Agent开场三连惊艳全场,追问几轮以后直接翻车——答非所问、重复推荐、连最基础的格式约定都开始无视,甚至像个失忆患者,把你前面交代过的关键约束忘得一干二净。如果你点头了,那恭喜你…

作者头像 李华
网站建设 2026/10/21 18:41:10

三菱FX3U PLSR脉冲指令底层原理与加减速调试指南

1. 用了多年PLSR,我终于把这条指令拆开看了1.1 一条常见的程序,背后却有四层动作打开三菱FX3U的官方编程软件,在梯形图里写下这段程序,几乎是做步进电机控制的人入门后的第一件事:LD X0 PLSR D0 D10 D20 Y000D0放脉冲总…

作者头像 李华
网站建设 2026/10/21 18:40:31

SPX3819M5-3-3 LDO 应用指南:选型、电路设计与调试避坑

简介:SPX3819M5-3-3中文资料.pdf面向电源管理与模拟电路设计工程师、电子爱好者及便携式设备开发者,聚焦低噪声低压差线性稳压器的选型与应用。该资料围绕SPX3819系列LDO展开,涵盖其低噪声(40μV)、1%输出精度、满载34…

作者头像 李华