外观模式(Facade):微服务网关——为子系统中的一组接口提供一个一致的界面
外观模式,说白了就是给一堆复杂的子系统套个「壳」。
我刚开始接触微服务时,最头疼的就是客户端要调好几个服务。下单要调库存、支付、物流,每个接口的协议、参数、返回格式都不一样。客户端开发同学天天跑来问我:「这个库存接口怎么又超时了?」「支付回调怎么处理?」——嗯,那时候我就意识到,必须有个统一的门面。
什么是外观模式?
外观模式的核心思想很简单:为子系统中的一组接口提供一个一致的界面。它定义了一个高层接口,让子系统更容易使用。
你想想看,客户端不需要知道背后有几个服务在协作。它只需要告诉外观:「我要下单」,剩下的脏活累活,外观帮你搞定。
核心角色:
- Facade(外观):统一的高层接口,封装子系统的复杂性
- Subsystem(子系统):一组完成具体功能的类或服务
- Client(客户端):通过外观访问子系统
业务场景:下单流程
我们来看一个真实的下单场景。一个订单创建,背后要经历三步:
- 库存系统:检查商品库存,扣减库存
- 支付系统:创建支付订单,处理支付回调
- 物流系统:生成物流单,安排发货
如果没有外观模式,客户端代码会写成什么样?
// 客户端直接调用三个子系统
// 伪代码示意
InventoryService inventory = new InventoryService();
PaymentService payment = new PaymentService();
LogisticsService logistics = new LogisticsService();
// 第一步:检查库存
boolean hasStock = inventory.checkStock(productId, quantity);
if (!hasStock) {
return "库存不足";
}
inventory.deductStock(productId, quantity);
// 第二步:创建支付
String paymentId = payment.createPayment(orderId, amount);
payment.processPayment(paymentId);
// 第三步:安排物流
String logisticsNo = logistics.createLogistics(orderId, address);
logistics.ship(logisticsNo);
这段代码有什么问题?
- 客户端要了解三个子系统的接口细节
- 任何一个子系统接口变化,客户端都得改
- 事务、异常处理、重试逻辑散落在客户端代码里
我曾经在一个老项目里见过类似的代码,下单逻辑散落在十几个页面里。后来加了个新渠道,改得我头皮发麻。那之后,我养成了一个习惯:任何涉及多个子系统协作的操作,先考虑外观模式。
用外观模式重构
我们定义一个 OrderFacade,把三个子系统的调用封装起来:
public class OrderFacade {
private InventoryService inventory;
private PaymentService payment;
private LogisticsService logistics;
public OrderFacade() {
this.inventory = new InventoryService();
this.payment = new PaymentService();
this.logistics = new LogisticsService();
}
public OrderResult placeOrder(OrderRequest request) {
// 1. 检查并扣减库存
boolean stockOk = inventory.checkStock(
request.getProductId(),
request.getQuantity()
);
if (!stockOk) {
return OrderResult.fail("库存不足");
}
inventory.deductStock(request.getProductId(), request.getQuantity());
// 2. 创建支付
String paymentId = payment.createPayment(
request.getOrderId(),
request.getAmount()
);
boolean payOk = payment.processPayment(paymentId);
if (!payOk) {
// 回滚库存
inventory.restoreStock(request.getProductId(), request.getQuantity());
return OrderResult.fail("支付失败");
}
// 3. 安排物流
String logisticsNo = logistics.createLogistics(
request.getOrderId(),
request.getAddress()
);
logistics.ship(logisticsNo);
return OrderResult.success(logisticsNo);
}
}
客户端调用变得极其简单:
OrderFacade facade = new OrderFacade();
OrderResult result = facade.placeOrder(request);
你看,一行调用,背后完成了三个子系统的协作。客户端完全不需要知道库存怎么扣、支付怎么处理、物流怎么安排。
外观模式 vs 微服务网关
说到这,你可能会想到微服务网关。没错,微服务网关就是外观模式在分布式系统里的典型应用。
| 维度 | 传统外观模式 | 微服务网关 |
|---|---|---|
| 作用范围 | 单个应用内的子系统 | 分布式系统中的多个服务 |
| 通信方式 | 方法调用 | HTTP/RPC |
| 附加能力 | 接口封装 | 路由、限流、鉴权、日志 |
| 典型实现 | OrderFacade 类 | Zuul、Spring Cloud Gateway |
我个人习惯在微服务架构里,把网关当作「企业级外观」。它不只是封装接口,还承担了:
- 协议转换:把外部的 HTTP 请求转成内部 RPC 调用
- 聚合调用:一个接口背后聚合多个服务的数据
- 降级熔断:某个子系统挂了,网关能优雅处理
小技巧:在设计外观接口时,尽量让参数和返回值「粗粒度」。比如下单接口,传入一个 OrderRequest 对象,返回一个 OrderResult 对象。不要暴露子系统的细粒度参数给客户端。
SVG 框架图:外观模式核心逻辑
避坑指南
我曾经踩过的坑:
- 外观变成「上帝对象」:把所有逻辑都塞进外观里,导致外观类几千行。记住,外观只做编排,不做业务逻辑。
- 忽略异常传播:子系统抛出的异常,外观要统一处理。不要让 SQL 异常、网络异常直接暴露给客户端。
- 事务边界模糊:外观方法里调了多个子系统,事务怎么控制?我建议用 Saga 模式或 TCC,不要依赖本地事务。
什么时候用外观模式?
我总结了几条经验:
- 客户端需要调用多个子系统完成一个功能
- 子系统接口经常变化,你想隔离变化
- 你想给不同客户端提供不同粒度的接口(比如给移动端一个精简接口,给 Web 端一个完整接口)
外观模式不是银弹。如果子系统本身很简单,强行加一层外观反而增加复杂度。但像下单这种涉及库存、支付、物流的流程,外观模式几乎是标配。
嗯,今天就聊到这。记住一句话:外观模式的核心不是「减少接口数量」,而是「降低客户端与子系统的耦合」。