外观模式是为内部强耦合子系统提供可控、可测、可演进的调用入口,面向内部协作而非外部请求,需封装错误聚合、上下文透传与轻量协调逻辑,区别于关注业务语义的@Service。

外观模式不是给微服务“加一层壳”,而是为内部多个强耦合子系统(比如订单、支付、消息)提供一个可控、可测、可演进的调用入口。它不解决服务间通信问题,也不替代 API 网关——网关面向外部,Facade 面向内部协作。
什么时候该在微服务里写 Facade 类,而不是直接调用子系统
当你的微服务内部出现以下任一情况时,Facade 就不是“锦上添花”,而是必要隔离手段:
- 一个业务操作(如
createOrder)需要串行调用InventoryService、PromotionEngine、WeChatPayClient、SmsSender四个模块,且每步都带重试、降级、日志、事务边界判断 - 不同 Controller 或 Job 重复组装相同调用链(比如 3 个地方都手动 new
PaymentProcessor+ settimeoutMs+ 调validate()+execute()) - 某个子系统即将重构(如把本地
RedisLock替换为DistributedLockService),但你不想改遍所有调用点
这时 Facade 的价值就落地了:它把“怎么调”收口,把“调什么”留给子系统自己演进。
Facade 类不能只做方法转发,必须封装关键控制逻辑
很多人写的 Facade 只是把子系统方法名拼在一起,比如:
public class OrderFacade {
public void create(OrderRequest req) {
inventory.decrease(req);
payment.charge(req);
message.sendSuccess(req);
}
}
这等于没封装。真正有用的 Facade 至少要处理以下三类事情:
-
错误聚合与语义升维:把
TimeoutException、InventoryNotEnoughException、InvalidSignatureException统一封装成OrderCreationFailedException,并附带可读原因码(如ERR_INVENTORY_SHORTAGE) -
上下文透传与隔离:自动注入
TraceId到每个子系统调用中,避免手动 set;同时确保ThreadLocal里的用户身份不被下游污染 -
轻量协调逻辑:比如库存扣减失败时,自动触发已扣减部分的回滚(
inventory.increase(reservedId)),而不是把补偿逻辑扔给上层
Facade 和 Spring @Service 层的区别在哪
别把 Facade 当成又一个 @Service。它们定位完全不同:
-
@Service是领域行为载体,关注“做什么”(如order.cancel()、user.updateProfile()),应具备业务语义和状态变更 -
Facade是跨子系统编排器,关注“怎么串起来才稳”,本身不持有领域状态,也不实现核心业务规则(比如优惠计算逻辑仍在PromotionEngine里) - 一个微服务里可以有多个 Facade 类,按场景分:比如
OrderFacade(面向下单)、RefundFacade(面向售后),但不应出现OrderRefundFacade这种大而全的类
如果发现某个 Facade 方法里开始写 if-else 判断订单状态再决定走哪条路径,说明它已经越界,该拆或该退回到领域 Service 了。
测试 Facade 时最容易忽略的三个点
Facade 的单元测试常流于形式,真正难测的是它的“胶水行为”。重点关注:
- 子系统 mock 的返回顺序是否覆盖了真实调用链的时序依赖(比如
payment.charge()必须在inventory.decrease()成功后才发起) - 异常传播路径是否完整:当
message.sendSuccess()抛出NetworkException,Facade 是否仍能保证前两步的幂等/可补偿性,而不是静默吞掉 - 并发场景下共享资源(如缓存 key 构造器、本地计数器)是否线程安全——Facade 往往是整个服务里最可能被多线程高频调用的入口
Facade 的复杂度不在代码行数,而在它隐式承担的协调责任。写得越“简单”,背后要兜住的边界条件就越具体。


















