ARTICLE DETAIL

资讯详情

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

架构图重构:用C4模型厘清系统边界与依赖

架构图重构:用C4模型厘清系统边界与依赖 前阵子接手一个历史项目代码能跑但团队里没人说得清“这个服务为什么在这里”“那两个模块之间到底是什么关系”。每个人脑子里都有一张不同的架构图开会各说各话。这大概是很多团队的真实状态。今天我们聊的就是“架构图重构”这第一笔——不是用画图软件把旧图重绘一遍而是用一张图把系统的真实边界、依赖和决策重新串起来。这也是“架构师觉醒从重构到引领”系列第2集的核心架构师不必记得每条代码路径但必须能在画布上用第一笔把系统的骨架和边界表达清楚。网上有个说法三流架构师照着别人的图画架构图二流架构师照着自己的代码画架构图一流架构师让架构图带着代码演进。这话虽然带点调侃但点破了一个真相——架构图重构这件事的难点从来不在“画”而在“想”。本文不讲虚的直接说清楚架构图重构的目标、准备工作、C4分层画法、一个真实项目的实操记录以及图怎么维护才不变成墙纸。1. 为什么要重构架构图从“会画”到“有用”1.1 架构图不是一张图是一段思考过程很多同学把架构图当成“交付物”画完发给领导、贴进文档就完事了。这是最大的误区。架构图本质上是一段思考过程的快照你对系统边界的判断、对依赖方向的取舍、对模块归属的定义全部凝固在图上的方框和箭头里。重构架构图就是把这些判断重新翻出来审视一遍。系统的业务在变、团队在变、技术栈在变旧图上那些“当初合理”的边界和依赖现在可能早就变形了。比如最早把用户模块和订单模块画在一个方框里因为当时订单逻辑简单后来订单逻辑膨胀成十几个类、三张表还是放在同一个方框里结构上就出问题了。画布上的第一笔就是逼自己回答一个问题当前这个系统的现实结构和我认为的结构差距在哪里所以重构架构图的第一步不是打开画图工具而是进入“审视模式”。把系统当成一个陌生项目去读不带原有的心理预设。这个过程通常很不好受因为你会发现自己之前引以为傲的设计在真实代码面前漏洞百出。但这是架构师觉醒的必经之路。1.2 旧架构图的四宗罪我经手过不少遗留系统它们的架构图几乎逃不出四个问题第一宗罪是“大泥球”。所有模块画在一个大框里框内密密麻麻塞了几十个方块看不出边界。这种图的信息量约等于零因为它没有表达任何结构。第二宗罪是“颜色地狱”。用红橙黄绿青蓝紫区分模块图例占半页你把颜色对应到方块上要数半天。颜色作为辅助编码可以但不能作为主要表达手段。第三宗罪是“节日贺卡图”。比例失调、箭头穿透方框、文字斜得扭脖子美观有余但信息混乱像过节群发的贺卡没人愿意细看。第四宗罪是“孤岛图”。画完就归档代码演进六个月后图彻底失真。新同事看这张图反而被误导。这类图比没有图更危险。重构的目标就是把这四类图变成“演进图”边界清晰、依赖有向、层级分明、随代码更新。我不追求一次画到完美先求得一个“能支撑拆解决策”的可信版本。1.3 重构架构图的三个真实收益有人问重构架构图到底能带来什么实际好处我的归纳是三个决策有依据、团队有共识、演进有基线。决策有依据说的是拆分微服务、模块合并、中间件选型这类事不用再看代码猜了。图上箭头指向一目了然谁依赖谁、谁是底层支撑、哪块被三个模块引用、哪块像挂在系统上的寄生藤直接呈现在眼前。团队有共识说的是产品和后端、前端和后端、新人和老人开会时终于能盯着一张图说事。大家看到的是同一个边界而不是各自脑补的系统。这张图就是团队对系统的“共同基线”。演进有基线说的是每次重构动刀前后都把架构图当作对照底稿。改完一处图跟着动一笔图上的依赖变少重构才真正落地。图不是目标的终点而是过程中一把随时拿来量一量的尺子。2. 画之前要做的事现状梳理与边界定义2.1 先收集事实而不是先开画架构图重构最忌讳一上来就画。你得先回答“现在的系统到底是什么样”这个客观问题。我一般从四份素材里找事实代码仓库的目录结构。看包名、模块名、服务名这些名字是最诚实的告白。一个叫order-api的模块依赖一个叫common-utils的模块那它们的关系就写在文件名里。接口与消息队列清单。Controller 路由、RPC 接口、MQ Topic把每一组“谁调用谁”记录下来这就是箭头的来源。我习惯用一段简单的脚本扫出所有 Feign Client 接口名和 RabbitListener 指定的队列名生成资源清单。数据库的库表归属。看每个服务是否独占自己的库表还是多个服务共用一个库这直接反映服务边界的真实情况。共用库表几乎总是边界模糊的信号。部署与配置清单。看线上到底部署了几个进程、几个节点、哪些服务通过配置中心互相引用地址这能暴露代码里看不到的运行时依赖。这一步的产出是一张“现状资源清单”可以用表格记录调用方、被调方、调用方式HTTP/RPC/MQ/共享DB、调用频率猜测。有了这张表后面的图才有事实支撑。2.2 明确图的服务对象和图幅很多架构图失败是因为连“给谁看”都没想清楚。给CTO汇报的架构图和给新人讲解代码的架构图内容完全不同。我在动笔前会问自己三个问题第一读者是谁如果是技术决策层关心的是服务边界、依赖关系和技术选型合理性如果是开发团队关心的是模块归属、接口流向和部署形态如果是运维关心的是进程、端口、数据存储位置。第二这幅图做什么用支撑拆分方案评审解释线上故障的依赖链路还是帮助新人定位修改位置用途不同图的颗粒度完全不同。第三图幅有多大一张图装不下整个世界。系统上下文、服务容器、模块组件、代码类这四层信息放在四张图里而不是揉在一张巨图里。我见过太多失败案例都是想“一图全览”结果任何一层都没看清。2.3 工具选型找个能进版本库的画布工具这块我这些年基本形成了一个原则能文本化、能进Git、能自动生成优先级最高。因为架构图是要随代码演进的二进制图形文件在合并冲突、历史追溯方面实在很难用而文本绘图可以 diff、可以 code review。个人常用的四类工具各有利弊下面这个表可以直接参考工具优点缺点适合场景PlantUML文本作图、支持C4宏、版本友好、免费布局自动生成复杂图偶尔错位C4四层图、时序图、部署图Mermaid渲染轻量、GitHub原生支持、上手快表达复杂依赖时略显吃力快速示意图、文档内嵌图draw.io所见即所得、模板丰富二进制文件难diff协作靠手动一次性对外汇报图、非技术场景Excalidraw手绘风、适合白板讨论不易沉淀为长期维护物头脑风暴、快速建模推荐组合是日常架构决策用 PlantUML 的 C4 模板画完顺手生成图片放入文档对外汇报用 draw.io 做美化版团队白板讨论用 Excalidraw。三种工具服务不同场合核心模型只在 PlantUML 里维护避免多处维护不一致。3. 核心方法C4模型下的一笔一画3.1 Level 1 系统上下文图先画外部世界C4 模型把架构图分为四个层次上下文、容器、组件、代码。架构图重构的“第一笔”应该从 Level 1 系统上下文图开始。系统上下文图的主角是“这个系统本身”和“它周围的人”。人包括用户角色、外部系统、第三方服务。这张图不画内部结构只画边界系统对外提供什么依赖外部的什么。它回答的问题是我们的系统在更大的生态里处在什么位置。画这一层最大的价值在于边界意识的建立。很多重构的坑“坑”在没搞清楚系统的外部依赖和真实用户就动手了。比如一个订单系统真正的用户不只是 C 端消费者还有后台运营、客服系统、供应链系统、财务系统。漏掉一个外部依赖架构图就不完整拆解方案就可能在后续被某个“隐藏用户”打乱。3.2 Level 2 容器图把系统拆成可部署单元Level 2 容器图里“容器”指的是可独立部署/运行的单元微服务、Web应用、后台任务、数据库、消息队列等。这一层是微服务架构图的主角也是多数人最熟悉的“微服务架构图”的样子。画这一层的核心动作是决定哪些功能属于同一个容器哪些必须拆出去。判定依据通常有三条独立生命周期、独立伸缩需求、独立故障边界。如果一个模块的发布频率明显高于其他模块且它崩了不影响核心链路就有充分的理由独立成容器。这层图上的箭头含义必须统一。我用一种约定实线箭头表示同步调用HTTP/RPC虚线箭头表示异步消息MQ细线双向表示共享数据库访问但方向弱化。箭头上标注接口名或 Topic 名不标没有意义。很多图画了大量箭头但读者不知道这些线是什么含义图就白画了。3.3 Level 3 组件图打开容器看零件Level 3 组件图针对某一个具体容器画出内部的“零件”哪些类/模块负责接口、哪些负责业务规则、哪些负责数据访问以及它们之间的关系。这层图不是给所有人看的是给要动这个容器的开发团队看的。画这一层时我坚持“一个容器一张组件图宁缺毋滥”。只有这个容器需要重构、需要被仔细拆解时才画它的组件图。至于底层代码实现细节交给代码注释和单元测试架构图上不堆砌类名。在组件图里最容易发现的就是“假容器、真泥球”。表面上看这是一个独立的订单服务打开组件图才发现里面塞了库存的缓存预扣、支付的回调处理、营销活动的优惠计算边界纠缠不清。组件图的意义就是揭开这层遮羞布。3.4 避坑依赖方向与边界划法架构图重构中箭头方向最容易画错而方向恰恰是最有价值的信息。我的约定是依赖被依赖方箭头从依赖方指向被依赖方。不能在图中一会儿“谁调谁”一会儿“谁被谁调用”混用箭头语义会让整张图的推理作用归零。依赖方向的判定有个简便标准谁发生变更会影响对方A 改了导致 B 要跟着改就是 B 依赖 A箭头从 B 指向 A。这比看运行时调用的代码更容易判断因为它在评估“影响范围”而非仅仅观察“调用行为”。边界划法上一个核心原则是“边界要划在变更频率不一致的地方”。订单模块和支付模块如果一周变更十几次一把梭放在一起每次发布都互相牵连那它们之间就是一条应该画出来的边界。相反两个技术组件虽然代码上是两个模块但如果它们总是一起变更、一起发布、部署在同一进程里画图时也可以先作为一个容器对待。4. 实操记录一个微服务系统的架构图重构4.1 重构前的原始状态上季度接手了一个典型的业务系统姑且叫它“订单中台”。代码仓库里有一份半年前的架构图画的是五个服务网关、用户、订单、商品、支付。看这张图一切井井有条。但真正接入手后发现完全不是那么回事支付服务的代码里直接访问了用户服务的数据库表。严格说它俩是“共享库”但图上是虚线松耦合表示异步调用。订单服务里塞了一个“营销计算器”的模块它既读商品服务的价格表又通过 Feign 调营销服务获取券信息。论运行功能它散落人间。网关服务的过滤器链里藏了一段日志上报逻辑直接往外部日志平台发数据绕过了所有内部服务。如果用旧图去指导拆分只会按图索骥把本来纠缠的东西自然保留。这张图给出的边界是虚假的边界。4.2 分步处理理清单、画事实、找病灶我第一步做的事情是把统计到的接口调用和库表归属做成了现状资源表并且把旧架构图丢到一边基于代码事实画了一张“现状态图”。现状态图不追求美观只有方块和箭头甚至故意画得乱一点。画的过程中出现了几个关键发现订单服务到用户数据库的访问路径画出来就是一条“越界大动脉”。这一步直接暴露了共享库问题也决定了后面拆分时“用户库必须先独立、支付服务必须改走接口”的处理顺序。营销计算器被三个服务引用画出来像个“中心枢纽”但它不属于任何一个基础设施层而是混杂在订单模块里的业务组件。这说明营销能力该单独沉淀成服务或者明确下沉为公共模块。网关过滤器里的日志逻辑画出来发现网关与外部日志平台之间存在一条数据通路这在架构上没有统一出口。这里的处理不是立刻重写而是先明确“统一接入日志服务”的新目标。4.3 重构后的图形结构在现状态图基础上我和团队定下了目标架构图。对比一下重构前后的结构差异重构前依赖关系呈“网状发散”随便拉两个服务之间都有线没有人能在脑海里展开这张图。重构后依赖关系是分层的上层业务流订单、营销、商品依赖中层领域服务用户、支付、库存中层又依赖底层基础设施配置中心、日志平台、网关路由。每条依赖箭头都清晰指向一个方向。边界上的变化也很大。用户数据访问权限收口到用户服务外部一律通过接口获取用户信息。营销计算器从订单服务中迁出沉淀为独立的营销能力服务同时挂到中台的能力开放层。网关里的日志逻辑剥离统一收集到日志服务处理。这版图不是一天画出来的中间过了三轮评审每轮都会发现“旧人情结”在作祟。有人觉得“支付读一下用户库也没啥省事”这种观点必须被图上那条越界箭头和政策决定压过。图是我们的共同基线不是哪个人说省事就能绕过的。4.4 用架构图反推并发现问题架构图不只是结果我还是把它当排查工具来用。图上每一处“环形依赖”几乎都对应着一处真实的代码异味。比如在现状态图里订单服务调营销服务营销服务又回调订单服务查订单状态形成一个小环。这种环形依赖在运行时不一定会出错但它意味着两个团队无法独立发布——任何一方的改动都可能波及另一方。看到这个环对应的动作就是定下一个决策营销服务查询订单状态必须改成读订阅的订单事件或者查询只读视图绝不允许反向调用线上接口。再比如“明星依赖”问题。用户服务被几乎所有服务依赖图上一看五六个箭头都指向它。这个节点一抖动全站雪崩。架构图上这种“明星”节点一旦出现就要考虑加缓存、加降级、物理隔离或者在能力开放层加一层适配。没有图的时候这些问题分散在告警系统里每次都像在打地鼠。另一点要提醒架构图重构过程中不是所有问题都要当场解决。我们的原则是“看见、记录、排期”。图上标出来列入技术债清单设定处理优先级比当场修更重要。重构的目标是把结构画清楚不是顺手把系统改完一步步来才不会失控。5. 常见问题速查与长效维护5.1 架构图维护的六个高频坑维护架构图这事踩坑的人很多我整理成了一张速查表看一眼就知道怎么处理问题现象根因处理方式图上没有分层全部平铺没想清楚读者是谁按C4分层一个人一张图只看一层箭头满天飞但无标注依赖语义混用统一箭头含义标注接口名或事件名图例比图还长颜色表示过度颜色只用于辅助区分不承载核心信息新旧混画图里一半代码一半概念层级跳变明确每张图的颗粒度不混层画完三个月没更新缺少演进制度把架构图纳入代码评审前置门槛一张图上画满50个模块贪大求全坚持一张图一个主题必要时画多张细看这些坑根子都在一条图没有和“决策”绑定。如果一个图不服务任何决策就没人有动力维护它。5.2 让架构图和代码同步演进架构图和代码同步这是维护的核心。我见过不少团队把“架构图更新”当成一个年底任务结果每次都是年底花一周重画一次平时没人管。要想真正同步就得把“更新架构图”这个动作嵌入到日常开发的流程里。我实践下来比较有效的一个动作是在 MRMerge Request模板里增加一个勾选项——“本次变更是否涉及架构图所示的服务/模块边界如果是请同步更新架构图并附带截图”。这个勾选项逼着开发者在提交代码时思考自己的变更落在哪条边界上。另一个动作是“架构评审作为发布前置条件”。凡是涉及新增服务、新增对外接口、新增共享库访问的变更强制走一次架构图对照评审。评审不通过不能合入主分支。这个制度一开始会被开发团队嫌弃觉得“画个图还要审批”但坚持两三个月所有人都会认可它带来的确定性——没有人在合代码的时候才突然悟出“这个模块原来不该放这里”。5.3 架构决策记录与图互相印证长期维护架构图光有图还是不够。我建议你从重构的第一天起就开始同步维护一份 ADRArchitecture Decision Record架构决策记录。每做一个重要边界决策比如“支付服务独立访问用户接口”就写一条轻量记录背景、决策、影响、替代方案。架构图和 ADR 互相印证。图表达“最终结果”ADR 表达“为什么是这么定”。新人接手的时候看图是“What”读 ADR 是“Why”。这份组合才是完整的架构知识沉淀。ADR 怎么写才不劝退我自己的模板就四段背景一句话、约束有哪些限制、决策定了什么、后果带来的好处和代价。写一页以内控制在 10 分钟能完成。时间拖长了坚持不住。这样坚持一年团队就拥有了一本活的“架构演进史”。提示系统架构师考试近年来也很看重“分层”和“边界分析”这类能力考纲中架构设计部分反复强调从逻辑架构、物理架构、运行架构等不同视图描述系统。这个 C4 分层习惯对备考同样有效画图时养成分视角建模的肌肉记忆比考前突击背概念来得扎实。6. 从画布到引领架构图如何驱动团队6.1 静态图之外也要会看动态图架构图重构到一定程度你会发现静态图只表达“结构的截面”而系统真正的复杂度往往体现在“运行时的动态交互”上。接口的时序、故障的扩散、数据的流转这些信息静态图表达不了。我的处理方式是不强行把动态信息塞进架构图而是用“架构图时序图”组合。架构图负责规划边界时序图负责描述关键路径的交互流程。画关键业务链路的时序图时脑海里要有架构图的坐标系时刻知道正在画的这条交互序列落在哪条服务边界上。对架构师来说静态图是骨架动态图是行为。两者配合才能在评审别人方案时直接指出“你这个交互流程跨了三个服务边界失败处理的归属没有写清楚”之类的问题。6.2 用架构图做技术评审和技术决策技术评审会议最怕没有共同语境后端说这个接口要加到订单服务前端说为什么订单服务又要加一个通知方法产品说这不是我们当初说的模块划分。有了架构图评审会就变成了对着图上的方块和箭头讨论问题。评审时我常用的三连问这个变更落在图上哪个方块里它和相邻方块的关系是加箭头还是改箭头改动后是否会形成新的环或新的明星节点这三连问看起来简单但在架构评审里效果很好能把讨论话题从“怎么实现”拉回“边界怎么划”让技术方案从一开始就走在正确的架构方向。画图还有一个不容易察觉的作用它是架构师建立“可信度”的工具。你拿着代码里挖出来的真实依赖关系图去谈拆分和你拍着胸脯说“我觉得这里该拆”力度完全不同。图是思考的外化这是“画布上的第一笔”真正的分量。6.3 从“画图的人”到“引导的人”回到开头那个说法三流架构师画图一流架构师引领。但“引领”不是脱离架构图、站在高处指手画脚。我的理解恰恰相反引领的前提是你比团队所有人更熟悉这张图上每一条边的来历和后果。你不是在画图你是在为团队建立一套认知系统的坐标系。这个坐标系一旦建立后续的每一次重构、每一次容量评估、每一次故障排查都挂在这个坐标系上。架构师从“被技术债追着跑”的状态变成了“带着团队看清结构再动手”的状态。这就是“从重构到引领”的真正起点。经历了这次架构图重构我最大的体会是重构掉的不只是图上那些混乱的线条更是自己心里对系统的模糊感觉。第一笔落下去的时候你就已经回不了头——因为你开始相信任何一团乱麻都能理出结构任何一张模糊的图都能被画清晰。最后一招想分享给卡在“不知道从哪里开始”的朋友挑一个你手头最痛的系统不要画宏大图只画一张 Level 1 系统上下文图加一张 Level 2 容器图把“现状”如实画出来就行不用美化。画完你会发现问题不是不知道怎么办而是从来没有把问题看完整过。
返回列表