十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

图表设计工程化:用代码和CI管理架构图生命周期

图表设计工程化:用代码和CI管理架构图生命周期 说到diagram-design大多数人脑子里冒出来的第一个问题就是用什么工具画图。但我在一线折腾了几年之后慢慢意识到工具只是最表层的东西。真正让人头疼的是图的生命周期管理图经不经过评审进了Git仓库没有改代码的时候有没有人顺手改图团队各画各的符号含义互相冲突新同学根本看不懂。这篇文章就把我这套图表设计工程化的完整打法整理出来包括踩过的坑、验证过的工具链、评审清单以及一套可以直接抄走的模板规范。适合正在被架构图、数据流图、部署图逼疯的研发、架构师和技术Leader参考。1. 一次架构评审暴露出的三个图的问题1.1 同一套系统五张不一样的图我对那次评审会印象特别深。不是方案本身多有争议而是会议室里出现的五张图没有一张能当成当前事实来讨论。架构师手里是一张厚重的总览图服务之间的连线画得很粗颜色也漂亮但那是三个月前的版本后端同学现场打开一张在线工具里的图节点倒是全可字体大小不一有几个服务还重复画了两遍测试同事在白板软件上整理过一张调用时序口径和架构师的完全不同。单看每一张好像都能看懂放在一起谁也说服不了谁。后来我花了整整一个下午把线上真实的服务依赖、配置里的路由关系、日志里的调用链全部拉出来才勉强拼出第四张图。问题不在于哪张图画错了而在于每张图都只服务了画图人自己当下的需求没有人对它负责也没有人给它定义过什么才算准确。这份混乱在团队里其实存在了很久只是恰好被一场高强度的评审会彻底放大。这次经历让我重新理解了diagram-design这件事图表设计真正的问题不是用什么工具而是图在团队里到底处于什么地位。如果图只是某个人的草稿它就一定会在某个瞬间和现实脱节而这个脱节在关键时刻会变成决策错误的来源。1.2 图改了代码没改文档也没改还有一次线上事故排查定位到某个服务已经改用gRPC通信了但架构图里还是标注的HTTP。原因是三周前做技术方案时大家顺手对着旧图画了个新方案旧图本身一直没更新。改动的人不觉得图是交付物只当它是过程稿。这种事太常见了代码改了接口配置改了路由消息改了Topic但图的更新永远排在有空再说这个优先级的最底部。这不是个例只要图没有参与到代码评审、合并、发布这条工程链路里它就会以极快的速度漂移。代码有版本管理、有review、有CI有静态检查图作为纯手工产物天然缺少这套约束。漂移几乎是必然的而不漂移才是特例。所以我后来在团队里提了一个比较直接的说法图如果不进Git仓库不参与MR评审不参与CI渲染那它就不是文档只是一张临时草稿。这话确实有点绝对但它成功让不少人开始重新思考图的生命周期图不应该是一次性创作而应该是一份随系统演进而持续维护的资产。1.3 没有图语言新成员看不懂新同学入职后一般都会先看代码、看文档、看架构图。但最让我头疼的是团队里不同人画的图符号含义完全不一样。有人用虚线表示异步消息有人用虚线表示后续规划有人用红色表示核心链路有人用红色表示异常状态。新人看图需要先猜画图人的习惯这比看代码还累代码至少有语法图完全没有约定俗成的语义。这就是图语言的缺失也是一个团队从个人画图走向协作画图时必须跨越的门槛。后来我参考C4模型那套分层思路把图分成系统上下文、容器、组件三层并规定形状、线型、颜色的固定含义才慢慢把各画各的扭转到一套词汇上。这部分我会在第3章详细展开。可以这么说图语言才是diagram-design真正值钱的地方工具只是载体语义才是核心。2. 工具选型代码画图是如何胜出的2.1 常用工具的横向对比先说结论工具没有绝对好坏但不同工具的适用场景差异极大。我长期实测下来大体可以分为三类拖拽型、白板型、代码型。拖拽型代表是draw.io这类白板型代表是在线协作画板代码型则包括Graphviz、Mermaid、PlantUML、D2等DSL方案。它们各自的特性差别非常明显维度拖拽型白板型代码型上手成本低打开即画低适合头脑风暴中等需要学一下DSL语法表达能力灵活任意曲线文字摆放非常灵活自由涂写结构性表达强自由摆放弱版本管理原生不支持不支持Git友好可diff可review中文渲染依赖系统字体麻烦但可控依赖平台字体配置字体栈后最稳定复杂大图超过80个节点容易乱更乱有布局引擎反而更整齐自动化弱无强能接入CI渲染、批量生成可以看到拖拽型和白板型解决的是先画出来的问题代码型解决的是持续维护的问题。对于系统架构图、数据流图、部署拓扑这类需要长期更新的图代码型几乎是唯一能坚持到项目结束还能保持同步的方案。如果只是画一张临时示意图拖拽型确实快很多但当你需要维护十张彼此关联的图时拖拽型会变成灾难。2.2 为什么选中代码风格而不是拖拽风格我之所以坚定选择代码画图背后的逻辑就一句话靠像素管理复杂关系一定失败靠结构管理复杂关系才有机会。用生活化的例子说拖拽画图就像在白板上用马克笔画流程画的时候很顺手但哪条线要改后面几十个连接点可能都要手动挪代码画图像写代码你只改文本里那一个词布局引擎会自动重新整理而且每一次改动都能在diff里清清楚楚看到。图的内容变化、新增节点、删除依赖审查的人直接看文本变更就行这比拿肉眼对比两张图片的差异可靠太多。代码画图还有一个隐藏优势就是可复用性。节点、分组、样式、连接线都可以用变量和抽象定义画第二张图时只需要引入第一张图的公共模块不用重复着色、重复排版。对于需要批量生成配截图、周期性更新依赖图的场景这种能力是拖拽工具完全给不了的。当然代码画图也有短板最大的短板是不自由。如果要做像素级排版、精准控制曲线弧度、画一张很有设计感的封面图代码工具确实费劲。所以选型时要搞清楚我们是要一份能长期活着的工程文档还是要一张临时给领导汇报的视觉图。两种需求都合理但不要混用。2.3 工具链长什么样DSL 渲染 导出我在团队里最终搭起来的链路并不复杂。源文件用可读的DSL语法写在仓库里路径统一放在 /specs/diagrams 目录下提交后由CI执行渲染输出SVG同时根据配置决定是否生成PNGSVG直接嵌入内部文档站README里用相对路径引用MR评审时图的源文件和代码一起被review改动一目了然。这条链路跑起来之后图的所有权就从个人手里转移到了团队仓库里不会再出现某个人电脑里有一份最新版的状态。为什么优先输出SVG而不是PNG因为SVG是矢量格式放大不糊、可以文本检索、可以嵌入网页体积又小。PNG不是不能用但它是位图后续如果想改颜色、想适配暗色主题基本只能重新渲染。还有一个容易被忽略的细节SVG嵌入文档站时要考虑主题适配最好用CSS变量控制颜色这样文档站切暗色模式时图也能跟着切。如果只是把一张静态位图贴在页面里后续维护体验会差很多。3. 建立自己的图表设计语言3.1 从画得好看到读得清楚五条设计原则我不反对图好看但图表设计的第一优先级永远是读得清楚。这一点很多团队直到图被反复误解才会真正理解。我总结过五条原则每条背后都踩过实实在在的坑一图一事。一张图只表达一个主题要么是模块关系要么是调用时序要么是部署结构。最忌讳的是把系统拓扑、消息队列、部署环境全部塞进一张图结果谁看谁晕讲的人还要反复解释这条线其实不是这个意思。流向一致。主流向必须自上而下或从左到右选定一个方向就全程保持一致。边角出现反向线时要特别标注否则读者会默认按主方向理解把依赖关系看反。类型收敛。节点类型尽可能控制在四种以内标准服务、外部依赖、数据存储、角色或用户。类型再多人的短期记忆就开始吃力一张图就变成了迷宫。颜色语义化。颜色只能用来表达状态、层级或风险比如绿色表示稳定服务、橙色表示待迁移、红色表示故障点。不要为了好看给每个模块配一种颜色那等于没有语义还会让色盲同事完全读不了图。标注克制。线上文字只放必要信息比如协议、接口路径、关键参数。背景注释放在图例或说明区不要试图把所有信息都堆到线上线上一旦变成段落作为图形语言的信息层级就崩了。3.2 分层建模系统上下文、容器、组件图之所以会乱很大程度是因为把不同抽象层级的东西画在同一张图里。我用类似C4模型的分层思路把图的抽象层级彻底拆开。第一层是系统上下文图也叫系统边界图画的是整个系统在生态里的位置节点是用户、外部系统、支付网关、ERP这类用来给新人和非技术受众讲全局。第二层是容器图画的是系统内部的进程和应用节点是BFF、订单服务、商品服务、消息队列、数据库等用来讨论技术方案和依赖关系。第三层是组件图画的是某个容器内的模块拆分比如订单服务里拆出下单、履约、库存预占几个模块一般只在深入某个服务时单独画。每一张图永远只属于某一个抽象层级千万不要让上层节点和下层节点混在一起。社区里C4其实还有第四层代码层但对于绝大多数业务团队日常维护到组件层已经足够类图应该交给IDE去生成而不是靠人手画。这样一套分层图集既能做到足够薄方便新人快速建立全局认知又能做到足够全需要深入细节时每一层都有对应的图可以展开。3.3 命名与引用规范统一命名是图语言的一部分也是团队协作里最容易忽略、但一旦做好就收益极高的事。我的经验是节点命名遵循类型-领域-职责的格式例如 order-service、payment-gateway、user-client关系线遵循动作 协议 路径的格式例如 HTTP POST /v1/orders、gRPC CreateOrder。命名简明扼要是为了看图时不用反复翻译节点名本身就在解释自己的身份。同一份图集里同一个服务在不同图中必须使用完全相同的名字不允许出现订单服务order-service订单中心三种写法。跨图引用时在节点描述里写清详见容器图-03让读者知道该去哪里找细节。命名规范单独写出来确实很枯燥但它是后面所有自动化校验能跑起来的基础。没有规范脚本就没有规则可查后面5.2节里的校验逻辑也完全没有办法落地。4. 落地一个团队模板库的方法4.1 模板要封装的四件事一个能被团队真正用起来的模板如果只是一张白纸那它毫无意义。真正有用的模板要提前把四件事封装好主题变量、常用模块、布局参数、示例用例。主题变量定义整套颜色体系、字体栈、节点形状偏好常用模块把标准服务外部系统数据库人/角色做成现成的块布局参数把节点间距、方向、分组规则固定下来示例用例则提供每一种典型图的实际样例。模板目录里自带一个订单中台上下文图的完整范例新同事照着改而不是从零开始画。封装模板的核心目的不是让图变得好看而是消除选择成本。一个人每次画图都要重新决定配色、字体、形状、间距精力都消耗在无关细节上真正重要的结构反而被冲淡了。模板把这些决策集中做一次之后所有人画图都按同一套视觉基线走图表才能累积设计资产。4.2 写一个可供团队复用的标准块我用类似D2风格的DSL举个例子说明标准块的写法vars: { d2-config: { theme-id: 0 layout-engine: dagre pad: 100 } colors: { service: #4F46E5 external: #6B7280 database: #D97706 } fonts: { default: Noto Sans CJK SC } } service: 订单服务 { shape: rectangle style.fill: $colors.service } database: 订单库 { shape: cylinder style.fill: $colors.database } client: 用户端 App { shape: rectangle style.fill: $colors.external } client - service: HTTP POST /v1/orders service - database: 读写这里的变量是全团队统一的新成员不需要记颜色值只需要知道有 service、database、external 这几个语义角色要用的时候引用对应的语义变量就行。标准块的价值在于让不同的人画出来的图风格一致评审的时候大家注意力可以放在依赖关系和接口设计上而不是被这个蓝色和那个蓝色为什么不一样这类问题干扰。4.3 从0到1推行时的节奏推规范最忌讳一步到位我见过太多团队一上来就出一份几十页的规范文档结果根本没人看。我的经验是先给甜品再立规矩。先在仓库里放好模板和两个标杆图任何人想画新图直接从模板复制改十分钟就能出一张体面的图。然后再在评审流程里提出有图才能评方案让图变成方案评审的刚需大家自然会开始使用模板。等大家已经养成用模板画图的习惯了再提命名校验和CI检查阻力会小很多。还有一个容易被忽略的细节规范文档不要写长两页以内是上限。没有人会认真读一份十页的图表规范大家真正需要记住的其实只有命名规则和颜色语义那几行其余细节全部由模板和校验工具承载。把规范变成工具的一部分比变成文档的一部分有效得多。5. 评审环节需要一个图检查清单5.1 一份可勾选的评审表有了规范之后评审看图就有了抓手不再依赖评审人当场凭感觉提意见。我整理了一张检查清单评审人照着过一遍就行。这张表不用复杂但覆盖了我在实际评审中最常发现的几个问题检查项合格标准常见问题单图主题一张图只讲一件事拓扑混合时序混合部署流向方向全图自上而下或自左而右随意转向分不清主次节点类型不超过4种矩形圆角圆形菱形全混用颜色语义颜色表达状态、层级、风险为美观而给每个模块换色线上标注只标协议和必要参数每根线上写一长段说明跨图引用写清详见第X层图图与图之间无任何关联状态时间有最后更新时间或对应版本拿到手不知是否过期孤立节点没有未连接的元素残留草稿节点这份清单的另一个作用是倒逼画图人自查。画完图自己先勾一遍比评审会上被人一条条挑毛病要高效得多也能减少评审会上用于图形问题的讨论时间把时间留给真正的方案讨论。5.2 自动化的轻量校验人力检查终究会漏自动化校验必须补上。我在CI里加过几个很轻量的检查逻辑都不复杂但确实拦截掉了大部分低级问题。第一检查是否存在孤立节点也就是没有任何连线的节点通常是画图时留下的残骸。第二检查节点ID是否重复、命名是否符合规范前缀防止同一服务在两张图里写法不一致。第三检查颜色是否落在主题变量表内出现硬编码颜色就直接警告。第四检查画布尺寸是否超过设定阈值提醒画图人当前这张图信息量过大、该考虑拆分了。这些校验不需要特别复杂的框架哪怕用脚本对DSL文件做正则扫描都能实现大半。真正关键的是把它们接进CI让不合规的图直接让流水线失败这比在评审会上反复提醒有用得多。程序给出的反馈是刚性的没有商量余地而这恰恰是规范得以长期执行的原因。5.3 与文档站和CI的集成方式图的最终归宿应该是文档站而不是某个人的本地文件。我们的做法是仓库里维护所有图的DSL源文件合并主分支后由CI统一渲染把SVG同步到内部文档站的固定目录文档站再按服务维度把相关图集组织在一起。这套流程跑通之后每个服务页面点进去都能看到最新架构图团队里再也没人问现在系统到底长什么样这种问题了。还有一个让体验提升很大的细节在每张图下方自动渲染出最后更新时间、对应Commit编号和源文件链接。图有了新鲜度信息读者一眼就能判断它是否过期维护者也更容易找到改图的入口特别适合在排查问题时快速确认这张图是不是一年多没动过了。性能方面建议做增量缓存DSL文件没变化的图不要重复渲染。一两个图无所谓图一多CI全量渲染的时间会膨胀得非常离谱。6. 踩坑集那些文档里不会写的细节6.1 中文与字体一次令人崩溃的方块事件有段时间本地渲染一切正常一到CI服务器上导出的SVG里所有中文都变成方块。刚开始我以为是CI环境缓存了旧版本清掉缓存重试还是方块把CI生成的SVG下载下来检查font-family字段发现字体名根本没写进去只写了一个不存在的兜底字体。排查到这里我基本确定是CI容器里没有中文字体渲染器找不到字形就输出成了方框。问题不在DSL而在运行环境。解决方案需要两件事同时做一是在CI容器里安装中文字体比如 Noto Sans CJK SC二是主题配置里明确设置字体栈不要依赖渲染引擎的默认字体。还有一个很隐蔽的坑文档站嵌入SVG时有些浏览器或PDF导出器不会去下载SVG里引用的外部字体文件导致线上看正常、导出PDF却变成方块。稳妥的做法是尽量使用系统常见字体或者把需要的字体子集直接嵌入SVG。这也是我在前面强调字体栈必须写进主题变量的原因因为字体问题一旦出现影响的是全团队所有图。6.2 大图渲染的性能与布局抖动画数据血缘图时源表加目标表加中间任务轻轻松松就几百个节点。我第一次画完500多个节点时渲染耗时直接翻了好几倍而且相邻两次提交之间只要源文件里某个节点的声明顺序变了整张图的布局就会全乱。原因有两层一是dagre这类布局引擎的初始化会引入随机性两次渲染可能给出完全不同的排布二是图拓扑变化会导致节点层级重构布局漂移被进一步放大。解决办法是把可复现性放在第一位。固定随机种子和布局算法参数让同一份源文件重复渲染出完全一致的结果这样diff才真正有意义。同时要做好拆分血缘图按业务域拆成多张每张控制在80个节点以内不要迷信布局引擎能处理无限大的图。拆图不只是为了性能更是为了人的阅读体验一张有500个节点的图即使渲染出来也没有人能从视觉上消化它。6.3 不是所有图都适合代码画最后说点泼冷水的话。代码画图很强大但它绝对不应该是唯一方案。需要随手涂改的白板头脑风暴画的是灵感不是交付物追求视觉冲击力的方案封面需要设计感而不是信息密度像素级精细设计的汇报材料需要的是设计师而不是DSL。这类场景用代码画图是自找麻烦效率低还限制创造力。我现在在团队里保留两条轨道正式交付的工程图全部走DSL仓库纳入评审和CI日常讨论的白板图随意发挥画完拍个照片丢进会议纪要就行。两条轨道不需要打通因为它们的产出物定位完全不同。姿态放平一点diagram-design的目标从来不是坚持用某种工具而是让正确的人在正确的时候读到正确的图。能想明白这一层工具之争就不再是问题了。
返回列表