Skip to content

设计原则与架构演进 ​

把单一职责、开闭、依赖倒置等设计原则翻译到架构层,理清单体到集群、微服务、平台化的演进驱动与代价,并识别过度设计信号。


一、设计原则的架构层解读 ​

1.1 从代码原则到架构原则 ​

代码层的 SOLID 原则讨论的是"类与接口怎么设计",架构层讨论的是"模块、服务、系统边界怎么切"。同一个名字,尺度不同,判断方法也不同:

原则代码层语义架构层语义
单一职责 SRP一个类只有一个引起它变化的原因一个模块/服务只承担一条变化轴上的职责,某个业务变更只应触及一个服务
开闭原则 OCP对扩展开放、对修改封闭(继承/多态/策略)通过扩展点(SPI、事件、插件、网关路由)演进,而不是改既有服务内部
依赖倒置 DIP依赖抽象而非具体实现上层不依赖下层实现:领域层依赖仓储接口、调用方依赖服务契约,用防腐层隔离外部系统
接口隔离 ISP客户端不依赖不需要的接口服务契约按消费方裁剪,避免"大而全"的共享 API 造成的强耦合
里氏替换 LSP子类可替换父类服务提供方的契约演进必须兼容存量消费者,版本兼容是架构层的 LSP

判断尺度:代码层看"一个类",架构层看"一次变更的波及面"。架构设计的高频追问应当是"这条需求要改几个服务、几个模块"——数量越少,SRP 在架构层越成立。

1.2 单一职责在架构层的两种切法 ​

  • 按技术层次切:网关层、应用层、数据层各司其职,纵向复用基础设施;
  • 按业务能力切:订单、库存、支付各自成域,横向隔离变化。

真实系统两者叠加,先按业务能力纵切(变化隔离),再在每个能力内部按层次横切(关注点分离)。常见错误是只按层次切(如"Controller 工程""DAO 工程"),导致一次业务改动要横穿多个工程发布。

1.3 开闭原则的架构实现手法 ​

架构层"不修改就扩展"主要靠四类机制:

  1. 插件/SPI:预留扩展点,新实现通过注册接入,不改核心代码;
  2. 事件驱动:核心流程只发领域事件,新增消费者(短信、审计、风控)不触碰核心链路;
  3. 策略路由:网关按规则路由到新老实现,实现新旧能力切换不修改调用方;
  4. 数据驱动配置:把易变规则外置到配置中心或规则引擎。

反模式:为了"扩展"提前抽象出三层接口、五个工厂,但唯一实现只有一个——这属于把开闭原则用成了过度设计(见第三章)。

1.4 依赖倒置与防腐层 ​

架构层最常见的依赖倒置应用是领域层不依赖基础设施:领域代码只依赖仓储接口,MySQL/Redis 的实现细节由基础设施层提供。向外对接第三方系统(短信、风控、旧系统)时用**防腐层(ACL)**包一层,把外部模型的"方言"翻译成内部领域语言,避免外部模型污染核心模型。

典型错误是 Controller 直接操作 DAO、Service 直接 new 具体客户端,导致领域逻辑与中间件 API 缠绕,中间件升级即全量返工。

1.5 原则冲突时的取舍顺序 ​

原则之间会打架,按以下优先级取舍可减少纠结:

  1. 先保可用性与可运维性(架构首先要能活下来);
  2. 再保变化隔离(SRP/OCP,把最常变化的业务隔到最小边界);
  3. 最后才优化代码复用(复用是手段不是目标,跨边界强行复用往往制造耦合)。

记住:架构层"复用"的优先级低于"隔离"。为了复用把两个变化频率不同的模块捏在一起,等于同时违反 SRP 与 OCP。

1.6 架构原则速查表 ​

场景信号该用的原则反例
一次改单影响多个服务SRP把用户与订单逻辑放同一服务
新接入一个外部渠道要改核心代码OCP / 防腐层支付渠道逻辑散落在订单流程各处
领域代码混着 MyBatis/SQLDIPService 直接注入 Mapper
上游接口一改下游全炸契约/版本管理、LSP共享 DTO 随意删字段
所有消费方被迫传一堆无用参数ISP一个万能 API 服务所有客户端

二、架构演进路线 ​

2.1 演进驱动因素总览 ​

架构不会自发变复杂,驱动演进的通常是四类力量:

力量典型信号引发的结果
业务复杂度上升模块间互相依赖、改一处崩多处模块化/服务化
流量上升CPU 打满、DB 连接耗尽集群化、加缓存、读写分离
团队规模上升提交冲突频繁、发布互相阻塞按团队边界拆分、服务化
部署与可靠性诉求发布即全停、故障全波及独立部署、故障隔离、灰度

演进的正确姿势是"信号驱动"而非"潮流驱动":只有出现上面某一列的信号,才考虑进入下一形态。

2.2 阶段一:单体架构 ​

  • 形态:一个进程、一个数据库、一套代码库,模块按 package 分层。
  • 优势:开发简单、调试直观、事务天然、部署运维成本最低,是 0 到 1 阶段的最优解。
  • 代价:代码库膨胀后构建慢、启动慢;模块间只能靠约定约束边界(package 边界可被轻易绕过);任何变更都是全量回归;水平扩展只能整机复制,DB 成为单点。
  • 适用边界:团队 10 人内、业务逻辑以简单 CRUD 为主、日活量级小。

单体架构本身不是错误,错误的是"业务已经长成多个域,却还硬塞在一个进程里"。

2.3 阶段二:集群化与纵向分层 ​

流量先于业务复杂度到来时的首选演进,不拆服务也能抗量:

  1. 无状态化:Session 外置到 Redis,任何实例可被替换;
  2. 反向代理 + 负载均衡:Nginx 分发流量,多实例水平扩展;
  3. 缓存前置:Redis 挡住热点读,降低 DB 压力;
  4. DB 读写分离:主库写、从库读;
  5. 动静分离:静态资源上 CDN。
  • 优势:架构改动小,收益立竿见影,仍是单体代码库。
  • 代价:有状态逻辑(定时任务、本地缓存、文件存储)需要改造;分布式下的事务与锁问题初现(需要分布式锁、最终一致性);运维对象从 1 个进程变成 N 个实例加一堆中间件。
  • 关键判断:如果这个阶段的问题只是"性能不够",不要用拆微服务来解决——那是用复杂度换性能的亏本生意。

2.4 阶段三:服务化与微服务 ​

  • 驱动:业务复杂度和团队规模信号(模块耦合、发布阻塞、单点故障波及全站),而不是流量。
  • 形态:按业务能力/子域拆成独立可部署服务,服务间通过 REST/gRPC 或 MQ 协作。
  • 优势:独立开发、独立部署、独立伸缩、故障隔离、技术栈可选。
  • 代价(必须清醒认识的账):
    • 基础设施成本:注册中心、网关、配置中心、链路追踪、日志聚合缺一不可;
    • 一致性复杂度:跨服务事务只能退化为最终一致性;
    • 联调与治理成本:接口契约管理、灰度、全链路排查难度显著上升;
    • 团队要求:需要能独立负责端到端的服务 Owner,否则只是把单体乱拆成分布式单体。

2.5 阶段四:中台 / 平台化 ​

  • 驱动:组织内多条业务线反复出现同质能力(账号、支付、消息、风控),单点重复建设。
  • 形态:把共享能力沉淀为平台服务(技术中台/业务中台),前台业务(如多个 App、多条产品线)复用。
  • 优势:避免重复建设、统一标准、数据打通。
  • 代价:
    • 抽象前置风险:在需求尚不清晰时抽象共享模型,容易造出"没人用对的中台";
    • 组织博弈:中台团队与业务团队之间出现"中台化即权力收编"的阻力;
    • 中台服务变成新的单点与瓶颈:所有前台依赖它,它的变更影响面巨大。
  • 现实教训:很多中台项目失败不是技术问题,而是把"组织能力复用问题"误当成了"技术平台问题"。中台应自下而上从成熟能力沉淀,而非自上而下先建框架再等业务。

2.6 各阶段代价对照表 ​

阶段解决的核心问题新增的核心代价什么信号出现再走下一步
单体快速验证业务协作与扩展瓶颈模块耦合、团队冲突、构建变慢
集群化性能与可用性中间件运维、有状态改造单域代码超过团队维护能力
微服务独立演进与故障隔离基础设施、一致性、治理成本多条业务线共享能力重复建设
平台化跨线复用抽象前置、组织博弈数据/团队各自为政无法打通

2.7 演进不是必然:停在当前形态 ​

每一步演进都是"拿复杂度换能力",而能力当前用不上就是纯亏损。以下情况应明确停在原地:

  • 日活低、变更少的小系统:集群化都不需要,一台高配机器 + 备份即可;
  • 没有独立团队的单体:拆分后没人能维护独立服务的生命周期;
  • 业务形态还在剧烈变化:过早拆分/中台化,抽象会被业务反复推翻。

架构演进是"负债表"管理:技术债与复杂度债要匹配业务能产生的收益。


三、过度设计的信号与防范 ​

3.1 什么是过度设计 ​

过度设计 = 为当前不存在的复杂度预先付出成本。它与"前瞻性设计"的区别只有一个判断标准:这个抽象在可预见的 1~2 个迭代内是否会被用到。会用到的是前瞻,不会用到的是过度。

3.2 典型过度设计信号清单 ​

信号典型表现潜在后果
为低流量系统上微服务单体就能支撑,却引入注册中心+网关+分布式事务排障链路成倍拉长,发布效率反而下降
接口三层抽象只有一个实现Service 接口 + Impl + 代理层,无第二实现每层都是纯转发,徒增跳转与阅读成本
为不存在的问题做通用化提前做多租户、国际化、可插拔规则引擎需求方根本不在,代码无人敢删
用分布式事务解决所有一致性问题把能合并的服务硬拆开,再上 Seata/XA用最大的复杂度换了本可避免的跨服务问题
代码层 YAGNI,架构层却"凡事都要留扩展点"所有模块都插件化、配置化核心逻辑被扩展点切碎,可读性崩塌
中台化/数据中台先行组织没有多条业务线,先搭中台等业务平台空转,成为成本中心

3.3 必要复杂度 vs 偶然复杂度 ​

  • 必要复杂度:业务本身固有的复杂度(如支付必须对账、库存必须防超卖),删不掉,只能建模好;
  • 偶然复杂度:由技术选型与实现方式引入的复杂度(如为做微服务引入的注册中心、为做缓存引入的一致性问题)。

设计者应把精力投入"降低偶然复杂度":当引入一个技术方案时,先问"它消灭了什么复杂度,又引入了什么新复杂度,净值是否为负"。DDD、微服务、中台在引入前都应过这道"净值"账。

3.4 防御过度设计的实践 ​

  1. YAGNI(You Aren't Gonna Need It):不为"也许将来"写代码,为"现在 + 已排期的下一个迭代"写代码;
  2. 演进式架构:把"何时拆分、何时加缓存"这类决策延后到信号出现时,用 ADR(架构决策记录)追踪"当时为何不做";
  3. 最小可行架构(MVA):每次架构动作只解决当前最痛的 1~2 个问题,而不是一次到位;
  4. 重构通道:代码腐化不可怕,可怕的是没有定期还债的通道;把"删无用抽象"当成和"加新功能"一样正当的工作。

四、示例演进案例 ​

4.1 案例背景 ​

一个电商后端,从创业到稳定运营,经历了典型四阶段演进。下表记录每阶段的触发信号与决策依据(重点不是结论,而是"决策是怎么做出来的")。

4.2 阶段一:单体(0 ~ 100 万 GMV) ​

  • 形态:Spring Boot 单体 + MySQL,模块为 user / order / product / pay 四个 package。
  • 触发信号:无,业务尚未验证,团队 5 人。
  • 主动不做的事:不拆微服务、不做缓存预热、不上 MQ。理由:业务逻辑全部是简单 CRUD + 一次事务,微服务只会放大问题。

4.3 阶段二:单体 + 集群 + 缓存(100 万 ~ 3000 万 GMV) ​

  • 触发信号:大促时 DB CPU 飙高、单机 Tomcat 线程池打满、凌晨定时任务拖慢交易。
  • 动作:应用无状态化 + 双实例部署 + Nginx 负载均衡;商品详情 Redis 缓存(解决读热点);订单写库保持不变。
  • 代价记录:Session 外置、定时任务加分布式锁防重复执行、缓存一致性引入少量脏读容忍。
  • 决策复盘:此时"改单服务"仍只需一次发布,说明没有拆服务的必要。

4.4 阶段三:按业务能力拆微服务(3000 万 ~ 2 亿 GMV) ​

  • 触发信号:订单模块一次改动牵连商品/库存多处回归;交易与后台管理系统互相拖累发布;订单团队与商品团队开始冲突。
  • 动作:先按子域切出 user / order / product / pay / inventory 五个服务(DDD 边界方法见《DDD领域驱动设计》);引入注册中心、网关、配置中心;下单链路用"本地消息表/事务消息 + 对账"替代原单体事务(取舍方法见《微服务架构与拆分》)。
  • 代价记录:新增基础设施运维;下单从强一致变为最终一致;联调成本上升,靠契约测试与统一 Trace ID 缓解。

4.5 阶段四:中台化尝试与反思 ​

  • 触发信号:公司开第二条业务线(社区团购),发现账号、支付、消息、风控都要重写一遍。
  • 动作:把已成熟运行两年以上的支付、账号沉淀为平台服务,供两条业务线共用;没有把尚未稳定的社区团购业务逻辑提前抽象进中台。
  • 反思记录:沉淀的是"验证过的能力"而非"猜出来的需求";中台团队按平台服务设 Owner,不设大一统架构组。

4.6 演进决策复盘表 ​

决策点当时不做(做了什么)后来做(触发信号)教训
首版是否微服务单体业务验证后才按子域拆信号驱动,不赶潮流
是否提前中台化先各自建设第二条业务线出现且能力成熟后抽象不能早于使用方
是否每步都上 MQ直连调用出现削峰/解耦刚需后新技术要有明确买单人

五、如何落地与常见误区 ​

5.1 用 ADR 记录架构决策 ​

ADR(Architecture Decision Record)是防止"架构拍脑袋 + 后人不知道为什么"的最小工具,每条 4 节即可:

text
# ADR-007:订单服务拆分决策

状态:已接受
背景:订单模块频繁改动牵连商品回归,发布互相阻塞。
决策:按子域拆出 order 服务,下单改为本地消息表 + 对账的最终一致。
后果:新增 MQ 依赖与对账任务;换取独立发布与故障隔离。
# ADR-007:订单服务拆分决策

状态:已接受
背景:订单模块频繁改动牵连商品回归,发布互相阻塞。
决策:按子域拆出 order 服务,下单改为本地消息表 + 对账的最终一致。
后果:新增 MQ 依赖与对账任务;换取独立发布与故障隔离。

有了 ADR,"为什么不拆""为什么上 MQ"在几个月后仍可追溯,避免重复争论与推倒重来。

5.2 常见误区清单 ​

误区正解
把架构演进当升级打怪,一路走到微服务每个阶段都是可选停留点,以信号为准
用微服务解决性能问题性能问题优先用集群/缓存/读写分离解决
用中台解决组织协同问题组织问题先用组织手段解决,中台只是放大器
为代码复用而合并边界架构层隔离优先于复用
没有 ADR,架构决策靠口口相传每个决策留痕,含"当时为什么不这么做"
重构只加不删定期清理无用抽象与过期扩展点

5.3 落地检查清单 ​

  • [ ] 当前架构形态与团队规模、流量、业务复杂度匹配;
  • [ ] 能说出最近一次演进是被哪个信号触发的;
  • [ ] 架构层改动的波及面是否遵循 SRP(一次变更尽量命中一个边界);
  • [ ] 领域/核心代码不依赖具体中间件 API(DIP 是否成立);
  • [ ] 关键演进决策都有 ADR 记录;
  • [ ] 没有为未来 1~2 个迭代用不上的能力预留抽象。