DEV Community

Gabriel Moreira
Gabriel Moreira

Posted on

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<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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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)