☰
让架构图成为工程资产:基于文本DSL的建模与自动化校验实践
2026/10/19 6:19:19 网站建设 项目流程

接手过一个开发了半年多的内部系统,代码本身还算干净,但文档库里光是架构相关的图就有七八份:有的用在线白板画的,有的用桌面绘图工具导出的图片,还有一张是开会时直接贴进去的手机翻拍照片。风格不统一都还好说,真正难受的是语义对不上——同一份流程图里,一个动作在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,它就会变成公共的无主之地。我们的解决方式很朴素:每张长期维护的图指定一个 owner,owner 不一定是画图的人,但必须是对内容正确性负责的人。同时我们给初期图更新任务定得很小的粒度,比如"这周把系统上下文图里新加的消息队列补上",而不是"这个季度把文档里的图都整理一遍"。

小任务的好处是完成感强,容易被排进迭代计划。批评一个任务的粒度要具体到图、具体到节点、具体到截止时间,否则它大概率会沉底。我们用了大概六周时间,让首批三张核心图从"大部分过期"变成"基本健康",整个过程没有高深的管理方法,就是不断地把大图拆小、把责任落实到人头。

另外,负责人每周的周报里要有一行:本周哪些图更新了,哪些图仍处于过期风险中。这个机制的压力不大,但阻止了图悄悄被遗忘的默认轨迹。对一个小团队来说,这一行的杠杆甚至比整套自动化脚本还大。

5.2 几类失败的图设计案例与复盘

复盘阶段我们记录了几类失败案例,专门用它们在内部做反面教材。第一类是"全家福图":试图在一张图里画清楚所有业务模块、所有依赖、所有存储,结果节点数超过五十,线条交织成蜘蛛网,新同学看不懂,老同学也懒得改。它的根因不是在版式上省几个线条,而是在一开始就没有按图种、按业务域做拆分。规范里的节点上限加进去之后,这类图基本绝迹。

第二类是"无图例图":作者熟悉整套语义,画的时候觉得紫色就是异步、虚线圈就是集群,却没考虑两周后的自己和两周后新来的同学。没有图例的规范图,等于隐藏接口的 API,根本不对外开放可理解性。这个案例让我们把图例从"可选"改成了 lint 必查项,语法上不通过就不能出图。

第三类是"为画而画":某张图在文档里的使命根本没想清楚,内容也不是系统当前状态,只是因为在一次方案评审里用过,就一直留在文档里。处理方法是给它一个明确的归档流程:任何图超过一个季度没有被任何人引用,先移入 90-drafts 区,再过一个月仍无人认领,直接删除。图不是收藏品,留着还要维护,删除是对所有人解脱。

5.3 后续演进:让图从"给人看"走向"被程序读懂"

diagram-design 目前做到的位置,是让图的源文件结构化,所以最后讲一下它的下一步。由于文本 DSL 在渲染过程中已经生成了节点关系列表,我们完全可以把它当成一组结构化元数据使用。比如生成部署图的同时产出环境清单和端口表,服务注册配置可以直接引用;又比如在架构图里标记核心链路节点,配合可观测性系统对错误率做分层排查。这相当于让图表参与业务配置,而不是只停留在文档层面。

我们实际尝试过的方向包括:根据系统上下文图自动生成文档站里的目录导航;在异常告警里引用图上的服务归属和上下游关系;用图上的数据流关系反向补全依赖清单。效果都还不错。我的一个判断是,图的终极形态不是静态图片,而是"人读的视图 + 机器读的数据"双层结构。diagram-design 的这套实践,恰好是为这个方向打的底子。

个人体会是,做这类规范推广项目,产出物不只是一份规范文档和一组脚本,更重要的是把团队对"图"的认知从美术作品转成工程资产。走到这一步,后面怎么扩展都会顺理成章。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询