ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

图表即代码:用D2构建可维护的diagram-design工程实践

图表即代码:用D2构建可维护的diagram-design工程实践 最近两个月我一直在做一件听起来有点轴的事把团队里所有散落的架构图、流程图、时序图收编成一个叫 diagram-design 的代码工程。起因很简单文档里的图又双叒叕过期了——线上架构早就改了三轮Wiki 里那张图还停在三个月前谁看着都觉得不对但谁都不想动手去改。后来我想明白一个道理图之所以没人维护不是因为大家懒而是因为传统的画图方式本身就长在代码工程之外。图不跟代码走自然就没有人替它做版本管理、评审和更新。diagram-design 要解决的问题就是把这个画图的动作从手工劳动变成工程行为。如果你也在维护团队技术文档、写系统设计方案或者被架构图过期这件事反复折磨过这篇内容值得你花十分钟看完。我会从设计理念、工具选型、实际落地到持续集成把整套思路和踩过的坑都摊开来讲。1. 为什么 diagram-design 值得当工程来做1.1 传统画图方式的三个死穴先说说我在各种团队里见过的图是怎么死的。第一图与代码脱节。代码是活的每天都在变图是死的画完就没人管。等下一次有人按图施工发现跟线上对不上还得靠自己猜。最要命的是这种过期图会反过来误导新同学让他们以为某个服务还在走旧的调用链排查问题走了半天弯路。第二工具五花八门。有人用 draw.io有人用 ProcessOn还有人直接在代码注释里画 ASCII。风格不统一就算了图散落在不同平台想找一张系统全貌图得先问三个人。更麻烦的是这些平台导出的文件格式各异XML、PNG、SVG、PDF想统一处理都无从下手。第三没法 Review。大家写代码要做 Code Review但几乎没人会去 Review 一张图。图片格式的决定性不足——不能逐行 diff不能针对某个连线提意见不能知道这张图是什么时候被谁改的。图一旦错了只能靠人眼慢慢发现而人眼往往根本不会去看。这三个问题叠加在一起导致文档里的图要么快速腐烂要么直接缺席。我见过不少项目的架构文档文字写了上千行图只有一张还是早期规划阶段的草图。1.2 图表即代码的设计理念diagram-design 的核心思路特别朴素让图本身变成代码享受代码工程的所有配套。这句话展开来讲包含四层意思。一是版本管理。图源文件放进 Git每次修改都有提交记录哪天改坏了直接回滚。这个收益听起来基本盘但多少人从来没享受过在 draw.io 里画图改坏了只能靠自己记忆恢复这体验天差地别。二是代码评审。图源文件是文本天然支持 diff。别人改了一行连接关系PR 里一眼就能看出来。你可以针对某个节点、某条边提出修改意见就像平时 review 代码一样。这下子审图终于变成了一件可操作的事。三是自动化构建。图源文件能用命令行编译成 SVG、PNG、PDF能塞进 CI能在文档构建时自动重新渲染。只要你维护代码图就会跟着代码一起更新从机制上杜绝图过期。四是可复用。同一个组件可以在多张图里引用改一处全量生效。这在传统画图工具里几乎做不到但在代码化方案里是标配。我把这套思路叫做图表即代码。它跟代码即文档是同一个逻辑——先承认人不可靠所以用工程手段倒逼结果可靠。很多团队做不好技术文档不是缺文档是缺少让文档自动更新的机制diagram-design 这套方式就是奔着这个痛点去的。2. 工具选型与设计前置准备2.1 主流方案横向对比哪套才适合工程化确定代码化这个大方向之后下一步就是选工具。目前市面上能用来做文本绘制图的方案不少我拿它们跑了一个内部评估重点看四件事学习成本、布局控制力、工程化能力、输出质量。方案学习成本布局控制工程化能力输出质量典型槽点draw.io / Excalidraw低手动拖拽灵活但不可重现弱格式偏私有观感好无法文本 diff难以自动化Mermaid低弱复杂图容易乱中生态大中等布局全靠自动没法微调Graphviz高强Dot 语法功能多强偏学术风语法老旧看图需适应PlantUML中中中需 Java 环境中样式老气依赖重D2低中上支持多布局强单二进制高现代简约生态还在成长中当时跑完这个表我心里其实已经倾向于 D2但还没定因为选工具不是选最好而是选最适合。于是我又针对自己的使用场景做了一次更深入的验证。先说结论如果你团队以 Java 为主且不介意依赖PlantUML 可以选如果只是个人文档偶尔画两张Mermaid 轻量也够用但如果你想把图纳入严格的代码评审和 CI 流程D2 最贴合。它的定位就是面向现代工程师的图表语言语法干净编译结果确定性强而且单文件二进制没有运行时依赖。Mermaid 在浏览器生态里确实普及但当图变大之后它自动布局经常失控节点挤成一团连线跨来跨去。Graphviz 布局算法很强可 Dot 语法写起来太痛苦团队推广阻力大。D2 在这两者之间取了一个平衡语法接近直觉布局算法又可切换、可干预。2.2 为什么我最终选 D2这个决定背后有几个非常具体的理由。第一D2 的语法设计是现代的。它没有历史包袱声明式写法非常接近自然语言。比如server - client就是一条从 server 到 client 的连线看起来和读起来一样顺畅新人上手成本极低。我在团队内做了个半天小实验找两个没接触过 D2 的同学给了十个语法规则他们半小时内就能画出可用架构图这在 PlantUML 和 Graphviz 那里难以想象。第二布局内核可控。D2 默认使用 dagre 布局算法结构清晰连线基本不绕路如果图比较特殊还能切换到 ELK 布局。更关键的是它支持手动指定部分节点的位置这意味着我可以对自动布局做精细修正而不是一切听天由命。很多团队把图画得乱七八糟问题不在人在工具的自动布局太弱。第三工程化能力扎实。D2 是 Go 写的单二进制扔到 CI 里方便不用装 Java、Node 等运行时。导出格式有 SVG、PNG、PDF 以及纯文本等基本上覆盖了文档和演示场景。它的命令行工具还提供 watch 模式改完源码立刻重新渲染写文档时体验非常好。第四输出质量在简约现代这个方向上非常合格。对比一下同层级的图D2 导出的 SVG 配色克制、留白合理放到技术博客或项目文档里不会有违和感。这一点对非技术读者尤其重要图最终是要给人看的不是给机器看的。2.3 设计规范先行动手之前先定规则工欲善其事必先利其器但工具定完还不能马上动手画。我吃过没定规范就开工、三个月后全部返工的亏所以这次先立了几条军规写进仓库的 README。首先明确图的类型。不同场景用不同图系统模块关系用架构图处理流程用流程图跨服务交互用时序图别什么事都画一张结构图硬套。这个规定听起来像是废话但实际执行时特别有效因为大家一旦画图就容易闭眼堆方块。其次统一命名。以此为例节点名称一律用 kebab-case小写字母加连字符标签文字用中文连接线必须写明方向语义。凡是跨服务的连线必须在线上标注协议或事件名不能画一条光秃秃的箭头让人猜。再次规定画到什么粒度。架构图只画到服务级不画类和方法流程图只画关键分支不上细枝末节。图做大了没人愿意看也没人愿意维护就像代码一样大块头注定活不长。这些规范真正的意义是让全团队的图长一个样。你想想你打开任意一份文档看到的所有图都像出自同一个人之手阅读负担会小多少这跟代码风格统一是一个道理。3. 从零到一diagram-design 完整实操流程3.1 环境搭建与工程初始化D2 的安装是真的省心。macOS 上直接执行brew install d2Windows 用户可以下载官方 Release 里的可执行文件解压后丢进 PATH或者用包管理器安装。装完敲一下d2 version能出版本号就算成了。整个安装过程不到一分钟没有 Java 环境、没有 npm 依赖。编辑器适配建议直接装 VS Code 的 D2 插件语法高亮和实时预览都很完整。不用买任何付费工具就这俩免费组合已经能打。项目里我建议单独建一个目录装图表源文件比如这样docs/ diagrams/ system-overview.d2 deploy-flow.d2 MakefileMakefile里放几个常用命令把构建逻辑固化下来。比如.PHONY: build build: d2 docs/diagrams docs/build/svg --layoutdagred2命令的第二个参数可以是文件也可以是目录如果是目录它会遍历处理目录下所有.d2文件输出到目标目录。这个设计对批量构建非常友好不用每个文件单独写命令。3.2 实战用 D2 画出第一张架构图先来一个最最基础的例子。打开文件hello.d2写三行client: Client server: API Server client - server: HTTP /api/v1在终端里执行d2 hello.d2会生成一张hello.svg里面有两个矩形框一根带标签的箭头从 Client 连到 API Server。从写代码到出图整个过程不到十秒。这就是 D2 的魅力零废话直出。但实际项目里的图不可能这么简单。我们要几层服务后面还要数据库、缓存、消息队列。此时要用到容器特性把相关节点放进一个可视化的分组里。services: { gateway: API Gateway auth: Auth Service order: Order Service user: User Service } datastore: { shape: cylinder mysql: MySQL redis: Redis } services.gateway - services.auth services.gateway - services.order services.gateway - services.user services.order - datastore.mysql services.order - datastore.redis这段代码干了什么首先用两个块级容器services和datastore把节点分组前者默认是矩形分组后者因为标注了shape: cylinder整体变成圆柱体形状视觉上像存储。内部连线用services.order - datastore.mysql这种带路径的写法语义清晰。编译出来的图分组边框、节点层级、连线走向都是一目了然。再进阶一层给连接线加上标签调整节点颜色让关键路径高亮services.gateway - services.auth: JWT validate services.order - datastore.mysql: SQL services.order - datastore.redis: cache read/write services.order: { style: { fill: #eef4ff stroke: #2563eb } }这里的style.fill是填充色style.stroke是边框色。D2 对每个节点的样式控制粒度很细颜色可以写到十六进制。有了这套能力你可以把重点服务标出来把关键链路用颜色区分图的信息密度就上来了。3.3 样式主题与品牌统一团队文档里最怕什么每人一种风格。有人喜欢蓝底有人喜欢绿框还有人直接拿默认黑白色一整套文档看下来像拼盘。D2 的解决方案是全局主题。内置主题可以通过命令行参数切换亮色、暗色、多种配色的官方主题都现成。但真正的杀手锏是自定义主题在项目里创建一个d2style配置文件把品牌色、字体、边框、阴影统一写进去然后所有.d2文件编译时都引用这份配置。这样就算一百个工程师画一百张图出来的观感也是整齐划一的。实际落地时我建议团队把主题配置文件纳入版本管理和代码一起 review。谁要是想改一个颜色得走正常评审流程避免随手调色把整份文档的风格搞乱。这套玩法传统 GUI 画图工具是做不到的。我在团队里的做法是先用默认主题跑两周把大家的反馈收上来再统一定制一版品牌色主题一次到位。4. 工程化落地让图表进版本库、进 CI4.1 目录结构、命名与 Git 协作规范图源文件是文本所以要像管代码一样管它。我的仓库里有几条硬性约定。第一文件命名。建议采用01-system-overview.d2、02-user-flow.d2这种带序号的前缀格式确保文件名列表里的顺序就是文档阅读顺序。别小看这个细节文件多了以后没有排序列出来就是一场灾难。第二注释分类。D2 支持#注释务必在图源文件头部写明图的用途、负责人、最近更新时间。这些元信息本身也是文档的一部分而且跟随版本库一起流动。第三Git 提交规范。图文件的改动要写清楚在 commit message 里比如docs: update auth flow diagram。这跟代码提交没区别因为图也是会被 review 的。第四关于多人协作的冲突。文本文件 diff 虽然直观但多人同时改同一张图还是会冲突。解决办法有两种一是把大图拆成小图每张图只负责一张完整链路二是约定谁认领这张图的责任制改图前先同步。纯技术层面没有银弹还是要靠流程约束。4.2 自动化验证与文档流水线集成图变成代码之后最高光的时刻来了——它可以进 CI连构建和校验都能自动化。第一步格式统一。D2 提供了d2 fmt命令给所有.d2文件做格式化。类似 Go 的gofmt处理完大家写出来的代码风格完全一致。我把它纳入 pre-commit 钩子谁提交的代码格式不对本地就报错不用等 CI 反馈。第二步语法与渲染校验。在 CI 里跑一个步骤编译仓库里所有.d2文件。只要某份 D2 语法错误、引用了不存在的节点或者布局算不出结果CI 直接标红。这个机制提前拦截了大量低级错误。第三步产物发布和文档整合。我用的是类似下面的 GitHub Actions 思路- name: Render D2 diagrams run: | d2 docs/diagrams docs/build/svg --layoutdagre - name: Upload SVG artifacts uses: actions/upload-artifactv4 with: name: diagrams path: docs/build/svg核心价值在于每当docs/diagrams目录下的源文件变化CI 自动渲染出最新的 SVG再把产物挂到构建记录里或者直接更新到文档站点。整个过程无需任何人手工干预。第四步可视化 review。我在团队里还跑了个试验构造一个脚本对比两个分支的 SVG 差异自动输出差异图到 PR 评论里。reviewer 不用打开文件直接看评论里的对比图就能判断这次改动是否合理。这一步让审图变成了日常开发的一部分而不再是一个额外任务。这套链路跑起来之后文档站点上引用的图永远是最新且经过校验的。有人改了一行 D2 文档整个站点重新构建逻辑闭环就完成了。5. 常见问题与避坑指南5.1 布局与渲染问题排查D2 的自动布局在多数情况下表现不错但图一旦复杂比如超过二十个节点、十条以上跨组连线还是会遇到连线交叉、节点重叠的问题。我的排查顺序基本固定先看是不是布局引擎不合适。默认 dagre 适合分层结构图树状、有向依赖图用它效果极佳但如果图里大量节点是网状互联dagre 会疯狂绕线。这时候切换到 ELK 布局d2 input.d2 output.svg --layoutelk切换一次很多交叉问题会自动消解。如果还是不行就手动指定关键节点的位置。比如把核心服务放到图的中心D2 支持用class属性和坐标控制局部位置这时候自动布局和手动修正结合起来出图质量能上一个台阶。再有一个容易被忽略的点容器嵌套层级别太深。我见过有人把节点嵌三层容器编译出来的图字体缩到看不清。经验是容器最多两层再多就拆图。5.2 中文与字体渲染这块是我踩过最大的坑没有之一。D2 导出 SVG 之后在浏览器里打开一切正常但如果拿去转 PDF 或者用某些图片预览工具中文可能会显示成方框乱码。问题根源是 SVG 里缺了中文字体信息导致渲染时回退失败。解决办法有两个。一是渲染时指定系统已安装的中文字体在 D2 源文件头部加一条全局样式把font-family设为常见中文字体比如PingFang SC或Microsoft YaHei。二是在 CI 环境里额外安装中文字体包避免 CI 容器内置字体不支持中文导致乱码。我在 GitHub Actions 里被这个问题卡了一下午最后是在 workflow 里加了字体安装步骤才解决。如果你也遇到 CI 渲染出的图中文变成方块先看这个方向别去怀疑语法。5.3 团队协作与流程踩坑技术问题都好解决真正难的是让团队整个流程转起来。我在推进 diagram-design 时踩过两个软坑。第一个坑规范定得太细大家根本记不住。一开始我把命名、颜色、标注格式写了满满两页结果没人在乎。后来砍成三条铁律图源文件入仓库、改图必须走 PR、CI 必须通过。其他规范全部靠模板和示例约束。少即是多这一下推行顺畅了很多。第二个坑图源文件和文档引用散落在不同仓库导致图更新了、文档链接还是旧图。修复方式是让文档构建时动态引用渲染产物而不是引用静态 SVG 文件。简单讲文档站构建流程里先把 D2 编译成 SVG 并放到指定目录文档里的图片链接指向这个目录下由构建决定的相对路径。这样一来文档和图的更新天然同步。还有一个小建议不要急于把历史遗留的旧图全部迁移到 D2。先挑两张最重要的架构图用新方案画放在文档显眼位置让大家看到新的效果和使用成本。等团队产生信任感了再批量迁移。务实一点图是服务于人的不是人服务于图。这套 diagram-design 的方案我用下来最大的感受是依赖图的人最需要的是图的准确性而不是图有多漂亮。当图变成代码和系统一起演进它才真正值得被维护、被信任。如果你刚好也要整理团队文档里的图不妨先试着一个 directory 配上十张.d2文件让 CI 帮你兜底剩下的交给习惯养成。
返回列表