O que é o Facade Pattern?
O Facade é um padrão de projeto estrutural. Padrões estruturais, de forma geral, resolvem problemas de como montar as peças como classes e objetos se organizam para formar estruturas maiores sem virar uma bagunça de dependências cruzadas. O Facade, especificamente, tem um trabalho simples de descrever e absurdamente útil na prática: esconder a complexidade de um subsistema atrás de uma única interface simplificada.
O problema
Imagina um sistema de checkout de e-commerce. Pra simplesmente finalizar uma compra, o backend precisa:
- Verificar estoque no serviço de inventário
- Cobrar o cliente através de um gateway de pagamento
- Acionar o serviço de logística pra gerar a etiqueta de envio
- Disparar uma notificação (e-mail/push) confirmando o pedido
Cada um desses é, normalmente, um sistema (ou microsserviço) diferente, com sua própria API, seu próprio formato de erro, sua própria forma de autenticação. Se o CheckoutController chamar essas quatro APIs diretamente, você tem um problema clássico de acoplamento: qualquer controller, job ou endpoint que precise finalizar um pedido vai precisar conhecer os quatro subsistemas, na ordem certa, tratando os erros de cada um. Muda a API de pagamento? Você caça esse código espalhado pelo projeto inteiro. Quer escrever um teste do fluxo de checkout? Você precisa mockar quatro clientes diferentes toda vez.
O conceito
Uma forma fácil de se compreender é utilizando uma analógia, no caso a da recepção de um hotel. Como hóspede, você não liga diretamente para a governança pra pedir algo, nem para a manutenção pra consertar o ar-condicionado, nem para o restaurante pra reservar uma mesa. Você fala com a recepção, e é ela quem aciona cada um desses setores internamente, na ordem e na forma corretas. Do seu ponto de vista, existe uma única interface de contato; toda a complexidade operacional do hotel fica escondida atrás dela.
No código, o Facade cumpre exatamente esse papel: uma classe que expõe um método simples (finalizarPedido()) e, por trás dele, orquestra a chamada a N subsistemas complexos, na ordem certa, tratando os erros de cada um. Quem consome o Facade não precisa saber que existem quatro APIs diferentes ali dentro.
Cenário do mundo real
Vamos usar exatamente o cenário do checkout que descrevi acima: um serviço OrderService, dentro de uma arquitetura de microsserviços, que precisa finalizar um pedido interagindo com:
InventoryServiceClient = API de estoque
PaymentGatewayClient = API de um gateway de pagamento (tipo Stripe/PagSeguro)
ShippingServiceClient = API de logística
NotificationServiceClient = serviço de notificações (e-mail/push)
Isso é o tipo de dor que qualquer engenheiro backend que já trabalhou com microsserviços reconhece na hora.
Diagrama de classes (UML)
Repare que o CheckoutController só conhece a OrderCheckoutFacade. Ele nunca vê os quatro clients diretamente, isso é o Facade fazendo seu trabalho.
Desenho de arquitetura (alto nível)
O Order Service é o único ponto de contato entre o cliente e o resto do ecossistema. Internamente, a OrderCheckoutFacade é a peça que absorve a complexidade de falar com quatro sistemas externos diferentes, enquanto o resto da aplicação (controllers, jobs futuros, etc.) só depende dela.
Implementação em Java
Primeiro, os subsistemas (simplificados, cada um representaria, na prática, um client HTTP real):
java
public class InventoryServiceClient {
public boolean reservarItens(List<Item> itens) {
System.out.println("Reservando itens no estoque: " + itens.size());
return true;
}
}
public class PaymentGatewayClient {
public TransacaoResult cobrar(Pagamento pagamento) {
System.out.println("Cobrando R$" + pagamento.getValor());
return new TransacaoResult(true, "txn_123");
}
}
public class ShippingServiceClient {
public String gerarEtiqueta(Pedido pedido) {
System.out.println("Gerando etiqueta para pedido " + pedido.getId());
return "ETQ-" + pedido.getId();
}
}
public class NotificationServiceClient {
public void notificarCliente(String email, String mensagem) {
System.out.println("Notificando " + email + ": " + mensagem);
}
}
Agora, a Facade:
java
public class OrderCheckoutFacade {
private final InventoryServiceClient inventory;
private final PaymentGatewayClient payment;
private final ShippingServiceClient shipping;
private final NotificationServiceClient notification;
public OrderCheckoutFacade(InventoryServiceClient inventory,
PaymentGatewayClient payment,
ShippingServiceClient shipping,
NotificationServiceClient notification) {
this.inventory = inventory;
this.payment = payment;
this.shipping = shipping;
this.notification = notification;
}
public ResultadoCheckout finalizarPedido(Pedido pedido) {
boolean reservado = inventory.reservarItens(pedido.getItens());
if (!reservado) {
return ResultadoCheckout.falha("Itens indisponíveis");
}
TransacaoResult txn = payment.cobrar(pedido.getPagamento());
if (!txn.isAprovado()) {
return ResultadoCheckout.falha("Pagamento recusado");
}
String etiqueta = shipping.gerarEtiqueta(pedido);
notification.notificarCliente(pedido.getEmailCliente(),
"Pedido confirmado! Rastreio: " + etiqueta);
return ResultadoCheckout.sucesso(txn.getId(), etiqueta);
}
}
E, finalmente, quem consome a Facade:
java
@RestController
public class CheckoutController {
private final OrderCheckoutFacade facade;
public CheckoutController(OrderCheckoutFacade facade) {
this.facade = facade;
}
@PostMapping("/checkout")
public ResponseEntity<ResultadoCheckout> checkout(@RequestBody Pedido pedido) {
ResultadoCheckout resultado = facade.finalizarPedido(pedido);
return resultado.isSucesso()
? ResponseEntity.ok(resultado)
: ResponseEntity.badRequest().body(resultado);
}
}
Repara como o código reflete exatamente o diagrama de classes: o CheckoutController depende só da OrderCheckoutFacade, e é ela quem conhece e orquestra os quatro subsistemas.
Prós e contras
Vantagens:
Desacoplamento: o controller (e qualquer outro consumidor futuro) não sabe nada sobre inventário, pagamento, logística ou notificação, só sabe que existe um finalizar pedido.
Ponto único de mudança: se a API de pagamento mudar de fornecedor, você mexe só dentro da Facade.
Testabilidade: dá pra testar o CheckoutController mockando uma única dependência (a Facade), em vez de quatro.
Legibilidade: o fluxo de negócio (reservar → cobrar → enviar → notificar) fica explícito e centralizado num único método.
Trade-offs:
A Facade pode virar um "god object" se você não tomar cuidado, se toda regra de negócio nova for empilhada ali dentro sem critério, ela cresce descontroladamente.
Ela esconde a complexidade, mas não a elimina. Se um dos quatro subsistemas cair, alguém ainda precisa lidar com isso (retry, circuit breaker, etc.) o Facade não resolve resiliência sozinho.
Pode introduzir uma camada extra de indireção que, em sistemas muito pequenos, é overhead desnecessário.
Resumo
O Facade Pattern não inventa nenhuma mágica nova, ele só organiza uma verdade que toda engenharia de software aprende cedo ou tarde: complexidade não desaparece, ela só pode ser movida para um lugar mais controlável. No caso do checkout, em vez de espalhar o conhecimento sobre quatro APIs externas por todo o código, a gente concentra isso numa única classe com uma responsabilidade clara.
Visão pessoal
Antes de implementar isso na prática, o Facade parecia simples demais para ser considerado um padrão de projeto. Mas ao desenhar o OrderCheckoutFacade com atenção à manutenibilidade, à testabilidade e ao cenário de uma eventual troca de fornecedor de pagamento, ficou claro por que ele é tratado como um padrão e não como uma classe qualquer: a intenção por trás dele, isolar os consumidores da complexidade de um subsistema.
E você?
Já enfrentou uma situação em que um serviço ficou refém de mudanças em APIs externas? Como lidou com esse acoplamento? Deixe um comentário a discussão sobre quando vale a pena introduzir um Facade (e quando ele pode virar um problema) é sempre rica.


Top comments (0)