1. 从架构评审现场说起:为什么要把七大范式摊开重看
最近一次架构评审,讨论的是一个常规业务系统要不要拆成微服务。提方案的人一上来就说“先拆三个域,再上事件驱动”,我追问了一句:你想隔离的变化到底是什么?会议室安静了一下。这大概是过去几年架构评审里最常见的缩影——市面上对软件架构的讨论,几乎被“微服务”“事件驱动”“DDD”这些热门词垄断,更基础的那套架构设计模式反而少有人系统讲,架构范式成了ppt上的流行标签,而不是解决问题时的决策依据。
学生时代我们谈设计模式,谈的是23种GoF模式,解决的是类与对象的依赖、创建、行为问题,属于战术级别。工作多年之后你会发现,架构层面的设计模式解决的是另一类问题:系统边界划在哪里,数据往哪个方向流动,组件之间是同步请求还是异步事件,遇到突发流量时如何分摊压力。软件架构设计模式要回答的,从来不是“这个类怎么设计”,而是“这个系统在什么条件下,复杂度会从哪个方向涌进来,我们又该把复杂度挡在哪一层”。
举个更近的例子。这两年多Agent系统爆发式增长,很多人觉得主从模式、代理模式这些名词早就过时了。但如果你真的去拆解一套多Agent框架,会惊讶地发现,所谓“主控Agent调度多个subagent”,本质上还是主从/代理架构,甚至很多编排框架直接把subagent对主控暴露成一个“工具接口”,主控用function calling的方式去调用它。汽车行业从AUTOSAR CLASSIC走向Adaptive Platform,软件架构规范变得更面向服务,但内部依旧在大量使用分层、事件通知、服务发现这些经典范式。范式还是那些范式,载体变了,问题域变了,底层逻辑没变。
所以我想把这七大范式放在一起做一次系统性的横向审视。不是要给每个范式贴标签,而是梳理清楚:每个范式锁定的变化是什么,它默认接受的新风险又是什么。下面我先给出一张总表,再用三组维度把七个范式拆开讲,最后结合我在Agent项目里的实践说说新场景下的变化。你会发现,真正读懂一个架构风格,比知道多少种架构风格重要得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 七大范式速查:每个范式到底在隔离哪一种变化
2.1 一张速查表:核心思路与主要成本
我选了一个相对常见的七类切法:分层架构、微内核架构、微服务架构、管道过滤器架构、事件驱动架构、基于空间的架构、主从/代理架构。这七种不是同一个维度的产物,有些侧重技术切面,有些侧重业务边界,有些侧重数据流动,强行放在一起本身就有点“关公战秦琼”。但如果把它们都放到“隔离变化”这个统一视角下观察,很多特征立刻变得可比了。
| 范式 | 核心思路 | 主要隔离的变化 | 典型代价 |
|---|---|---|---|
| 分层架构 | 把系统按技术职责切成横切层,上层依赖下层 | 存储实现、框架选型、界面技术的变化 | 跨层样板代码多、业务逻辑容易被拆散到多个层 |
| 微内核架构 | 核心系统只保留最小闭环和扩展点,能力由插件提供 | 新扩展、第三方能力接入 | 扩展契约一旦定错,所有插件都要跟着返工 |
| 微服务架构 | 按业务边界拆分为可独立部署的服务 | 模块变化的频率差异、团队独立发布 | 分布式事务、跨服务调试、运维复杂度骤增 |
| 管道过滤器架构 | 数据依次流过处理单元,每个单元只做一次变换 | 处理规则、过滤逻辑、算法实现 | 交互性差、链路长时延累计、上下文难以共享 |
| 事件驱动架构 | 组件通过事件异步交互,发送方不知道接收方 | 发送方与消费方的生命周期、业务规则变化 | 最终一致性、重复消费、乱序、链路追踪困难 |
| 基于空间的架构 | 用分布式内存网格和无状态副本把并发流量打散 | 瞬时峰值流量、热点数据访问 | 强一致变弱、数据分片策略复杂、故障恢复依赖协调 |
| 主从/代理架构 | 一个协调点负责任务分派,多个执行者各自执行 | 任务拆分方式、执行节点组织变化 | 单点瓶颈、总控节点错误会被放大 |
如果你只记这张表,我建议你只记每一行的最后一列。架构选型的核心不是选优点,而是选你愿意承受哪个缺点。很多团队倒不是毁在选择错误,而是毁在根本没意识到自己同时把哪几个缺点买回家了。
2.2 为什么我要用“隔离变化”这个角度来归类
上面这七个范式如果按技术形态硬分,会分成“单体派”和“分布式派”,但这根本没有触及本质。我习惯把它们按“应对的不确定性来源”分成三组:
第一组处理的是系统内部边界:分层架构、微内核架构、微服务架构。它们的共同问题是“哪些代码应该放在同一个变更单元里”;第二组处理的是组件之间的流动:管道过滤器架构和事件驱动架构。它们的共同问题是“数据从一个模块到另一个模块,是同步走完还是异步放开”;第三组处理的则是规模与协同:基于空间的架构和主从/代理架构。它们的共同问题是“如果负载暴涨,或任务复杂到单点撑不住,应该怎么分摊”。
用这个方式去做系统审视,有一个额外的好处:当评审会上有人抛出一句“我们用事件驱动架构吧”,你不会急着去背事件驱动的概念,而是会下意识反问一句:这里到底要处理什么变化?是峰值流量,发布节奏差异,还是数据链路过长?句式一旦换掉,很多伪架构决策会当场现出原形。
3. 划定系统内部边界的三种范式:分层、微内核与微服务
3.1 分层架构:用单向依赖换可替换性
分层架构是最古老、也最容易被低估的架构风格。它的核心思想非常朴素:把系统按技术职责切成横切层,上层只管调用,下层只管实现,依赖方向是单向的。典型形态是展示层、应用层、业务层、基础设施层。分层架构换来的最大好处是“可替换性”:你从MySQL切到PostgreSQL,理论上应该只动基础设施层;你换一个UI框架,展示层以下的代码不应该被大面积重写。很多后端项目里“Controller -> Service -> Repository”的经典写法,本质就是分层思想在Web应用里的简化落地。
你去看Android源码的组件划分,Activity、Fragment处理展示与交互,ViewModel维护状态,Repository再往下对接数据源,这类分层思路已经渗透到了移动端框架设计里。这也是为什么《Android源码设计模式解析与实战》这类书会那么受欢迎——不是让大家去背源码里的某个类,而是看框架设计者如何把边界“焊死”,让团队在多人协作时不至于把依赖关系写成一团乱麻。
但分层架构扛不住几类情况。第一,当团队长大了,技术分层和业务模块之间的矛盾会越来越明显。支付、订单、商品都会穿过Controller、Service、DAO各层,一旦要改支付的一个流程,你往往要同时改五个层的支付相关代码,这时“按技术切”反而成了变更的阻碍。第二,数据库仍然是单点,所有请求最终打到一个库上,分层再干净,数据库一抖动整个链路都跟着抖动。第三,业务逻辑容易出现“贫血模型”——Service层越来越厚,里面的代码渐渐从业务流程变成了几百行的大杂烩。这些信号出现时,单纯靠分层已经解决不了问题。
3.2 微内核架构:一个稳定的插座,多个可拔插的电器
微内核架构是我个人很喜欢的一种“反脆弱”设计。它先把核心系统最小化,只保留两块东西:一块是系统运行的基础能力,比如模块加载、生命周期管理;另一块是统一的扩展契约。具体业务能力由插件完成,每个插件都实现同一套接口,接入时只要把插件放到指定位置,核心系统就能发现并加载它。
你日常用的IDE基本就是微内核的典型代表。编辑器只是核心,语言支持、版本控制、代码格式化全是插件。Eclipse很早就靠OSGi把插件化做到了极致;IDEA虽然商业闭源,但它的插件市场依旧依赖同一套心智模型。放到服务端,Java的SPI机制就是微内核的一种具体实现,Spring Boot的自动装配也把大量能力交到了“外部starter”手里。规则引擎、工作流引擎、车载SOA里的能力抽象层,都能看到类似的影子。
微内核最大的优点是“增加新的扩展不需要改动核心”,这对很多业务场景是决定性的。例如一套交易风控系统,经常要接入新的风控厂商,每接一家写一个插件,核心流程完全不动。它不是没有代价,代价在于扩展契约的制定极度困难。接口定细了,插件实现被束缚;接口定粗了,每个插件都会往上下文里塞自定义数据,契约名存实亡。我见过不止一个项目,号称微内核,最终核心代码里堆满了各种if判断插件的“特殊处理”,契约变成了摆设。
3.3 微服务架构:把团队和运维边界也放进了系统画布
从形态上看,微服务像是一个分布式版的分层,但从设计初衷看,它更像把“分层原则”应用到了团队与组织边界上。一个微服务对应一个明确的业务能力边界,由一个小团队长期维护,可以独立发布、独立扩容、独立故障。它与DDD结合时,最核心的概念是“限界上下文”——支付上下文里的订单和物流上下文里的订单,根本不需要是同一个数据模型,也不用共享同一张订单表。
微服务真正隔离的,是“变化频率的差异”。如果电商平台里的订单模块每天都在改,而优惠券模块一个月才动一次,把它们捆在同一个单体里意味着每一次发布都要一起上线。拆开之后,订单团队可以每天发版,优惠券团队按自己的节奏走,互不阻塞。这才是微服务的第一价值,性能弹性之类其实只是副作用。REST还是RPC,Kafka还是RabbitMQ,都不决定你是不是微服务;独立生命周期才是它的核心判据。
代价同样是系统性的。首先是跨服务事务不再有数据库本地事务兜底,开发人员必须设计Saga、Outbox、幂等表这些东西;其次是测试和排障从进程内变跨网络,一个调用链可能要翻四五个服务的日志;再其次是团队的发布节奏如果没跟上,微服务会退化成“分布式的单体加一堆网络调用”。对于两三个人的创业团队,微服务通常不是一个好选择;如果业务模块间的变化频率差异不大,拆微服务还会把内部重构的成本抬高好几倍。这些踩过的坑,我在后面第7部分会再具体展开。
4. 让数据在组件间流动的两种范式:管道过滤与事件驱动
4.1 管道过滤器架构:数据流经过一道一道工序
管道过滤器架构把系统想成一条流水线,数据从起点出发,经过一个个过滤器,每个过滤器只负责一种处理,处理完把结果顺次交给下一个。Unix命令行的“ps -ef | grep java | awk '{print $2}'”就是最直接的例子;ETL任务、日志清洗、音视频转码、编译器的词法分析到语法分析再到代码生成,都是这套拓扑的典型代表。
它的优势在于单一责任和可组合性:要加一个特殊日志过滤规则,不需要改上游和下游,只要往管道中插入一个新的过滤器;要换掉某个敏感词过滤算法,只要保证输入输出格式不变,过滤器内部可以随便重写。用这套架构建模时,一个组件容易测试,组合起来的路径也容易理解,调用链清晰没有任何“突然冒出来的异步跳跃”。
局限也很明显。管道拓扑天然是单向的,很难以自然的方式支持“用户交互完成后又要回去改某个前置环节”的流程。如果链路太长,每一个过滤器耗时会直接叠加,数据被逐级处理完之后才能交付,很难做局部响应。更麻烦的是,如果过滤器之间需要共享复杂上下文,比如处理一个订单不仅要判断黑名单,还要查它的历史消费记录,你通常只能在数据模型里塞一堆额外的上下文字段,管道的“干净”就保不住了。所以它适合确定性的数据处理,不适合高交互、强回退的业务系统。
4.2 事件驱动架构:先解耦,再面对一系列“已经发生的事”
事件驱动架构是过去十年被用滥、也最容易被误用的架构风格。它的核心区别在于:传统请求驱动里,A调用B并等待B返回,A知道B是谁;事件驱动里,A只发布一条“某某事情已经发生”的消息,至于谁在听、听了之后做什么,A完全不知道。这套机制带来的是时间和空间的双重解耦。空间解耦是发布方不依赖消费方地址;时间解耦是发布和消费不需要同时在线,消息可以先落地,消费方有空再来处理。
典型场景是下单后的
