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 BImplementaçã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.