diagram-design这个标题我第一次看的时候,以为是某个画图工具的仓库名,毕竟这两年“设计系统”“架构蓝图”之类的东西被讲得太多,很多人一上来就要画个一整套图。但真正把项目跑完一圈回来,我发现diagram-design要讨论的根本不是怎么把图画得好看,而是设计过程中,图应该落在什么位置、承担什么职责。它是系统设计的骨架性工作,是把一个模糊想法变成可讨论、可评审、可落地的一套思考方式。
如果你是做后端开发、技术架构,或者带一个小团队经常要对着需求文档发愁,那这篇内容应该对你有用。哪怕你只是自己写一个兴趣项目,diagram-design这套方法也能帮你避免一个很现实的问题:代码写了一部分,发现模块之间互相拉扯,回头炸掉重来。文章里我会拆解它的核心思路、完整实操过程、工具选型,以及我实际踩过的坑,这部分内容比较长,建议先收藏再慢慢看。
1. diagram-design 到底是什么,为什么它值得重新被拿出来讨论
1.1 先给这两个单词拆个底
diagram-design拆开看,"diagram"是图,"design"是设计。很多人以为它指的是“设计一张图”,比如用绘图工具画个架构图、时序图出来。但我在实践中更倾向于把它理解成另一层意思:让设计的核心产出物变成一张可以沟通的图,让“画图”这件事从结果变为过程。
传统的软件设计流程通常是这样的:接需求,想架构,写设计文档,然后评审,再修改,再评审,最后开发。设计文档虽然是文字为主、图作为插图,但实际上真正让人看懂结构、判断依赖关系、讨论边界合理性的部分,往往是几张图。文字是线性的,图是空间的,一个复杂的系统在文字里描述清楚需要很长篇幅,但在一张图里,一个框、一条箭头就能说明白这就是diagram-design想解决的核心问题:把设计的思考过程以一种更接近人脑认知的方式显性化。
我见过不少团队把“画设计图”当做一个交付物,代码写完立刻补一张图塞进文档做门面。那不是diagram-design,那是事后补作业。diagram-design应该发生在写代码之前,发生在一行代码还没出来的时候,图本身就是设计的草稿,而不是设计的说明书。
1.2 它和传统设计文档最大的差别在这
我以前也写过很长很厚的设计文档,第一章背景、第二章目标、第三章功能列表,写得规规矩矩。但实际评审时发现问题大多集中在一个环节:连某业务方A和业务方B到底是谁调谁都没达成一致。文字里写了“依赖关系”,但依赖关系在二维结构里往往需要反复盘才盘得清楚,这还是在文档组织得很好的前提下。
用diagram-design做设计,第一个显而易见的差别是评审效率。你把三张图铺在桌面上,一张外部关系图、一张核心链路图、一张内部模块图,所有的问题都指向具体的框和箭头,大家不用在文档里翻找上下文,直接对着图提问就行。我曾经有一次和团队评审一个订单处理方案,最开始文档写了一万多字,讨论到一半大家各自理解成不同版本,后来把图重新整理了一遍,重新只用了二十分钟就确认了模块边界和依赖方向,效率差距非常明显。
更核心的差别在于,diagram-design是一种结构优先的设计思维。写文档时人很容易被细节带跑,写接口参数、写表结构、写字段校验,结构问题反而变成背景板。但在图上,你能画到图里的信息是有限的,这反而逼着你先从宏观结构入手,把谁属于哪一层、谁和谁通信、谁依赖谁搞清楚,才有资格往细节里走。这是一种设计顺序的重塑,先有结构,再有细节,而不是反过来。
1.3 说白了,这是一套“低成本试错”机制
系统设计里最贵的错误不是代码写错,而是结构选错。代码写错,改几行就行;结构搞错了,可能意味着整块模块要推倒重组。diagram-design恰好是一个把结构试错成本压到最低的办法。
在图上画一条依赖线、挪一个模块位置,成本约等于零。但如果这个判断等到代码阶段才被发现,就变成重构,变成跨团队沟通,变成加班。我认识的一个朋友,A同学,曾在一个项目里因为没做这一步,把公共依赖包放在了最底层,结果上游模块全部反向依赖这个包,形成一个很深的循环耦合,最后花了大概一周时间才拆干净。这种问题如果先花半天画一张依赖图,一眼就能看出来。
所以,diagram-design不是形式主义,不是画几张漂亮的图给领导看,它是一套前期低成本试错机制。你把风险留在纸上,把确定性留给代码,这是我认为这个主题最核心的价值。
2. 核心方法论:怎么用 diagram-design 思维去拆解系统
2.1 先画链路,不要急着画模块
新手画架构图最常见的问题,就是上来画一堆正方形的模块,然后往里面填功能。这种画法往往死得很快,因为模块是静态的,你盯着模块看,根本不知道一笔业务流进来,系统是怎么处理它的。
diagram-design的习惯是先画链路。以任何系统为例,先把一条完整的业务链路拉出来,从外部请求进入,到系统响应结束,中间经过哪些环节,你先用箭头连出这条主线。然后再去问自己,每一个环节的职责是什么,所属模块怎么划分。这是一种动态思维,链路优先,模块其实是链路的副产品。
举个例子。你设计一个订单系统,别上来就画订单服务、支付服务、库存服务三个框,而是先画这样一条链:用户下单、校验库存、创建订单、发起支付、接收支付回调、更新订单状态。等这条主链路成立,你再去给链路中的节点分组,哪些节点内聚成一个服务,哪些节点可以拆出去,模块边界顺其自然就出来了。你先画模块后画链路,很可能画出来的模块是“感觉需要”,先画链路后画模块,模块的划分是有依据的。
2.2 分层画图,一张图只讲一件事
diagram-design一个很容易被忽略的约束是:一张图只表达一个层次的抽象。我见过很多架构图,上面既要表现物理部署,又要表现逻辑模块,又要标出消息队列,又要画出外部系统,结果整张图密密麻麻,谁也无法快速抓住重点。
正确做法是分层。画到纸上,第一层是系统外部关系图,画清楚系统边界和外部参与者、上下游系统之间的交互;第二层是系统内部核心链路图,画清楚业务请求如何流经内部所有处理节点;第三层才是详细的内部模块图,画出模块划分、数据存储、关键依赖。这三层图各司其职,外部关系图用于统一团队对边界的认识,链路图用于讨论核心流程和异常分支,模块图用于指导具体编码实现。
按照我的经验,大部分评审争论都是因为分层没分好。有人拿着模块图讨论外部对接方式,或者拿着部署图讨论业务时序,讨论自然跑偏。把不同抽象层次拆到不同图里,很多争论自动消解,因为每一张图都有明确的使用场景。
2.3 把箭头和边界画明白,这是图的灵魂
画图的人多,把图和画明白的人少。很多人画箭头时,根本没想清楚箭头代表什么。diagram-design对这些符号有比较明确的约束:箭头代表方向,方向代表依赖或者流程,二者不能混用,边界要用稳定且明确的形状标注。
这里面最容易混的是“依赖方向”和“数据流方向”。依赖方向指的是编译期和运行期的调用关系,A模块依赖B模块,箭头从A指向B;数据流方向则是指业务数据的传递方向,数据从A流向B,箭头还是从A指向B。问题在于,同一个图上,如果既画依赖又画数据流,而且箭头含义没有图例说明,看的人很容易错乱。我建议在用diagram-design的过程中,一个图里只画一种关系,要画依赖就全画依赖,要画数据流就全画数据流,两种关系混用必须要有明确的箭头样式区分,并且配图例。
边界也很关键。系统边界、模块边界、进程边界必须在图上有清晰的体现。比如外部系统模块就放在虚线框外面,内部模块放里面,这个虚线框就是边界的可视化表达,一旦画清楚了,谁属于系统内部、谁属于系统外部一目了然。
2.4 把时间维度放进图里
很多设计图最大的问题是没有时间感。图上画了A调B,B调C,但没画这些调用发生在什么时序,哪些是同步的,哪些是异步的,哪些需要重试,哪些可以丢消息。一个没有时间维度的图,本质上是一个静态的“零件关系图”,而不是一个动态的“系统运行图”。
diagram-design在画核心链路的时候,要额外关注这样几类时间相关的问题:同步调用还是异步通知,超时了怎么办,有没有补偿机制,失败重试的触发点在哪里。这些问题不画进图里,评审时根本讨论不到,等到写代码时才发现,一个异步回调的处理逻辑足够折磨人两周。画图时把这些关注点显式标出来,比如在异步调用的箭头上加一个“异步”标记,在可能超时的节点旁边加一个延迟符号,整张图的信息密度会立刻提升一个级别。
2.5 评审时,只讨论图上的结构问题
diagram-design最后还有一层纪律:图的评审必须聚焦在结构本身。所谓结构,就是模块划分合不合理、依赖方向是否一致、边界是否清晰、链路是否完整。而不是图画的漂不漂亮、字体是否统一、颜色是否协调。我参加过的很多评审,最后容易变成审美讨论会,这其实是一种常见的偏题现象。
我个人的做法是,评审开始前先给图定一个明确的评审维度,比如这次只看边界和依赖,其他不在本次讨论范围。这样评审效率会高很多,毕竟是设计阶段,重点应该是把结构风险暴露出来,而不是把图做成艺术品。
3. 实操演示:用一个订单系统案例走完整套流程
3.1 案例定义与画图准备
光讲方法论很容易飘,这里用一个模拟项目X来说明问题,我拿“订单处理系统”作为例子。假设接到一个需求:平台用户下单购买商品,系统需要支持下单、支付、订单状态流转、超时关单、售后这几个基本能力,同时还要对接外部支付渠道。
先准备工具,我建议用白板或者大屏草稿纸起步,画图软件反而会干扰思路。准备阶段不需要做得太精细,只需要三个要素:外部环境、核心节点、最终目标。在这个案例里,外部环境是用户和支付渠道,核心节点就是下单链路。明确这些之后,开始画图。
3.2 第一步:画外部关系图,先圈定边界
这张图要回答的问题是:系统站在哪里,谁在外面,谁在里面。画法很简单,框出系统边界,外部参与者逐一放到系统外面,并用双箭头表示交互。
订单系统的外部至少有:购物用户、支付渠道、库存服务、运营后台。图中四个外部对象,视为四方接入方。购物用户通过接口下单,系统调用支付渠道发起支付,需要扣减库存的时候请求库存服务,运营后台查询/操作订单。
画这张图的要点在于:图纸上哪些放进框内,哪些放在框外,拿不准的时候宁可放到外面,因为系统边界向外扩永远比向内缩容易解释。我见过很多团队把支付渠道画进“订单系统”里面,这会导致模块边界混乱,衍生出很多无意义的讨论。
外关系图画完之后,所有参与明确的人看着图都能说清楚系统边界在哪里,哪些能力是依赖第三方,哪些是我们自己的。这张图也是后续所有细节讨论的锚点。
3.3 第二步:画核心链路图,把主流程拉通
接下来在白板中间重新画一张图,这张图只画下单到订单完成的完整链路,暂不关注备选流程。
一个典型的核心链路大概是:用户发起下单请求,订单系统做参数校验,然后尝试预占库存,库存预占成功后创建订单,创建订单后发起支付,支付渠道返回支付结果,系统处理支付结果并更新订单状态,最后给用户返回结果。
画的时候每经过一个环节就停一下,问问自己:这个环节如果失败了,流程会断在哪。这一步实际上是在为后续的异常分支做准备。比如库存预占失败,订单还能不能创建;支付回调迟迟不来,订单应该挂起还是继续等待。这些问题不一定立刻在图上画出来,但要在脑子里留个标记,画出来的主线就是一张常规流程的“地图”,后续异常流程都是在这张地图上做分支。
链路上还有一个关键点:有些步骤是同步的,有些是异步的。在订单系统案例里,用户请求下单这一整段基本都是同步的,但支付回调是异步的,支付结果通知和前端用户看到的订单状态更新也不是一条链。画图时可以用不同的箭头或者标注区分同步异步,否则图看着是一条条线,实际运行时却会出现时间错位。
3.4 第三步:画内部模块图,明确职责与依赖
链路拉通后,再根据链路反推内部模块划分。这一步需要考虑,前面链路中的节点哪些应该在一起,哪些必须分开。
以订单系统为例,用户接口接入层和订单核心处理逻辑可以分开画,一个对外提供HTTP接口,一个对内处理业务逻辑。支付对接这部分建议拆成单独的模块,因为外部支付渠道变化频率较高,隔离出来更有利于应对渠道方的接口变化。然后是库存客户端模块、订单存储模块、定时关单模块,每个模块都有明确的职责边界。
模块图画好后,还有一个重要工作:在模块图上标注依赖方向,并检查依赖是否成环。比如支付模块依赖订单模块,订单模块依赖库存客户端,库存客户端不反向依赖订单模块,依赖是无环的。但如果出现“订单模块依赖支付模块去查单,支付模块又依赖订单模块去查状态”的隐式依赖,这就是典型的隐患,必须在模块图上敲定意见:谁应该收敛谁的依赖,或者引入一个更底层的公共状态查询模块来打破循环。
3.5 第四步:补全异常流程与扩展边界
一个完整的diagram-design草图,必须有异常流程的一席之地。
订单系统案例里,我至少会追加三类异常时序:支付超时、库存扣减失败、支付回调丢失。支付超时时,定时任务应该去关单还是去查询支付结果;扣减库存失败时,要回滚前面已经创建的订单;支付回调丢失时,需要主动去支付渠道查单补偿。画图时不把这些场景画进去,设计文档写得再完善,开发阶段也会反复被“支付回调没收到怎么办”这种问题打断。
扩展边界的追加还可以面向未来演进。比如以后要接入新的支付方式,那支付模块要预留渠道适配层;以后要做多平台订单,核心链路要支持来源追溯。这些并不复杂,但要在线路图上预留出扩展的一笔,未来的演进会顺利很多。
3.6 实操过程中我的几点心得
第一,画图不要想着一次画完,抱着草稿心态去画会容易很多。diagram-design的本质是思考的脚手架,不要在局部美观上花费太多时间。我实际操作时,同一张链路图会快速改写多次,每改一次,对系统的理解就更深一步。
第二,画的时候要随时留注释。某个角落写着“这里如果队列堆积怎么办”,比过两天再回忆要可靠得多。我自己常犯的错是画得太嗨,然后第二天看着图想不起来当初那句话指的是哪条链路,后来总结出一个习惯:图上的每一个批注都尽量带上具体节点名称和问题索引。
第三,图形约定在团队内要提前统一。比如框代表系统,圆角矩形代表服务,虚线代表异步,箭头代表依赖,这个约定最好落在项目文档里,不然每个人画出来的图都在互相猜语义。
4. 工具选型:纸上谈兵阶段,用什么工具其实有讲究
4.1 工具选型的核心原则:能让人专注思考就好
工具是很多刚接触diagram-design的人最容易纠结的地方:用白板还是用绘图软件,用在线协同还是本地文件。
我的判断标准很简单:这个工具能不能让你在修改图时保持思维连贯。如果你画一条线要想半天拖拽哪里,或者一个框的样式调整比思考链路花费的时间还多,这工具对设计思考就是负担。
设计草稿阶段我强烈推荐用白板或者粗笔手绘。理由在于,白板天然具备低约束、可擦除、可多人围观的特性,很适合讨论。笔和纸的颗粒度足够粗糙,你可以把注意力和表达重点放在结构和关系上,而不会被细节带偏。很多情况下,团队聚在一张白板前把核心链路画清楚,比各自在软件上发图再同步结论高效得多。
4.2 数字化工具阶段的对比与建议
当设计经历了白板讨论,进入需要留存、修改、评审、归档的阶段,就有必要转成数字化图表。它们的主线是“信息可分享、可版本化”,核心价值有两个:一是让异地协作成为可能,二是让图可以继续演进而不是画完就丢。
我用过的工具大致分三类:
第一类是通用绘图类,能画各种类型的图,优点是灵活度最高,缺点是命名和模板风格不统一时需要额外制定规范。第二类是在线协作白板类,多人实时编辑很流畅,适合远程评审,缺点是一些复杂连接和自动布局支持一般。第三类是专业UML工具类,画标准化的类图、时序图很规范,但整体偏重,不适合早期的自由讨论。
对于大多数项目,我的建议是分阶段使用工具,前期白板讨论,中期用通用绘图类出“丑图”,后期再用专业工具产出正式版。强行一步到位,往往会在正式工具上浪费时间精修错误结构,反而不利于快速修正。工具的整体评价核心就是三点:改动的便捷性、时间线和异步语义的表达能力、多人协作的流畅度。
4.3 工具之外的两个习惯
工具归工具,画图前有两个习惯一旦养成,diagram-design的收益会高出非常多。
一个习惯是给图写日期和版本。你画的每一张关键图,都应该附带“绘于何时、基于哪个版本需求”的标签。否则两张图看着似像非像,团队成员会分不清哪张才是有效的,这也是图维护的重要基础。
另一个习惯是画完图后自己拿着图走一遍核心链路,嘴里念一下每一段流程的输入和输出。只要念的过程中有任何断路,那多半是图上有信息缺失,或者链路本身设计就是有问题的。我管这个叫“看图说话测试”,特别适合在交付给评审前自查。
5. 设计图的项目生命周期:画出来只是开始
5.1 概念图、系统图、运行图,三类图的寿命完全不同
很多时候图不是一次性交付物,在项目里要持续演化。但并不是所有图都需要频繁维护,diagram-design里面我会把图分成三类,分别对应不同的维护频率。
概念图回答“系统是什么”,比如系统边界、核心模块划分,这种图变化很慢,只有业务定位调整时才会变,通常一个项目期维护一张即可。系统图回答“系统怎么工作”,比如核心链路、依赖关系,这种图跟着架构演进走,每有比较大的结构调整就需要更新。运行图回答“当前怎么部署、怎么配置”,这种图变化最快,跟着每次发布和配置变更走,版本号都要记录得比较细。
把这三类分开,维护成本才会可控。团队常见的问题就是把它们全部画在一张图里,结果改一个小配置,就得动一整张图,久而久之没人愿意去动它。
5.2 让图和任务绑定,评审时只改该改的地方
图不应该作为文档摆设存在,它要有自己的演进节奏。我习惯的节奏是:每次新需求开任务,先把相关图翻出来,对着图看改动范围落在哪;开发完成后,再重新瞄一眼图,确认图上表达和实际代码一致,不一致就顺手更新。这样图是处于持续更新状态的,而不是某一次集中补文档。
评审时的维护纪律也很关键:图上看得不顺眼的地方,先判断它是结构问题还是表现问题。结构问题才需要改图重画,表现问题比如颜色、布局这类,可以记下来但不打断讨论主线。没有这个取舍,一次评审可以因为一张图的排版而失控。
5.3 如何防止图变成“墙纸”
几乎所有团队到最后都会遇到一个尴尬:图挂在wiki里,从来没人看,变成一种“墙纸”似的存在。想避免这个问题,核心不是靠行政命令要求大家看,而是让图在关键节点上产生实际作用。
一个实战方法:新同学入职第一天,让ta看着系统图给团队讲一遍自己对系统的理解,讲错的地方就是图上没画清楚的部分,趁早补充。这个方法效果很好,当图足够清晰时,新同学基本能按图讲出准确的结构;讲不清楚,通常不是人的问题,是图的信息有问题。
另一个方法是在每个迭代的回顾会上,把核心链路图调出来,对着图对一遍这个迭代动了哪些模块。如果发现图上某块已经过时,当场更新。让图在项目的日常节奏里反复出现,它自然就会活下来。
6. 常见问题与排查技巧:图设计阶段容易踩的坑
6.1 图很漂亮,但没人看得懂结构
这是最普遍的一种问题。图颜色醒目、图标精美、布局整齐,但看完后你根本说不清这个系统分几层、谁依赖谁。原因通常是信息堆叠过多、没有分层、没有明确的箭头语义。
排查方法:盖住图的文字,只看框和箭头,能不能猜出大致的调用方向和数据流向。如果盖住文字后图变成一堆无意义图形的拼合,那信息表达就是不达标的,需要重新梳理图层的抽象级别,通常每一项细节都应该被移动到自己专属的细分图里,保留当前图只承担一种沟通职责。
6.2 图和代码脱节,设计图和实际实现不一致
代码写完了,图上还是最初的结构,后面维护的人越看越困惑,这种脱节几乎是每个项目的宿命,但要尽量延迟它的发生。
排查方法是抽查关键链路的真实性:选一条核心业务路径,对照代码调用栈走一遍,看图上标注的节点顺序和实际代码是不是一致。一旦发现不一致,要尽快判断是代码设计走样还是图表达错误,不管哪种,修复的成本都是越拖越高。最有效的预防方式还是前面提到的“任务结束就校对图”,不要等季度总结时再更新。
6.3 依赖方向画反了,模块之间循环依赖
画内部模块图的时候,A同学画了一个模块关系,箭头从订单模块指向支付模块,但实际代码里订单服务是调用支付服务的,箭头方向画反了,整个交流就会被误导,评审方向也可能被带偏。
排查方法:把图上的依赖箭头列成一个清单,然后逐个向自己一个简单的问题,A模块的代码里有没有引用B模块的接口,如果有,箭头方向指向B。一个项目拆下来的依赖清单,理论上必须是有向无环的,只要发现环,就要立刻停下来分析环上的哪一段依赖是可以靠调整设计消除的。图和代码脱节的另一个典型表现,就是实际代码里的依赖关系和图上画的不一致,这种不一致比图上画得不美观严重得多。
6.4 图的粒度控制失衡:太粗太细都有问题
有些图粗到只画了四个框,系统内部几乎没有任何交代;有些图细到把循环内部的变量名都写上去,连模块内的方法调用都要画出来。这两种极端的共同问题,是失去了为沟通服务的作用。
我的排查原则是:一张图应该能让你在三十秒内向一个不了解项目的人说清主要结构;同时,它应该能让你在五分钟内向一个准备接手模块开发的同事说清你关心的依赖和边界。如果一张图在三十秒内说不清,大概率粒度太细;如果五分钟内说不清接口边界,大概率粒度太粗。调整的核心是明确这张图的服务对象,服务对象定了,粒度自然就定了。
6.5 排查优先级建议:先修边界,再修链路,最后修模块
如果同时存在多个图的问题,我建议按照一个固定的优先级顺序来排查:第一是边界,边界错了,后续所有讨论都会白费;第二是链路,主流程没拉通,讨论模块等于空中楼阁;第三才是模块内部细节,毕竟内部结构在边界和链路确定之后才有意义。
结合团队实践,每次评审图时建议有一个固定的检查清单:边界是否有明确的框定、链路是否完整、依赖是否有环、异步和超时是否标注、焦点是否唯一。当前检查单跑完,图的结构质量基本就有了保障。
7. 一点个人体会
我实际做完整个diagram-design的过程,最大的感受不是“画图很有用”这种万金油结论,而是:当设计意图被画成图之后,讨论意见会具体很多。以前写文档,大家的意见经常泛泛而谈,比如“我觉得这里要分层”“这块设计不够灵活”。在图上讨论时,意见会变成“你这条依赖线应该再往下移一层”“这个异步标记应该放在支付回调而不是下单请求上”,这种讨论才是真正让结构变得越来越好的讨论。
如果你正好也在设计一个系统,或者刚接手一个没有图的旧系统,我的建议是先别急着补文档,拿起笔,把外部关系图和核心链路图先画出来。哪怕画得歪歪扭扭,哪怕你画完发现一堆问题,那也远比面对代码库发呆或者对着空文档焦虑要好得多。图的第一个读者是你自己,画的过程里,项目里很多没想明白的地方会浮出水面,这就是diagram-design最有价值的地方。