告别Visio:用D2让架构图像代码一样管理
先说结论:我在最近两个项目里彻底弃用了 Visio,所有架构图、流程图、拓扑图全部改用 D2 来画。这个 GitHub 上 2.5 万+ star 的开源项目,用纯文本描述就能生成非常漂亮的架构图,配合 Git 做版本管理、放进 CI/CD 里自动出图,体验完全不是以前拖拽对齐能比的。这篇文章会从为什么选它、语法怎么用、实际画一张微服务架构图,到踩坑排查全都讲一遍,无论你之前用没用过 Visio 或 Mermaid,都能直接上手。
很多同学第一次听说 D2,可能都会问:它和 Mermaid、PlantUML、Graphviz 到底有什么区别?我到底要不要换?我的判断是:如果你经常画架构图、且这些图需要跟着代码一起迭代,那 D2 值得认真试一次。但如果你只是偶尔画个流程图,那继续用你熟悉的工具也完全没问题。下面我从工具选型逻辑、核心语法、完整实操、常见问题四个维度展开。
1. 为什么架构图工具需要一次“重新定义”
1.1 传统绘图工具留给我们的那些“坑”
先聊聊我个人的真实经历。早年在做系统设计时,团队里画架构图的标准姿势是:打开 Visio,从左侧模具里拖几个方框,再手动拉一堆连线,最后还得花大量时间去对齐、调颜色、改字号。这套流程在一些正式文档、汇报 PPT 里确实够用,但一旦进入代码迭代期,问题就开始暴露。
最明显的问题是版本管理。Visio 的默认格式是二进制文件,一个 .vsdx 文件改了什么,Git 根本没法告诉你,Code Review 的时候也只能说“你打开图看看”。再加上多人协作时经常出现“你改了我不改,我改了又把你覆盖掉”的情况,架构图很快就变成和代码脱节的“一次性文档”。另一个痛点是很简单的图也要花大量手工操作:画一个三层架构,样式统一还行,稍微复杂一点的连线关系,拖拽和微调就很费劲了。
后来我也尝试过 draw.io、ProcessOn、Excalidraw 这些在线工具,它们解决了大部分“免费好用”的问题,但还是绕不开手动调整布局这件事。对于“图会频繁变动”的项目来说,每次微调都意味着成本,更不用说要把图嵌入到项目的 Markdown 文档、或者随代码构建自动更新了。
1.2 “文本即图表”到底解决了什么问题
再后来我开始重度使用 Mermaid 和 PlantUML。它们的核心思路一致:用一段结构化的文本来描述图形,再用命令行或插件渲染成 PNG、SVG。这样带来的好处是决定性的——图变成了纯文本,可以进 Git,可以逐行 review 差异,可以和代码放在同一个 MR 里提交。
但用久了又会发现新的问题。PlantUML 依赖 Java 运行环境,安装稍重;Mermaid 在流程图、时序图上很顺手,但画系统架构图时,想表达“微服务 A 依赖中间件 B,数据流向 C”,语法上总有点别扭,尤其是复杂容器嵌套和连线标签,布局经常要反复调。Graphviz 的 Dot 语言非常强大,布局算法也好,但语法曲率高,而且默认渲染风格比较丑,要做成能放进对外文档的图,需要依赖额外主题和自定义样式。
这是 D2 出现的最好时机。我理解 D2 的定位,就是想在“声明式文本画图”和“出图质量高”之间找到一个平衡点。理想状态是:写起来顺手、读起来像伪代码、渲染出来默认就好看,不需要我再去用 CSS 或者 SVG 手工调样式。
1.3 D2 的项目现状与定位
D2 是由 Terrastruct 这家公司主导开发的开源项目,核心语言是 Go,因此发布时就是一个单独的二进制文件,没有 JVM 这类沉重依赖。目前它在 GitHub 上的 star 数已经超过 2.5 万,这个数字放在绘图工具这个细分赛道里,已经相当能说明问题。
从定位上讲,D2 官方把它定义为“现代声明式图表语言”,主攻方向是架构图、系统设计图、网络拓扑图这类偏工程化的场景。它的语法尽量贴近自然语言,让每个 Map 关系看起来就像是在描述“谁连谁”。相较 Mermaid 偏文档型、Graphviz 偏底层布局引擎,D2 更希望做成“默认漂亮、开箱即用”的工程绘图语言。而且它的布局算法用的是一套名为 TALA 的自研布局引擎,后面我会专门讲它对出图效果的影响。
2. D2 核心细节解析与实操要点
2.1 安装与第一个 Demo:5 分钟跑起来
D2 的安装非常简单。官方提供了 macOS、Linux、Windows 的预编译二进制,也可以直接用一条命令装:
如果你想在项目里固定版本,更推荐用 go install oss.terrastruct.com/d2@latest,这适合已经装了 Go 工具链的团队,可以把 D2 的版本和项目绑定,避免不同同事本机版本不一致导致出图效果漂移。
装好以后,我们写第一个文件 hello.d2:
然后运行:
它会生成一个 hello.svg,用浏览器打开就能看到包含“用户”“登录服务”“用户中心”三个节点的箭头图,连线还带文字标签。这个第一印象很重要——完全没有学过 D2 语法的人,也能读懂 .d2 文件里的内容,这就是它的设计哲学:读起来像一段自然的描述。
这里有个小技巧:调试阶段可以加 --watch 参数:
之后只要你保存 .d2 文件,SVG 会自动重新生成,配合浏览器的自动刷新插件,能达到类似“边写边预览”的效果,比我想象中要舒服很多。
2.2 高频语法与常见用法:连接、容器、样式
D2 的语法虽然简单,但真要画复杂的架构图,还是需要掌握几个高频概念。
连接与箭头
最基本的关系就是 A -> B。你可以给连线加标签:
也可以双向连接:
这比我以前用 Visio 拉线快太多,尤其是批量描述节点关系时,写几行文本比鼠标点几十次要高效。
容器与嵌套
架构图里最常画的是“子系统里面包含多个组件”。D2 里可以直接把节点写成层级结构:
这样渲染出来就是一个名为“微服务集群”的大方框,里面包含三个服务节点。容器可以无限嵌套,比如外层是大区机房,里面再放 Kubernetes 命名空间,再里面放具体 Pod。嵌套层数深了以后,布局引擎会自动处理层次关系,这是手动画图时很难维护的结构。
样式修饰
D2 默认的蓝色主题已经比较顺眼,但很多时候需要给节点加颜色和图标语义。常用写法:
或者:
这里有个概念叫“关键字属性”,进入节点后通过 shape、style 等字段控制呈现。比如 shape: sql_table 会让节点变成数据库表样式,可以展示字段列表,这在画数据模型图时非常实用。另一个高频技巧是给同一个节点加多端口。默认一个节点只有一个连接点,但如果一个服务既要接收消息、又要输出事件,可以这样写:
不影响出图质量的情况下,这种多端口的写法让图中连线去向更清晰,我觉得比一堆线扎在同一点上好看得多。
2.3 布局与主题:默认漂亮背后的原理
D2 最吸引我的一点是“默认图就好看”,这主要归功于它的布局引擎。D2 一开始使用 Dagre 布局,后来重点推广自研的 TALA 布局。用 TALA 布局时,节点之间的间距、连线弯曲程度、子树的对齐方式都会更协调。
你可以通过 --layout 参数切换:
不止如此,D2 还内置了多套主题,用 --theme 参数切换:
主题 0 是经典蓝白,主题 200 是深色模式,主题 300 是黑金风格。有一次我要出一张给客户看的网络拓扑图,直接套了主题 300,再把背景换成深色,一整个高级感就出来了,完全不需要像以前那样去手调每个节点的颜色。
还有一个让我很惊喜的模式叫 Sketch。加上 --sketch 参数后,渲染出来的图会变成类似手绘风格的线条,有点 Excalidraw 的味道。如果团队讲究“白板感”,这个模式很讨巧:
总的来说,D2 在“工程化”和“美观度”之间做得很平衡。它不要求你做任何前端改造,只靠命令行参数就能应对不同场景下的视觉需求。
3. 实操过程:从零构建一份微服务架构图
3.1 场景设定与整体设计
理论讲太多容易飘,我们直接进入一个真实的实操场景。假设我要为某个电商系统画一张微服务架构图,要表达的内容包括:外部用户访问、API 网关、核心微服务、中间件、存储组件,还有服务之间的调用关系。
在动笔之前,我先在草稿纸上列清楚“图里要出现哪些对象”:
- 前端:Web、App
- 入口:API 网关
- 业务服务:用户服务、订单服务、商品服务、支付服务
- 中间件:消息队列 Kafka、缓存 Redis、搜索引擎 Elasticsearch
- 存储:MySQL、对象存储
然后是关系:
- 前端 → API 网关
- API 网关 → 各个业务服务
- 用户服务 → MySQL、Redis
- 订单服务 → Kafka(发送订单事件)、MySQL
- 支付服务 → Kafka(接收事件)、MySQL
- Elasticsearch → MySQL(通过同步任务)
过去用 Visio 画这张图,我大概要花半小时做布局和连线;用 D2,我的工作变成了“把上述关系翻译成文本”。这也是 D2 最颠覆我习惯的地方:思考的重点从“图怎么画”变成了“图里有哪些东西、它们之间什么关系”。
3.2 编写 D2 文件:逐步拆解与说明
我新建了 ecommerce.d2,第一步先定义前端和入口:
第二步再定义核心服务:
这里我注意到一个体验细节:D2 不要求提前声明节点再描述关系,你直接在关系里使用名称,它就会自动创建节点。比如“API网关 -> 用户服务” 这一句,同时完成了创建两个节点和画一条连线的动作,很符合“按描述顺序自然推进”的写作习惯。
第三步引入基础设施,用容器把相关组件聚合在一起会更有架构感:
然后把服务与中间件、存储的依赖关系补上:
到这一步,图的核心结构其实已经成型了。但我想让图更贴近系统的真实边界,于是加了一个外层大容器“电商系统”,把所有后端组件都包进去:
注意这里我用了 电商系统.API网关 这种“跨层级引用”的写法,作用是让外部节点直接连接到内部节点的某个端口,而不是连接到整个容器。如果不这样写,而只写 Web -> 电商系统,渲染出的箭头就会指向外层大框而不是网关,语义就弱了。
最后给节点加一点样式,让不同类型组件一眼可辨:
这样一个电商系统的微服务架构图就写完了。整个文件大概四五十行,全部是纯文本,任何人打开这个文件都能看懂系统里有哪些组件、谁依赖谁。
3.3 生成、导出与效果优化
写完 .d2 文件后,生成图只需要一条命令:
如果希望后续做二次编辑,可以生成 SVG:
个人建议:凡是要进 Git 的架构图,都导成 SVG 而不是 PNG。SVG 是矢量格式,放大不糊,而且文件更小;PNG 更适合直接贴到 IM、PPT 或内部文档里。
如果觉得整体布局不太紧凑,可以使用:
--pad 控制画布内边距,数值越大,图越松散。多试几次就能找到团队审美上最舒服的值。如果连线标签打架,或者节点间距不均匀,优先检查是不是同一个容器里的节点太多了,可以把部分节点拆成独立容器,布局会有明显改善。
这张图生成出来后,就可以直接放到项目的 docs/architecture/ 目录下,跟着代码一起纳入 Git 管理。之后每次架构调整,改动这个 .d2 文件再重新生成即可。
3.4 与 CI/CD 集成:让架构图永不“过期”
架构图最大的问题是“画完就过期”。传统做法里,架构评审时画的图,往往在半年后已经和线上部署不一致了,还很难被发现。用 D2 之后,我把架构图生成放进了持续集成流程,基本解决了这个问题。
以 GitHub Actions 为例,最简单的做法是:每次主分支有变更时,跑一个 Job 安装 D2,重新生成 PNG/SVG,再通过 git commit 把产物推回仓库。
一个简化的 workflow 是这样:
这个流程跑起来以后,团队里任何一次文档变更都会自动同步刷新架构图。这意味着,只要有人记得在改动系统设计时去更新 .d2 源文件,图就不会像以前那样停留在“某一次评审会议上”的版本。更进一步,我们可以在 PR 里强制要求:凡是涉及架构变更的 PR,必须修改对应的 .d2 文件,否则由 CI 直接拦截。纯文本文件的 diff 在 Review 时非常直观,这在一定程度上替代了过去“架构图评审靠截图比对”的尴尬。
4. 常见问题与排查技巧实录
4.1 中文与特殊字符处理
国内团队第一个遇到的基本都是中文显示问题。D2 默认的字体在某些操作系统上可能不包含中文字形,导致渲染出的图片里中文变成方块或者消失。解决办法分两步:
第一,检查系统是否安装了中文字体,macOS 一般有 PingFang SC,Windows 一般有微软雅黑。第二,如果有字体但 D2 没识别到,可以在 d2 命令里指定字体:
需要注意,D2 有多个字体变量,比如 --font-regular 和 --font-bold,最好都指向同一套中文字体,或者只使用 --font-regular,避免标题和正文渲染风格不一致。特殊字符方面,如果节点名称里包含空格或特殊符号,直接用双引号括起来:
这个习惯能省掉很多关键时刻的报错。
4.2 布局与美观问题:连线和间距的调优技巧
我在实际使用中最常调的是这三个参数:
direction:默认从上到下(down)。如果系统分层多,改成--direction=right让图从左侧开始向右扩展,会更有层次感。--pad:控制画布边距。图贴太紧时加--pad,非常直接有效。--theme:切换主题。如果默认蓝色看腻了,换个主题从视觉上会带来新鲜感,而且主题是一套全局变量,不会像手调颜色那样容易造成局部风格不一致。
嵌套容器比较深时,我遇到过容器内边距太小、子节点贴着边框的情况。解法是给容器的 style 增加内边距:
如果出现长文本标签换行不规范,可以在文本里加换行符:
总之,D2 默认效果不错,但到了复杂图上,微调还是一个常规操作。好在这些微调全是可复现的文本参数,不像 Visio 那样需要靠鼠标对准像素。
4.3 从 Mermaid、PlantUML、Visio 迁移的注意点
如果你是从 Mermaid 迁移过来,最大的感受会是语法更像“自然语言”,写起来很爽。但要注意,Mermaid 的时序图、甘特图目前 D2 还没有完全对等的能力。D2 的优势集中在结构图、架构图、网络拓扑图,所以迁移时最好先把目标图分类:如果只是流程图或时序图,留在 Mermaid 可能更合适;如果画的是系统架构图、容器依赖图,D2 是更优解。
从 PlantUML 迁移,重点要看有没有用到它的 component、package、rectangle 等特定元素,这些在 D2 里都有对应的容器写法。只要不是特别刁钻的图,迁移成本很低。
从 Visio 迁移则要有一个心理准备:你无法把 .vsdx 文件直接转换成 .d2。但是换个角度看,Visio 里的图通常都是手工摆放的,还原成 D2 时反而有机会重新梳理出一份“真实的节点关系”,画完后你会发现这个过程中对系统的理解也更清晰了。
4.4 实战避坑速查表
最后把我踩过的坑整理成一张表,方便大家直接查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 图表中文显示为方块 | 字体不支持中文 | 指定中文字体文件,或用 --font-regular |
| 节点名称含空格报错 | 解析器把空格当作分隔符 | 用双引号包裹名称 |
| 箭头指向外框而非内部节点 | 跨层级引用写错 | 用 父级.子级 语法 |
| 容器内子节点太拥挤 | 容器默认内边距不足 | 给容器设置 style.padding |
| 整体图太松散 | 默认间距较大 | 减少 --pad,或换 --layout=tala |
| 导出 PNG 模糊 | 分辨率不足 | 设置 --scale=2 或直接用 SVG |
| 连线标签重叠 | 节点太密 | 增大节点间距,或将相关节点放入子容器 |
这里提一个不算坑但很实用的小技巧:D2 支持 --scale 参数,比如导出 2 倍清晰度的 PNG 用于 PPT:
如果还想更进一步,可以在 D2 官网的 playground 里先试效果再复制回项目,非常省时间。
另外,关于 D2 的版本迭代,我想多说一句:它当前版本迭代速度很快,新特性经常在 minor release 里出现。如果你的 CI 脚本锁定的版本太老,可能无法用到最新布局算法,所以建议定期升级,并在升级后跑一遍已有图集,看看渲染效果有没有可以接受的变化。这个“可接受的变化”也是文本化图表工具的独特属性——升级工具时你会非常清楚哪些图受影响,因为 diff 会清清楚楚列出来。
回到开头那句“再见 Visio”,我现在更愿意把它理解为:架构图这种工程产物,应该像代码一样被管理、被版本化、被自动构建。D2 给的不是某一个炫酷功能,而是一套全新的“图表即代码”工作流。我个人的真实体会是,自从团队在研究文档里用 .d2 文件替代了 .vsdx 之后,架构评审的效率提升非常明显,因为每个人都能直接看代码 diff,而不需要打开一个图形化软件来回检查。
如果你准备小范围试水,建议先挑一张不复杂的架构图重画一遍,感受到“改一行文本、图自动重排”的顺畅之后,你自然会有继续迁移的动力。