
接手过一个开发了半年多的内部系统代码本身还算干净但文档库里光是架构相关的图就有七八份有的用在线白板画的有的用桌面绘图工具导出的图片还有一张是开会时直接贴进去的手机翻拍照片。风格不统一都还好说真正难受的是语义对不上——同一份流程图里一个动作在A图画成圆角矩形在B图画成菱形架构图里服务这个东西有人画成方框有人画成球体还有人干脆用一段文字加一个箭头表示。看起来是美术层面的凌乱实际上让团队失去了用图沟通的能力因为所有人都在用自己的方言画图。diagram-design 这个专项想解决的问题就是让这些图从一次性产出变成可以长期演进的资产。它的核心其实不是画图审美而是三件事给图的元素定义统一的语义把图的语言收敛成可管理的文本格式再让图的更新跟代码的演进绑到一起。我们做了大概一个多季度踩了不少坑最后沉淀下来的这套做法对很多中小团队都有参考价值。下面按项目推进的顺序把建模约束、目录设计、自动化校验、团队推广的完整过程拆开讲。1. 为什么图需要设计而不是只画出来1.1 散图之痛风格不统一只是表面问题大多数图的表面问题是不好看但真正致命的问题是没约定。一份架构图如果无法区分服务、数据存储、外部依赖、消息通道那这张图在跨团队讨论时就变成了纯粹的装饰品。我们在梳理旧文档时发现同一个系统在不同文档里至少有三种叫法一会儿叫接入端一会儿叫边缘组件一会儿又叫采集入口光是对齐术语就花了好几次评审会。把图源文件收进仓库之前先要想清楚一件事图的读者是谁。产品例会上的概念图和开发排障用的时序图完全是两种物种。diagram-design 的服务对象是那些需要被反复阅读、被长期维护的系统图例如架构图、核心流程时序图、数据关系图、部署拓扑图。至于活动海报草图、一次性的头脑风暴白板根本不在这套规范的覆盖范围内。1.2 图的维护成本远高于创建成本大多数图不是突然过期的它们是从诞生那一刻起就开始腐烂。原因很简单维护成本太高。一张架构图随着服务拆分、中间件替换、数据库分库几乎每个月都有改动点但手动改图要打开编辑工具、找到对应元素、调整连线、重新排版来回得花十来分钟还容易把布局弄得更乱。相比之下改代码反而是更轻松的事。所以我一直有个判断如果一张图不能在五分钟内被修改并提交那它迟早会被废弃。这也是我们最终把图源文件改成文本格式存储的根本原因。文本形式的图源天然支持版本控制可以做到逐字 diff一个字段的改动都能被评审发现它还能被脚本处理能放进持续集成里自动渲染、自动校验甚至可以从同一个源文件同时生成静态图片和结构化数据。作为对比GUI 绘图工具布局灵活但难以 diff多人协作容易冲突文件格式不透明白板类工具适合头脑风暴但承载不了需要长期演进的系统图。各有利弊后面详细说取舍。这里有个很容易被忽略的点团队需要的不是能画各种图的工具而是能让图一直被维护的工作流。diagram-design 在设计上没有把重心放在工具参数上而是先把约定定义好——哪些地方必须有图、图里必须有什么元素、更新时机是什么。工具只是刚好能支撑这些约定的载体。1.3 把图当作接口diagram-design 的基本定位给 diagram-design 一个准确画像它不是某个软件的名字它是一个约定集包含建模词汇、版式规范、目录命名、渲染校验脚本四个部分。展开来说建模词汇解决图里能出现什么版式规范解决这些元素怎么摆目录命名解决图放在哪里、按什么顺序读渲染校验解决图有没有老化、有没有写错。从设计理念上说我们把每张图看成对外发布的一个接口图本身是给人看的视觉内容图源文件是接口的契约。和代码里的接口一样它需要约束、需要版本、需要有 owner。有了这个定位后面做的一切决策都有了原则建模词汇收得越紧图越易读图源越结构化越容易自动化更新责任越明确越不容易腐烂。2. 建模约束与版式框架让一张图在团队里能读懂2.1 最小语义集先把节点和关系定义清楚让一张图真正可读第一步不是配色而是定义它的词汇表。我们做 diagram-design 时花了两周时间讨论一套最小语义集最后收敛成六类节点、五类关系。节点类型如下节点类型形状约定默认色调典型示例用户角色图形青色访客、运营、管理员服务/模块矩形蓝色认证服务、订单模块数据存储圆柱绿色关系库、缓存、对象存储消息通道队列形紫色消息队列、事件总线外部依赖圆角矩形灰色第三方支付、推送通道基础设施虚线框棕色集群、命名空间、防火墙关系类型分为调用实线箭头、数据流虚线带箭头、依赖实线无箭头、部署实线连接容器与实例、异步事件波浪线箭头。这里强调一点关系类型越少越好。我们见过团队设计了十几种线型最后连作者自己都要查图例才知道某条虚线是什么意思。建模词汇的收敛程度决定了图在跨团队沟通时能不能成立。选文本化图表语言的时候我们也做了简单的评分对比学习成本、表达能力、渲染依赖、持续集成友好度、布局算法。最终选了纯文本 DSL 的方案核心原因是语法干净、模块化能力强而且生成结果能在几分钟内接上渲染脚本。大家不必照抄这个选择关键是做决定之前先确认团队里至少有两三个人愿意长期写文本 DSL否则工具再优秀也会沦为摆设。2.2 版式与流向的默认约定元素定义清楚了接下来是排布。我们把版式约定总结成几条硬规则免得每次画图都重新商量一次版式。第一主流程方向统一从左到右用户入口在左侧外部依赖在右侧中间按调用链排列服务。第二数据存储放在被读写方的下层调用方在上层让依赖方向天然向下。第三尽量减少跨层回边也不允许两条边在图上交叉超过三次如果出现这种布局优先调整层级而不是硬拉线。泳道和容器是我们用得比较多的组织方式。在部署图中每个命名空间或主机画成一个大的虚线框内部的服务节点再按层次排列在流程图中泳道代表不同系统或角色每个泳道内只放属于该泳道的动作。容器不是装饰品它能表达边界和属性归属比如哪些节点属于同一模块哪些服务在同一个部署环境里这些信息如果只用节点颜色表达图会显得拥挤且难以扩展。还有一条很少被写进文档的经验空白也是布局的一部分。画图时总想在一个屏幕里塞尽量多的内容结果就是字号越来越小、连线越来越密。我们规定每张图的节点上限是二十五到三十个超过这个数量就必须拆成多张图再通过视图链接串起来。这个约束在一开始显得有些武断但确实让图的可维护性提升了一大截。2.3 配色语义颜色不是装饰是编码配色是很多人最容易放飞自我的环节。我们的原则是颜色承载固定的语义不承载情绪。蓝色只给服务绿色只给数据存储紫色只给异步消息灰色只给外部依赖如果这些类型之外又冒出新的颜色需求先问是不是类型定义不够了。这套色板被写进规范之后新同学看一张图不到十秒就能建立第一印象哪些是服务、哪里落库、哪里是外部系统的边界信息获取效率完全不同。必须提醒一点颜色永远不能是唯一的编码维度。部分读者有视觉差异而且打印出来或者放在灰度显示器上时会丢失颜色信息。所以我们在形状、线型、文字标签上都保持了冗余表达。形状和线型的差别在黑白打印后依然可读再配合文字标注才是一条稳妥的编码路径。图形设计里的可访问性在系统图中同样适用。3. 从一张架构图开始跑通完整设计过程3.1 工程内的目录与命名约定任何规范落到工程里第一件事就是定目录结构。我们最终采用的布局是这样的docs/diagrams/ ├── 00-overview/ │ ├── system-context.dsl │ ├── system-context.svg │ └── system-context.meta.json ├── 10-app/ │ ├── app-arch.dsl │ └── app-arch.md ├── 20-data/ │ ├── er-main.dsl │ └── er-main.svg ├── 30-infra/ │ └── deploy-topology.dsl ├── 40-workflow/ │ ├── on-off-boarding.dsl │ └── incident-response.dsl └── 90-drafts/ └── ideas-for-refactor.dsl命名规则是三段式序号 类型短名 图名。序号前两位数表示图在整个文档体系里的阅读优先级00 开头意味着先看它90 则明确告诉你这是草稿区。类型名我们用复数是为了在目录列表里形成分组感app、data、infra、workflow 一眼能看出图覆盖了哪些层面。草稿区是一个很实用的缓冲地带很多图刚开始画的时候根本不确定要不要长期维护有了 90-drafts就不会因为还没想好而让草图散落在个人笔记里。生成文件SVG 图片、元数据 JSON默认不放在单独的构建目录而是直接和源文件放一起。这样做的好处是人翻阅文档系统时可以直接看到图片文件也方便在 Pull Request 里直观对比图形的变化。代价是仓库会多一些静态资源但对文档类目录来说完全可以接受。如果团队里有人对仓库体积敏感也可以在持续集成里定时清理历史生成物只保留最新版本。3.2 从需求到成图一个系统上下文图的设计过程我们拿某数据采集平台举例跑一遍从空白到成图的完整过程。第一步不是画图而是列参与者清单系统内部有哪些边界哪些角色或外部系统会与它交互数据流向是同步还是异步清单出来后我通常先用文字写一版草稿类似这样数据采集平台 边界 ├── 用户角色: 业务运营人员、平台管理员 ├── 外部输入源: 设备网关、第三方数据接口 ├── 核心服务: 接入服务、清洗服务、存储服务、调度服务 ├── 数据存储: 指标库、元数据库、文件存储 └── 外部依赖: 消息通知、邮件网关第二步才动手写文本 DSL先把边界容器写出来再加角色和外部系统最后填内部服务和数据流向。示意代码如下脱敏简化用户角色: 运营人员 外部系统: 设备网关 数据采集平台 { 接入服务 - 清洗服务 - 存储服务 调度服务 - 接入服务 指标库 元数据库 } 运营人员 - 数据采集平台: 配置采集任务 设备网关 - 接入服务: 上报原始数据 数据采集平台 - 指标库: 写入指标 数据采集平台 - 外部系统: 发送结果通知这段文本渲染出来的图信息密度不高但边界、角色、主要数据流都非常清楚。第三步是回到 2.1 的语义表自查每个节点能不能归到某一类颜色有没有写错关系类型是不是用对了这里有个值得坚持的习惯图需要一份图例块放在图的下方哪怕团队只有五个人也要放因为三个月后连绘图者本人也会忘记紫色代表什么。这样的过程看起来慢实际上一张系统上下文图从零到初稿大概二十分钟真正的时间花在讨论边界和词汇上而不是排版上。先有文字草稿再上图是避免画到一半才发现语义不清的性价比最高的方式。3.3 流程图、时序图、部署图的差异化处理diagram-design 不是只处理架构图不同图种的设计重心差别很大放在同一套规范下时需要分别定义。流程图的核心是分支和异常路径。我们要求每一张流程图都明确标出主流程和异常分支泳道必须与责任系统一一对应。业务动作和决策点有没有混在一起是流程图可读性最关键的分水岭。很多新手把判断逻辑画成普通动作框等于把 if 语句写成了顺序执行这是需要重点 review 的内容。时序图的核心是横向交互与纵向时间顺序。参考规范是左侧列出所有参与方每条箭头带一个动词短语消息编号用数字前缀体现顺序和嵌套关系。时序图最常见的失败方式是只画成功路径把超时、失败后补偿这类关键分支全部省略这样的时序图在故障复盘时几乎没有用处。所以我们的合规要求是时序图必须包含至少一条异常分支哪怕它只是简单的一行箭头加说明。部署图的核心是物理边界和拓扑位置。节点要写真实部署单元例如某个服务实例所在的环境而不是写抽象的模块名。网络分段、对外开放端口这些细节才是部署图的价值所在。三种图的差异我用一张表总结图种首要信息最常见败笔最小可用要求流程/泳道图责任人与分支分支缺失分支完整泳道对应责任系统时序图消息顺序与异常只画成功路径异常分支存在部署图物理边界与连接画成逻辑架构图节点能落到环境与实例很多混乱正是因为没有想清楚图种。一张时序图突然加上部署信息一张架构图突然画成数据库字段级关系样式可读性都会严重下降。所以在规范里我们规定一张图只承担一个主要图种混着画不如拆开画。这算是我们在评审阶段被骂出来的教训。4. 让图不被遗忘审阅、渲染与自动校验4.1 图与代码同步的最小动作把图源文件放进仓库之后真正的挑战是让图跟上代码演进。我们采取的最小同步单位是改模块时顺手改对应的单图。一个服务改动了对外接口提交代码时就同时改掉描述这个接口的时序图或架构图整个系统级别的变更由架构负责人发起一次专门的图更新提交而不是让每个开发顺手改大图。这么做避免了一个很现实的问题如果每次代码变更都要求全量更新架构图大家会觉得负担过重最后只会拖到图彻底过期。我们还在 Pull Request 模板里加了一个字段本次变更是否影响已有图影响的话贴一下渲染后的新旧对比图。这个动作看起来简单实际非常有效。它把一个原本需要记忆的流程规则变成了提交时的强制提示。刚开始团队成员会觉得多了一步操作但亲眼看到图因为这一步而长期保持鲜活后抱怨就变成了自发遵守。我建议所有团队在建立这套机制时先花一个月只做一件事每次合入代码前检查人先问图有没有过期。不要急着上自动化先让图要和代码一起变更的意识落地。如果这个习惯没有自动化工具只会让你更快地生成一大堆过期的图。4.2 渲染与检查脚本把琐事交给机器人工记忆和自觉靠不住的部分才轮到脚本上阵。我们把渲染和检查逻辑写成了三个命令render 用来渲染全部图diff 用来对比两张图的输出lint 用来检查源文件是否符合规范。render 命令遍历 docs/diagrams 目录对所有文本 DSL 文件执行渲染输出 SVG 和 JSON 元数据这个命令能很快发现语法错误、引用了未定义的样式别名等问题。lint 命令检查的项比较机械但很重要。例如源文件是否包含图例块节点类型是否全部来自预定义集合颜色代称是否与语义表一致节点数量是否超过限定值文件名是否符合三段式命名目录是否放置了对应生成物。这些检查项整理成表检查项为什么要查失败时如何处理节点类型合法性避免出现未定义语义元素报告具体行号颜色语义一致性保证视觉编码统一输出自动纠正建议图例块存在保证新读者可自解释提醒补图例节点数量上限防止信息过载建议拆分图生成物与源文件同步保证仓库内产物可用触发重新渲染这些规则看起来琐碎但它们把图画得规范从个人品味变成了可以持续改进的技术债。只要 lint 不过就不能合并长时间积累下来图的质量会一直维持在及格线以上。真正的增益在持续集成里的应用。我们用一条轻量任务跑在合并前的流水线上先渲染全部图再做语法和规范检查最后比较两次提交前后关键图的像素差异。像素差异本身不是判定标准它是给人看的信号——如果图发生了无预期的变化说明这次改动波及了不该动的地方。这类图防回归机制帮我们挡过两次因为接口调整导致架构图悄悄失真的问题。4.3 评审清单审图的人到底在看什么图也是一种代码审图当然要有清单。我们的评审清单经过几轮迭代去掉了很多虚的东西最后留下六个核心问题这张图的时序或逻辑是否与代码实现一致。每个元素是否有清晰的类型定义命名是否沿用了项目统一约定的术语。边界是否准确外部依赖有没有被画出系统边界之外。是否包含已经下线或改名模块的残留信息。表达是否经济信息密度是否值得读者花时间看。异常路径是否完整流程图是否画出了失败分支。这六个问题的共同点是把审图的焦点放在替代了什么老图和带来什么新的正确信息上而不是审美层面。审美是加分项审图先审正确性。我们在实践早期浪费过很多时间争论线头曲率、阴影效果后来这些都被踢出清单改成一句注释看优先级先改布局再考虑美化。5. 团队推广过程中的阻力与应对5.1 最大的阻碍不是工具是没人要求图必须更新我以为推动 diagram-design 最难的是让大家学习新工具的语法但实际推广时发现最大的阻碍是责任感的缺位。代码有人 review测试有人跑可一旦图没有 owner它就会变成公共的无主之地。我们的解决方式很朴素每张长期维护的图指定一个 ownerowner 不一定是画图的人但必须是对内容正确性负责的人。同时我们给初期图更新任务定得很小的粒度比如这周把系统上下文图里新加的消息队列补上而不是这个季度把文档里的图都整理一遍。小任务的好处是完成感强容易被排进迭代计划。批评一个任务的粒度要具体到图、具体到节点、具体到截止时间否则它大概率会沉底。我们用了大概六周时间让首批三张核心图从大部分过期变成基本健康整个过程没有高深的管理方法就是不断地把大图拆小、把责任落实到人头。另外负责人每周的周报里要有一行本周哪些图更新了哪些图仍处于过期风险中。这个机制的压力不大但阻止了图悄悄被遗忘的默认轨迹。对一个小团队来说这一行的杠杆甚至比整套自动化脚本还大。5.2 几类失败的图设计案例与复盘复盘阶段我们记录了几类失败案例专门用它们在内部做反面教材。第一类是全家福图试图在一张图里画清楚所有业务模块、所有依赖、所有存储结果节点数超过五十线条交织成蜘蛛网新同学看不懂老同学也懒得改。它的根因不是在版式上省几个线条而是在一开始就没有按图种、按业务域做拆分。规范里的节点上限加进去之后这类图基本绝迹。第二类是无图例图作者熟悉整套语义画的时候觉得紫色就是异步、虚线圈就是集群却没考虑两周后的自己和两周后新来的同学。没有图例的规范图等于隐藏接口的 API根本不对外开放可理解性。这个案例让我们把图例从可选改成了 lint 必查项语法上不通过就不能出图。第三类是为画而画某张图在文档里的使命根本没想清楚内容也不是系统当前状态只是因为在一次方案评审里用过就一直留在文档里。处理方法是给它一个明确的归档流程任何图超过一个季度没有被任何人引用先移入 90-drafts 区再过一个月仍无人认领直接删除。图不是收藏品留着还要维护删除是对所有人解脱。5.3 后续演进让图从给人看走向被程序读懂diagram-design 目前做到的位置是让图的源文件结构化所以最后讲一下它的下一步。由于文本 DSL 在渲染过程中已经生成了节点关系列表我们完全可以把它当成一组结构化元数据使用。比如生成部署图的同时产出环境清单和端口表服务注册配置可以直接引用又比如在架构图里标记核心链路节点配合可观测性系统对错误率做分层排查。这相当于让图表参与业务配置而不是只停留在文档层面。我们实际尝试过的方向包括根据系统上下文图自动生成文档站里的目录导航在异常告警里引用图上的服务归属和上下游关系用图上的数据流关系反向补全依赖清单。效果都还不错。我的一个判断是图的终极形态不是静态图片而是人读的视图 机器读的数据双层结构。diagram-design 的这套实践恰好是为这个方向打的底子。个人体会是做这类规范推广项目产出物不只是一份规范文档和一组脚本更重要的是把团队对图的认知从美术作品转成工程资产。走到这一步后面怎么扩展都会顺理成章。