Design Patterns

Facade

Fornece uma interface simplificada para um subsistema complexo

Conceito e intenção

Facade oferece uma interface de alto nível para um conjunto de interfaces em um subsistema. O padrão reduz a complexidade visível ao cliente: em vez de orquestrar múltiplos serviços, o cliente faz uma única chamada que encapsula toda a coordenação.

O problema motivador é o acoplamento em leque. Quando um controller de API precisa chamar StockService, PaymentService, OrderService, NotificationService e LoyaltyService para completar um checkout, ele está acoplado a cinco serviços distintos. Qualquer mudança em qualquer um deles pode impactar o controller. Testar o controller exige mockar cinco dependências. Adicionar fraud check exige modificar o controller.

Facade extrai essa orquestração para uma classe dedicada. O controller fala com a Facade; a Facade conhece o subsistema; o subsistema não conhece a Facade. A complexidade não some — ela é encapsulada no lugar certo.


Estrutura

┌─────────┐       ┌─────────────────────┐
│ Client  │──────▶│      Facade         │
└─────────┘       │  + operacaoSimples()│
                  └──────────┬──────────┘
                             │ delega
               ┌─────────────┼────────────┐
               ▼             ▼            ▼
        ┌────────────┐ ┌──────────┐ ┌──────────┐
        │ SubsysA    │ │ SubsysB  │ │ SubsysC  │
        └────────────┘ └──────────┘ └──────────┘

Participantes:

  • Facade — interface simplificada; conhece os subsistemas e delega para eles
  • Subsystem classes — implementam a lógica complexa; não conhecem a Facade
  • Client — usa apenas a Facade; não precisa conhecer os subsistemas

Implementação Java — processo de compra

// Subsistemas (cada um com sua API própria)
@Service
public class InventoryService {
    public void reserve(List<OrderItem> items) { /* reserva estoque */ }
    public void release(List<OrderItem> items) { /* libera estoque */ }
    public boolean hasStock(List<OrderItem> items) { /* verifica */ return true; }
}

@Service
public class PaymentService {
    public PaymentResult processPayment(PaymentRequest request) { /* processa */ return null; }
    public void voidPayment(String paymentId) { /* cancela */ }
}

@Service
public class OrderService {
    public Order createDraft(Cart cart) { /* cria rascunho */ return null; }
    public void confirm(String orderId) { /* confirma */ }
    public void cancel(String orderId, String reason) { /* cancela */ }
}

@Service
public class ShippingService {
    public Shipment schedule(Order order) { /* agenda entrega */ return null; }
    public void cancel(String shipmentId) { /* cancela entrega */ }
}

@Service
public class NotificationService {
    public void sendOrderConfirmation(String customerId, String orderId) { /* email/sms */ }
    public void sendOrderCancellation(String customerId, String orderId) { /* notifica cancelamento */ }
}

@Service
public class LoyaltyService {
    public void creditPoints(String customerId, Money amount) { /* adiciona pontos */ }
}

@Service
public class FraudDetectionService {
    public FraudScore evaluate(Cart cart, Customer customer) { /* avalia fraude */ return null; }
}

// Facade — orquestra o fluxo completo de checkout
@Service
public class CheckoutFacade {

    private final InventoryService inventory;
    private final PaymentService payment;
    private final OrderService orders;
    private final ShippingService shipping;
    private final NotificationService notifications;
    private final LoyaltyService loyalty;
    private final FraudDetectionService fraud;

    // Spring injeta todos via construtor
    public CheckoutFacade(
            InventoryService inventory,
            PaymentService payment,
            OrderService orders,
            ShippingService shipping,
            NotificationService notifications,
            LoyaltyService loyalty,
            FraudDetectionService fraud) {
        this.inventory     = inventory;
        this.payment       = payment;
        this.orders        = orders;
        this.shipping      = shipping;
        this.notifications = notifications;
        this.loyalty       = loyalty;
        this.fraud         = fraud;
    }

    public CheckoutResult checkout(Cart cart, Customer customer, PaymentInfo paymentInfo) {
        // 1. Verificar estoque antes de qualquer coisa
        if (!inventory.hasStock(cart.getItems())) {
            return CheckoutResult.failed("Produto sem estoque");
        }

        // 2. Avaliação de fraude
        FraudScore score = fraud.evaluate(cart, customer);
        if (score.isHighRisk()) {
            return CheckoutResult.failed("Pedido bloqueado para análise de segurança");
        }

        // 3. Criar rascunho do pedido
        Order order = orders.createDraft(cart);

        // 4. Reservar estoque (pode falhar se concorrência)
        try {
            inventory.reserve(cart.getItems());
        } catch (InsufficientStockException e) {
            orders.cancel(order.getId(), "Sem estoque");
            return CheckoutResult.failed("Estoque insuficiente");
        }

        // 5. Processar pagamento
        PaymentResult paymentResult = payment.processPayment(
            PaymentRequest.of(cart.getTotal(), customer.getId(), paymentInfo)
        );

        if (!paymentResult.isApproved()) {
            // Rollback: liberar estoque e cancelar pedido
            inventory.release(cart.getItems());
            orders.cancel(order.getId(), "Pagamento recusado");
            return CheckoutResult.failed("Pagamento não aprovado: " + paymentResult.getReason());
        }

        // 6. Confirmar pedido
        orders.confirm(order.getId());

        // 7. Agendar entrega
        Shipment shipment = shipping.schedule(order);

        // 8. Notificar cliente (assíncrono — não bloqueia)
        notifications.sendOrderConfirmation(customer.getId(), order.getId());

        // 9. Creditar pontos de fidelidade
        loyalty.creditPoints(customer.getId(), cart.getTotal());

        return CheckoutResult.success(order.getId(), shipment.getEstimatedDelivery());
    }

    // Façade também pode expor operações de cancelamento
    public CancellationResult cancelOrder(String orderId, String customerId, String reason) {
        Order order = orders.findById(orderId);
        if (!order.getCustomerId().equals(customerId)) {
            return CancellationResult.forbidden();
        }
        if (!order.isCancellable()) {
            return CancellationResult.failed("Pedido não pode ser cancelado no status: " + order.getStatus());
        }

        payment.voidPayment(order.getPaymentId());
        inventory.release(order.getItems());
        orders.cancel(orderId, reason);
        notifications.sendOrderCancellation(customerId, orderId);

        return CancellationResult.success();
    }
}

// Controller limpo — desconhece completamente os subsistemas
@RestController
@RequestMapping("/checkout")
public class CheckoutController {
    private final CheckoutFacade checkoutFacade;

    public CheckoutController(CheckoutFacade checkoutFacade) {
        this.checkoutFacade = checkoutFacade;
    }

    @PostMapping
    public ResponseEntity<CheckoutResult> checkout(
            @RequestBody CheckoutRequest request,
            @AuthenticationPrincipal Customer customer) {
        CheckoutResult result = checkoutFacade.checkout(request.getCart(), customer, request.getPaymentInfo());
        return result.isSuccess()
            ? ResponseEntity.ok(result)
            : ResponseEntity.unprocessableEntity().body(result);
    }

    @DeleteMapping("/{orderId}")
    public ResponseEntity<CancellationResult> cancel(
            @PathVariable String orderId,
            @RequestParam String reason,
            @AuthenticationPrincipal Customer customer) {
        CancellationResult result = checkoutFacade.cancelOrder(orderId, customer.getId(), reason);
        return result.isSuccess()
            ? ResponseEntity.ok(result)
            : ResponseEntity.unprocessableEntity().body(result);
    }
}

Facade vs Mediator

São frequentemente confundidos. A diferença fundamental:

Facade simplifica uma interface para um subsistema que existe de qualquer forma. O subsistema não conhece a Facade. Comunicação é unidirecional: Cliente → Facade → Subsistemas.

Mediator coordena a comunicação entre objetos que se conhecem mutuamente para desacoplar interações. Os objetos conhecem o Mediator (ou são registrados nele). Comunicação é centralizada e bidirecional: Componente ↔ Mediator ↔ Componente.

Facade:               Mediator:
Client → Facade       A  ─▶  Mediator  ◀─  C
          ↓↓↓                    ↓
          ABC                    B

Implementação TypeScript

// Subsistemas
class InventoryService {
  async reserve(items: OrderItem[]): Promise<void> { /* ... */ }
  async release(items: OrderItem[]): Promise<void> { /* ... */ }
  async hasStock(items: OrderItem[]): Promise<boolean> { return true; }
}

class PaymentService {
  async charge(amount: number, customerId: string, token: string): Promise<PaymentResult> {
    return { approved: true, paymentId: 'pay_001' };
  }
  async void(paymentId: string): Promise<void> { /* ... */ }
}

class OrderService {
  async create(cart: Cart): Promise<Order> { return { id: 'ord_001', ...cart } as Order; }
  async confirm(orderId: string): Promise<void> { /* ... */ }
  async cancel(orderId: string, reason: string): Promise<void> { /* ... */ }
}

class NotificationService {
  async sendConfirmation(customerId: string, orderId: string): Promise<void> { /* ... */ }
}

// Facade
class CheckoutFacade {
  constructor(
    private readonly inventory: InventoryService,
    private readonly payment: PaymentService,
    private readonly orders: OrderService,
    private readonly notifications: NotificationService,
  ) {}

  async checkout(cart: Cart, customerId: string, paymentToken: string): Promise<CheckoutResult> {
    const inStock = await this.inventory.hasStock(cart.items);
    if (!inStock) return { success: false, error: 'Sem estoque' };

    const order = await this.orders.create(cart);

    await this.inventory.reserve(cart.items);

    const paymentResult = await this.payment.charge(cart.total, customerId, paymentToken);
    if (!paymentResult.approved) {
      await this.inventory.release(cart.items);
      await this.orders.cancel(order.id, 'Pagamento recusado');
      return { success: false, error: 'Pagamento recusado' };
    }

    await this.orders.confirm(order.id);
    // Notificação assíncrona — não aguarda
    this.notifications.sendConfirmation(customerId, order.id).catch(console.error);

    return { success: true, orderId: order.id };
  }
}

// Uso no controller (Express/Fastify)
app.post('/checkout', async (req, res) => {
  const result = await checkoutFacade.checkout(req.body.cart, req.user.id, req.body.paymentToken);
  res.status(result.success ? 200 : 422).json(result);
});

No mundo real

Spring Boot TestRestTemplate e MockMvc — facades sobre o complexo sistema de testes HTTP do Spring. Em vez de configurar cada parte manualmente, o desenvolvedor chama .perform(post("/url").content(...)).

java.net.URL.openConnection() — facade sobre o subsistema de networking, DNS, SSL e protocolo HTTP.

SLF4J é uma facade sobre os sistemas de logging (Logback, Log4j2, JUL). Você chama log.info(...) e a facade delega para a implementação configurada.

jQuery foi historicamente uma facade sobre as inconsistências do DOM entre navegadores — $('.sel').hide() ocultava a complexidade de element.style.display = 'none' com cross-browser fixes.

ProcessBuilder em Java — facade sobre a API de processos do sistema operacional: configurar ambiente, redirects, diretório de trabalho, e então .start().

Axios é uma facade sobre XMLHttpRequest e fetch, adicionando interceptors, transformações e tratamento de erros automático.


Quando usar

  • Uma operação de negócio requer coordenação de múltiplos serviços ou subsistemas
  • Você quer proteger o cliente de mudanças internas no subsistema
  • Precisa simplificar uma biblioteca complexa para os casos de uso mais comuns
  • Quer criar um ponto de entrada único para um módulo, reduzindo a superfície de API pública

Quando NÃO usar

  • Quando a Facade se torna um “God Object” — se acumular muita responsabilidade, quebre em múltiplas facades
  • Quando o cliente legítimo precisa de acesso granular ao subsistema — a facade não deve forçar abstração desnecessária
  • Quando não há subsistema complexo — criar uma facade para encapsular dois métodos é over-engineering
  • Quando a Facade começa a conter lógica de negócio própria — deve ser apenas orquestração, não processamento

Combinações com outros padrões

Facade + Adapter: Internamente, a Facade pode usar Adapters para normalizar as interfaces dos subsistemas antes de orquestrá-los.

Facade + Template Method: O método principal da Facade (ex: checkout()) pode ser estruturado como Template Method, com hooks para customização:

public CheckoutResult checkout(Cart cart, Customer customer) {
    preCheckout(cart, customer);     // hook
    doCheckout(cart, customer);      // lógica principal
    postCheckout(cart, customer);    // hook
}

Facade + Observer: A Facade pode publicar eventos de domínio após completar a operação, permitindo que outros subsistemas reajam sem que a Facade conheça os observadores.

Facade + Singleton: Em aplicações sem IoC container, a Facade frequentemente é um Singleton — há uma única instância que orquestra os subsistemas.