Domando o Caos das Integrações: como o Facade Pattern simplificou um checkout de e-commerce
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 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 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)
Comments
No comments yet. Start the discussion.