
GitHub 上 2.5 万星标的开源项目 D2最近把我彻底从 Visio 拉回了代码编辑器。坦白说我当年也搜索过“visio下载安装教程”那时候画架构图确实是把架构图当一张图来“画”——拖方块、拉箭头、对齐线耗一下午。后来我换了思路把架构图当成“代码”来维护才发现原来一张图可以活得和程序一样体面。这篇文章就聊聊 D2 是什么、为什么它能做到“重新定义架构图”以及我从个人尝鲜到把它搬进团队协作流程后踩过的坑和攒下的经验。适合谁看如果你日常要画微服务架构图、系统部署图、业务流程图并且受够了图形编辑器里的手工排版如果你听说过程序员用代码画图但看着 Graphviz 的 DOT 语法、Mermaid 的“一复杂就崩”有点劝退如果你希望架构图能像代码一样进 Git、可评审、可追踪——那 D2 值得你花一个晚上试试。1. Visio 时代的三个隐藏成本其实早该被算一算Visio 不是不好它是图形编辑器的优等生。但图形编辑器这个范式放到今天以代码为中心的工作流里问题越来越明显。我不是劝你把 Visio 卸了而是想帮你算清楚每次用图形工具画架构图时你到底在为什么买单。1.1 图形编辑器的时间黑洞第一个隐藏成本是手工布局。架构图的本质是一堆节点和连接关系可是在 Visio 里每加一个服务你就得亲手拖一个框拖完框还要拖线拖完线还要担心线穿过别的框。我记得有一次给一个微服务项目画架构图三十多个服务画到一半产品经理过来看说“把鉴权服务挪到网关下面吧”。我挪了二十分钟挪完发现左边三根线的交叉点全变了又花半小时整理连线。这种工作本质上不是“设计”而是“排版”。排版本身不产生信息它只是把已有的关系摆好看。可很多人发现自己画架构图 80% 的时间都耗在这种不产生信息的排版上。更尴尬的是你刚排完需求一变所有排版又得重来一遍。图形工具的“所见即所得”在这种场景下反而成了诅咒——你看到的每一根线都要手拖你拖出来的每一根线都是不可复用的手工劳动。1.2 二进制文件的版本管理困境第二个隐藏成本在协作环节。用过 Visio 的人都懂.vsdx 是个二进制文件。你和同事同时维护一张架构图他改了两个框的位置你改了一条连线的颜色合并的时候就傻眼了——二进制文件没法按行 diff你们的改动只能靠“后存盘的人覆盖先存盘的人”这种原始方式解决。这个问题比表面上看起来要严重得多。架构图一旦进了二进制格式它就跟代码仓库彻底隔离了。代码每次变更都有 commit、有 review、有 blame架构图却永远停留在某次“另存为最终版 v3千万别改.vsdx”。我见过太多团队架构图画完的那一刻就已经开始失真三个月后图上的服务和线上实际跑的完全对不上只能重新画一张。图的腐烂速度往往和团队规模成正比。1.3 架构图与代码现实脱节第三个成本也是我最在意的一个图形编辑器画出来的架构图跟它要描述的系统之间没有任何机器可读的联系。你在 Visio 里画了一个“订单服务”但这个框和代码仓里的 order-service 目录没有任何绑定关系。你画了“用户服务 → 数据库”但这条线代表的是 HTTP 调用还是消息队列只有画图的人自己知道别人只能靠猜。这种脱节直接导致一个结果架构图成为“一次性交付物”。评审完了汇报完了图就被遗忘在共享盘里。没人维护的图比没有图更危险因为新人会照着这张过期的图去理解系统然后踩进坑里。想解决这个问题就必须把架构图从“画出来的图”变成“写出来的资产”让它能跟着代码一起长大——这就是 D2 这类声明式绘图工具出现的根本原因。2. 为什么架构图可以用代码写D2 的声明式模型D2 的全称是 Declarative Diagramming官方给自己的定位是“现代声明式图表语言”。理解 D2最核心的一件事就是理解“声明式”这三个字。它不是一个更好用的画图软件而是一种完全不同的绘图哲学。2.1 “描述意图而不是拖动像素”在 D2 里你不描述像素你描述关系。你不说“把订单服务这个框放到坐标 (100, 300)再从它的右侧画一条线到数据库的左侧”你只需要写订单服务 - 数据库: 读写这一行字什么意思意思是“订单服务依赖数据库依赖类型是读写”。至于订单服务这个框画在哪、两个节点之间距离多远、箭头怎么拐弯全部由 D2 的布局引擎自动决定。很多人第一次用会不习惯觉得布局不由自己控制很没安全感。但这恰恰是声明式的价值所在布局是渲染问题不是内容问题。想想你写 HTML 的场景——你写了个h1标题/h1从来不关心浏览器把标题渲染在页面哪个像素位置那是排版引擎的事。D2 的想法一模一样它把“画图”拆成了两层你负责定义图的结构引擎负责让结构好看。这种范式转换带来一个直接好处——改图成本大幅下降。以前需求变更是“重新拖一次”现在需求变更就是改一行字的事。比如产品说“我们的服务要先经过消息队列再进数据库”你在 D2 里插入一行订单服务 - 消息队列 - 数据库完事。箭头的走向、布局的调整、交叉线的处理D2 自动搞定。2.2 文本化带来的版本管理红利当图变成纯文本Git 就天然成了它的版本管理系统。这是我迁移到 D2 后感知最强的一点。架构图不再是一个孤零零的 .vsdx 文件它可以和代码放在同一个仓库里跟着每一次提交走。设想一个很常见的场景。开发提了一个 PR把“用户服务”拆分成了“认证服务”和“用户中心”他顺手在同一个 PR 里更新了架构图的 .d2 文件删两行、加一行。Code Review 时其他人看到的不是一张模糊的图片对比而是清晰的文字 diff- 用户服务 - 订单服务/ 认证服务 - 用户中心 - 订单服务。改动一目了然架构演进的过程被完整记录了下来。哪怕三个月后想追溯“订单服务到底什么时候加了这条依赖”一行 git log 就能查出来。这种“文字 diff 版本回退 责任追溯”的能力二进制格式永远给不了。Visio 也有版本功能但它基于文件级快照做不到行级的精细化对比。对靠代码协作的团队来说这个差距不是“优化”级别的而是“质变”级别的。2.3 从交付物到代码资产D2 带来的第三个理念变化架构图从“汇报用的 PPT 素材”变成了“仓库里的代码资产”。因为它写起来足够简单维护成本足够低你才愿意让它和代码一起活着。我以前画 Visio图永远是一次性的现在我维护的 .d2 文件每次有架构调整都会同步更新因为它真的不费劲。这背后其实是一个数学问题。架构图腐烂的本质是“维护成本远大于重新画一张的成本”。Visio 模式下改一张旧图往往比重新画一张还痛苦旧图的逻辑你早就忘了连线的隐藏约束你也不知道所以大家选择重画于是旧图被丢弃新图又很快腐烂。D2 把修改成本压到极低改一个依赖关系只需要一行 diff维护旧图比新建图便宜得多图才真正活得下来。所以 D2 重新定义的不只是画图方式更是架构图的生命周期管理。3. 两小时上手 D2核心语法速通我知道你肯定想问这东西上手难不难我负责任地讲如果你用过 MarkdownD2 的学习曲线约等于零。下面我把最核心的语法过一遍你跟着敲一遍基本就会了。3.1 安装一个二进制就够了D2 用 Go 写的官方分发方式就是单个可执行文件没有任何运行时依赖。macOS 用户直接brew install d2Linux 或者 Windows 用户可以到官方 GitHub Releases 页下载对应平台的压缩包解压后把二进制丢进 PATH 就行。装完验证一下d2 --version输出版本号就说明装好了。D2 的渲染命令也非常简单d2 -w architecture.d2 architecture.svg这个-w是 watch 模式意思是你改一下 .d2 文件它自动重新渲染 SVG省去手动敲命令的麻烦。后面我还会聊怎么用 VS Code 插件配合这个模式实现“边写边看”。3.2 第一个图节点、连接与标签新建一个demo.d2输入下面这几行direction: right 客户端 - 负载均衡: HTTPS 负载均衡 - 用户服务: gRPC 负载均衡 - 订单服务: gRPC 用户服务 - 用户数据库: SQL 订单服务 - 订单数据库: SQL然后执行d2 demo.d2 demo.svg用浏览器打开 demo.svg你会看到一张自动排好版的架构图从左到右依次是客户端、负载均衡、两个服务和两个数据库。语法规则就三条箭头-表示连接关系箭头左边是源右边是目标。冒号后面跟的是连接上的标签比如“HTTPS”。direction: right控制整体布局方向可选值还有 left、up、down。注意我们全程没有指定任何坐标节点位置全是引擎算出来的。这就是声明式的核心体验。3.3 容器、class 与图标库真实架构图很少是扁平的一堆节点往往有分层、分组、打标签的需求。D2 用花括号表达“容器”云平台 生产环境 { 边缘层 { 负载均衡 } 应用层 { 用户服务 订单服务 } } 客户端 - 云平台.边缘层.负载均衡: HTTPS 负载均衡 - 云平台.应用层.用户服务: gRPC 负载均衡 - 云平台.应用层.订单服务: gRPC容器可以理解成一个带边框的分组嵌套的容器用.来引用比如云平台.应用层.订单服务。这种表达方式很接近代码里的命名空间读起来语义非常清楚。要给节点或容器加样式用 class 语法。比如把核心服务标红.critical { style.fill: lightcoral } 用户服务.critical 订单服务.critical想加图标也简单用户: { icon: https://icons.terrastruct.com/people/084-user.svg }官方图标库里有 AWS、GCP、Azure、云原生等一堆常用图标直接填 URL 就行。不过我在项目里实际用得不多个人经验是架构图的可读性主要靠结构和标签图标是锦上添花不是必需品。早期我沉迷给每个节点配图标后面发现图变得花哨但信息密度并没有提升就精简掉了。3.4 变量、导入与导出格式D2 还支持变量和文件导入这两个功能在维护大图时非常有用。变量能避免重复配置vars: { dsn: postgres://prod-1:5432/order } 订单服务: $dsn文件导入适合把公共模块抽出来。比如你有一套统一的网络层定义放在base.d2里其他图直接引用import base.d2这样多张架构图可以共享同一个基础定义改一次全局生效。导出格式方面D2 支持 SVG、PNG、PDF甚至 PPTX。我平时用 SVG 最多因为矢量格式放大不糊且可以直接嵌进网页或文档。4. 都是代码画图为什么偏偏是 D2 拿到 2.5 万星“代码画图”这个概念 D2 不是首创Graphviz 做了几十年Mermaid、PlantUML 也都很流行。可最后 GitHub 上拿到 2.5 万多星的却是 D2这不是没道理的。这个领域不缺工具缺的是把开发者体验做到头的工具。4.1 与 Graphviz 的差距可读性决定生死Graphviz 是提效工具里的老前辈但它的 DOT 语言今天看起来非常反人类。举个例子同样画一个“客户端访问两个服务”的图Graphviz 大概要写digraph G { client - svc1; client - svc2; }乍一看还行但你写复杂一点就发现DOT 的语法规则多且混乱节点属性、边属性、子图属性的定义散落在各种大括号里语义不直观。更致命的是 Graphviz 的布局算法经常给出诡异结果——两个不相关的节点莫名靠在一起连线从一堆节点底下穿过。它的默认输出风格还停留在上个世纪想调得好看得花大量时间研究样式属性。D2 显然吸取了这些教训。它的语法简洁到几行就能讲完布局引擎默认输出的图干净整齐配色现代。语法上 D2 更像是“给人读的”而 DOT 更像是“给机器读的”。在开发者工具这个领域可读性往往直接决定生死D2 把这点做到了位。4.2 与 Mermaid 的取舍定位不同Mermaid 是现在文档圈最流行的图表方案GitHub 直接原生支持渲染。我也经常用 Mermaid 画时序图、流程图。但 D2 和 Mermaid 的定位其实差别很大。Mermaid 更偏向“文档内嵌图表”——在 Markdown 里写一段 fenced block 出个小图轻巧方便。它的强项是快速生成简单图表弱项是复杂图表一旦节点多了布局、嵌套、样式都会变得不可控。我在项目里用 Mermaid 画超过二十个节点的架构图时经常遇到布局完全失控的情况节点重叠、箭头乱飞、改一行重排全图。D2 在布局引擎上下的功夫明显更深它有专门针对容器嵌套、大图的优化并且支持多种布局引擎切换。另外 D2 的语法表达力更强比如深层次的容器引用、样式 class、变量系统这些在纯绘图场景里是刚需Mermaid 做得不够深。说白了两者不是竞争关系而是互补。我的习惯是Markdown 文档里画个简单流程图用 Mermaid正经的架构图、部署图、系统拓扑图用 D2各干各的专长。4.3 与 PlantUML、Structurizr 的较量PlantUML 资格更老它能画各种 UML 图但依赖 Java 运行时首次安装体验就劝退一波人。而且 PlantUML 的语法偏古旧表达复杂连接关系时容易绕晕。Structurizr 则是 C4 模型的忠实信徒它不只画图还要你先做架构建模思路很专业但过度依赖建模流程和 Java DSL对一个只想要一张架构图的程序员来说太重了。D2 的策略是“轻盈地变得专业”。它不像 Graphviz 那样学术化也不像 PlantUML 那样老旧更不像 Structurizr 那样重度。它的语法接近自然语言安装是单二进制输出的图默认就能看。用一个表格总结一下我对这几个工具的体感工具依赖语法体验布局能力适合场景我的槽点GraphvizC/原生难读强但不可控论文图、学术图示样式太旧调优费劲MermaidJavaScript轻量弱节点一多就乱文档内嵌简单图复杂图完全不可控PlantUMLJava一般中UML 序列图安装重、语法旧StructurizrJava/DSL重中C4 架构建模建模思维门槛高D2Go 单二进制简单直观强可切换布局引擎架构图、部署图、拓扑生态还在长大中D2 之所以能吸引这么多星标关键还是它在“简单易用”和“专业表达”之间找到了一个极其舒服的平衡点。这背后是作者对开发者工具的深刻理解工具被放弃的常见原因不是功能少而是“学习成本超过了使用频率带来的收益”。D2 把学习成本降到极低又把日常画架构图的体验做到远超图形编辑器口碑自然就滚起来了。5. D2 落地实录从个人尝鲜到团队协作工具用得顺不顺光看官网示例是不够的得放到真实项目里滚一遍。下面这几条是我带团队用 D2 之后踩出来的经验每一条都是文档里不会写的。5.1 中文字体与 CI 环境最容易翻车的地方第一个坑是中文渲染。D2 生成 SVG 时中文字符的渲染依赖执行机器的字体库。如果你的图里有中文然后在某个精简容器里跑渲染输出的 SVG 里中文很可能变成一个个方块。我第一次在 CI 里渲染架构图就栽在这儿本地明明好好的CI 产物一打开全是豆腐块。原因不复杂——SVG 渲染发生在浏览器里浏览器要去系统字体库找中文字体找不到就只能显示方框。解决办法也简单在跑 D2 的机器上装中文字体。我在 GitHub Actions 的 Ubuntu 环境里会先执行一句sudo apt install -y fonts-noto-cjk再执行 D2 渲染问题立刻消失。如果你在基础镜像里跑记得把中文字体打进镜像。这个坑真的非常隐蔽不遇到一次根本想不到。5.2 节点一多就乱布局的调优思路第二个经验是关于大图布局的。D2 默认使用 dagre 布局引擎这个引擎对“层级关系明确”的图效果很好比如典型的从网关到服务再到存储的分层架构。但当你画一张几十个节点的网状关系图节点之间存在多对多连接时默认布局还是可能会出现交叉线过多的情况。实战中我总结了几条有效的调优思路。第一用容器强行分组把同一个域内的服务包进一个容器布局引擎会优先处理容器之间的位置关系再处理容器内部的弯曲整体会清爽很多。第二控制单个图的大小一个 .d2 文件里超过三四十个节点时我会反思是不是图拆小了更合理——一张图讲清楚一件事比一张图上堆一百个框更有用。第三灵活使用direction切换布局方向有些结构横着排清晰有些竖着排清晰多换方向看效果。另外D2 也支持通过环境变量切换布局引擎比如社区里有人接入了 ELK 布局对树状结构有更好的表现。但我个人建议先从容器和拆分图做起布局引擎只是最后手段结构表达清晰才是根本。5.3 把 D2 放进协作流程Git 与 CI 的落地D2 真正的价值要在协作里才能充分发挥。我目前的团队是把 .d2 源文件放在代码仓库的一个architecture目录下和代码一起走 MR 流程。每次架构调整开发者改 .d2 文件提交Review 的人看 diff。CI 里加了一个步骤每次代码合并到主干时自动渲染所有 .d2 文件为 SVG然后把 SVG 发布到内部文档站上。这个流程跑通以后架构图的更新频率翻了不止一倍。以前大家改完架构懒得去更新 Visio 图现在 .d2 文件就在仓库里改一行字的事没有心理负担。SVG 还能直接内嵌进内部 wiki评审会上用浏览器打开实时演示改一行代码立刻能看到图的重新布局——这种现场感给别人演示过一次就回不去了。集成 CI 时我还有一个建议固定 D2 版本。D2 还在快速迭代期语法有小概率出现不兼容更新。CI 里最好用固定版本号的二进制别用 latest不然某天 D2 升级可能导致老仓库里所有 .d2 文件渲染失败那种排查过程够你喝一壶的。5.4 哪些图我仍然不会用 D2 画工具用久了就会知道它的边界我从来不建议无脑迁移。以下三类图我仍然会用老办法第一类是“手绘感”的设计稿式示意图。比如给产品做汇报用的概念图讲究风格统一、视觉冲击力强这种图文字化表达效率很低还是在绘图工具里做合适。第二类是需要像素级控制信息布局的图。比如一张复杂的网络拓扑要精确标注每个端口、每条链路的具体位置D2 自动布局反而会添乱这时候手动拖拽的图更可控。第三类是高度机密的架构图。D2 官方虽然有自己托管的渲染服务但你完全可以本地跑二进制、离线渲染没有任何依赖。真正挡住它的不是技术而是团队愿不愿意把架构图当成代码来维护。结语一个让架构图活起来的工具习惯最后分享一个我目前固定下来的工作流。写 .d2 文件的时候我会打开 VS Code 的 D2 插件终端里跑一条d2 -w architecture.d2 architecture.svg浏览器开着渲染好的 SVG。左边写代码右边图实时更新每次架构评审会我都是投屏这个窗口。产品经理说“把鉴权逻辑挪到网关层”我当着他的面改一行字整张图自动重排——那种感觉已经不是“画图工具”能给你的了。如果你已经被 Visio 的手工排版折磨过几次我建议今晚就装一个 D2把手上最常被问起的那张架构图用文字重新写一遍。不用写复杂先把节点和连接关系列清楚渲染出来的图大概率不会难看。等你发现修改架构图比修改代码还快的时候你就明白这 2.5 万颗星是怎么来的了。