Facade
Fornece uma interface unificada e simplificada para um conjunto de interfaces de um subsistema, tornando o subsistema mais fácil de usar — sem esconder nem vedar o acesso direto ao subsistema quando necessário.
Intenção
Simplificar o acesso a um subsistema complexo expondo uma interface de alto nível que orquestra as interações entre os componentes internos. O cliente usa a Facade para os casos de uso comuns e acessa o subsistema diretamente apenas quando precisa de controle fino.
O Facade pertence à categoria de padrões estruturais no catálogo do GoF (1994). É um dos padrões mais intuitivos — provavelmente você já o aplicou sem saber: sempre que criou uma classe de serviço que orquestra repositórios, validators e notificadores, você criou uma Facade.
Problema
Considere o processo de finalizar uma compra (checkout) em um e-commerce. Este fluxo envolve múltiplos subsistemas: verificação de estoque, processamento de pagamento, cálculo de frete, geração do pedido, envio de e-mail de confirmação e atualização de relatórios. Cada subsistema tem suas próprias classes, dependências e regras.
Sem uma Facade, o código do controller ou do caso de uso precisaria conhecer e orquestrar cada subsistema diretamente — com todas as suas nuances de ordem de chamada e tratamento de erros. Isso cria um acoplamento alto entre o código cliente e os detalhes de implementação dos subsistemas.
Com o crescimento do sistema, qualquer mudança interna em um subsistema (ex.: migrar o gateway de pagamento) exige alterar todos os clientes que o conhecem diretamente.
Facade não é uma barreira impenetrável
Um equívoco comum: a Facade esconde completamente o subsistema, impedindo acesso direto. Não é isso. A Facade simplifica o caso comum — para necessidades avançadas, o subsistema continua acessível diretamente. A Facade é uma porta de entrada conveniente, não um muro.
Solução
O Facade organiza o código em dois grupos principais:
-
Facade: classe de alto nível que conhece quais classes
do subsistema são responsáveis por cada parte do trabalho. Ela delega
as solicitações dos clientes aos objetos apropriados do subsistema,
gerenciando a ordem de chamadas e ocultando a complexidade de orquestração.
Ex.:
CheckoutFacadecom o métodofinalizarPedido(). -
Subsystem classes: as classes que implementam a
funcionalidade real. Elas não conhecem a Facade — a Facade é que as
conhece. Ex.:
ServicoEstoque,ServicoPagamento,ServicoFrete,ServicoEmail.
A Facade pode ter uma ou várias interfaces de entrada — métodos de alto nível que representam os casos de uso mais comuns. Cada método orquestra a sequência correta de chamadas aos subsistemas.
Facade e Singleton
Fachadas são frequentemente implementadas como Singletons — faz sentido ter apenas uma instância da camada de orquestração. Em aplicações com containers de injeção de dependência (NestJS, Laravel, Spring), o container já cuida do ciclo de vida; em aplicações mais simples, é comum ver a Facade como um Singleton explícito ou como um módulo (em Node.js/TypeScript, módulos são naturalmente singletons por cache de importação).
Estrutura
Código cliente
│
│ facade.finalizarPedido(pedido)
▼
CheckoutFacade
┌────────────────────────────────────────────────────┐
│ - estoque: ServicoEstoque │
│ - pagamento: ServicoPagamento │
│ - frete: ServicoFrete │
│ - email: ServicoEmail │
│ │
│ + finalizarPedido(pedido): ResultadoCheckout │
│ 1. estoque.verificar(pedido.itens) │
│ 2. pagamento.processar(pedido.pagamento) │
│ 3. frete.calcularEAgendar(pedido.endereco) │
│ 4. email.enviarConfirmacao(pedido.cliente) │
└────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
Subsistemas (independentes, não se conhecem entre si)
ServicoEstoque ServicoPagamento ServicoFrete ServicoEmail
Nota: os subsistemas ainda são acessíveis diretamente
quando necessário — a Facade não os esconde.
Exemplos de código
Exemplo 1 — Facade de checkout de pedido
A Facade orquestra os subsistemas de estoque, pagamento, frete e e-mail numa única chamada de alto nível. O código cliente não precisa conhecer a ordem nem os detalhes de cada subsistema.
// ── Subsistemas — cada um com sua própria responsabilidade ───
class ServicoEstoque {
verificar(itens: string[]): boolean {
console.log(`[Estoque] Verificando ${itens.length} item(ns)...`);
return true; // simulação: sempre disponível
}
reservar(itens: string[]): void {
console.log(`[Estoque] ${itens.length} item(ns) reservado(s).`);
}
}
class ServicoPagamento {
processar(valorCentavos: number, metodoPagamento: string): string {
console.log(`[Pagamento] Processando R$${(valorCentavos / 100).toFixed(2)} via ${metodoPagamento}...`);
return `TXN-${Date.now()}`; // ID da transação
}
}
class ServicoFrete {
calcularEAgendar(cep: string): { prazo: number; valorCentavos: number } {
console.log(`[Frete] Calculando para CEP ${cep}...`);
return { prazo: 5, valorCentavos: 1490 }; // 5 dias, R$14,90
}
}
class ServicoEmail {
enviarConfirmacao(email: string, transacaoId: string): void {
console.log(`[Email] Confirmação enviada para ${email}. Transação: ${transacaoId}`);
}
}
// ── Tipos de domínio ─────────────────────────────────────────
interface DadosPedido {
itens: string[];
valorCentavos: number;
metodoPagamento: string;
cep: string;
emailCliente: string;
}
interface ResultadoCheckout {
transacaoId: string;
prazoEntregaDias: number;
freteValorCentavos: number;
}
// ── Facade ───────────────────────────────────────────────────
class CheckoutFacade {
constructor(
private readonly estoque: ServicoEstoque,
private readonly pagamento: ServicoPagamento,
private readonly frete: ServicoFrete,
private readonly email: ServicoEmail
) {}
// Interface de alto nível — orquestra toda a sequência:
finalizarPedido(pedido: DadosPedido): ResultadoCheckout {
// 1. Verifica disponibilidade
if (!this.estoque.verificar(pedido.itens)) {
throw new Error("Um ou mais itens estão indisponíveis.");
}
// 2. Processa pagamento
const transacaoId = this.pagamento.processar(
pedido.valorCentavos,
pedido.metodoPagamento
);
// 3. Reserva estoque e agenda entrega
this.estoque.reservar(pedido.itens);
const entrega = this.frete.calcularEAgendar(pedido.cep);
// 4. Notifica o cliente
this.email.enviarConfirmacao(pedido.emailCliente, transacaoId);
return {
transacaoId,
prazoEntregaDias: entrega.prazo,
freteValorCentavos: entrega.valorCentavos,
};
}
}
// ── Uso — o cliente usa apenas a Facade ──────────────────────
const facade = new CheckoutFacade(
new ServicoEstoque(),
new ServicoPagamento(),
new ServicoFrete(),
new ServicoEmail()
);
const resultado = facade.finalizarPedido({
itens: ["Livro GoF", "Caderno"],
valorCentavos: 9800,
metodoPagamento: "cartao_credito",
cep: "01310-100",
emailCliente: "dev@exemplo.com",
});
console.log(`Pedido finalizado! Transação: ${resultado.transacaoId}`);
console.log(`Entrega em ${resultado.prazoEntregaDias} dias.`);
console.log(`Frete: R$${(resultado.freteValorCentavos / 100).toFixed(2)}`);
// [Estoque] Verificando 2 item(ns)...
// [Pagamento] Processando R$98.00 via cartao_credito...
// [Estoque] 2 item(ns) reservado(s).
// [Frete] Calculando para CEP 01310-100...
// [Email] Confirmação enviada para dev@exemplo.com. Transação: TXN-...
// Pedido finalizado! Transação: TXN-...
// Entrega em 5 dias.
// Frete: R$14.90
<?php
// ── Subsistemas ───────────────────────────────────────────────
class ServicoEstoque
{
/** @param string[] $itens */
public function verificar(array $itens): bool
{
echo '[Estoque] Verificando ' . count($itens) . ' item(ns)...' . PHP_EOL;
return true; // simulação: sempre disponível
}
/** @param string[] $itens */
public function reservar(array $itens): void
{
echo '[Estoque] ' . count($itens) . ' item(ns) reservado(s).' . PHP_EOL;
}
}
class ServicoPagamento
{
public function processar(int $valorCentavos, string $metodoPagamento): string
{
$valor = number_format($valorCentavos / 100, 2);
echo "[Pagamento] Processando R\${$valor} via {$metodoPagamento}..." . PHP_EOL;
return 'TXN-' . time();
}
}
class ServicoFrete
{
/** @return array{prazo: int, valorCentavos: int} */
public function calcularEAgendar(string $cep): array
{
echo "[Frete] Calculando para CEP {$cep}..." . PHP_EOL;
return ['prazo' => 5, 'valorCentavos' => 1490]; // 5 dias, R$14,90
}
}
class ServicoEmail
{
public function enviarConfirmacao(string $email, string $transacaoId): void
{
echo "[Email] Confirmação enviada para {$email}. Transação: {$transacaoId}" . PHP_EOL;
}
}
// ── Facade ───────────────────────────────────────────────────
class CheckoutFacade
{
public function __construct(
private readonly ServicoEstoque $estoque,
private readonly ServicoPagamento $pagamento,
private readonly ServicoFrete $frete,
private readonly ServicoEmail $email
) {}
/**
* @param array{
* itens: string[],
* valorCentavos: int,
* metodoPagamento: string,
* cep: string,
* emailCliente: string
* } $pedido
* @return array{transacaoId: string, prazoEntregaDias: int, freteValorCentavos: int}
*/
public function finalizarPedido(array $pedido): array
{
// 1. Verifica disponibilidade
if (!$this->estoque->verificar($pedido['itens'])) {
throw new \RuntimeException('Um ou mais itens estão indisponíveis.');
}
// 2. Processa pagamento
$transacaoId = $this->pagamento->processar(
$pedido['valorCentavos'],
$pedido['metodoPagamento']
);
// 3. Reserva estoque e agenda entrega
$this->estoque->reservar($pedido['itens']);
$entrega = $this->frete->calcularEAgendar($pedido['cep']);
// 4. Notifica o cliente
$this->email->enviarConfirmacao($pedido['emailCliente'], $transacaoId);
return [
'transacaoId' => $transacaoId,
'prazoEntregaDias' => $entrega['prazo'],
'freteValorCentavos' => $entrega['valorCentavos'],
];
}
}
// ── Uso — o cliente usa apenas a Facade ──────────────────────
$facade = new CheckoutFacade(
new ServicoEstoque(),
new ServicoPagamento(),
new ServicoFrete(),
new ServicoEmail()
);
$resultado = $facade->finalizarPedido([
'itens' => ['Livro GoF', 'Caderno'],
'valorCentavos' => 9800,
'metodoPagamento' => 'cartao_credito',
'cep' => '01310-100',
'emailCliente' => 'dev@exemplo.com',
]);
echo "Pedido finalizado! Transação: {$resultado['transacaoId']}" . PHP_EOL;
echo "Entrega em {$resultado['prazoEntregaDias']} dias." . PHP_EOL;
printf("Frete: R$%.2f\n", $resultado['freteValorCentavos'] / 100);
// [Estoque] Verificando 2 item(ns)...
// [Pagamento] Processando R$98.00 via cartao_credito...
// [Estoque] 2 item(ns) reservado(s).
// [Frete] Calculando para CEP 01310-100...
// [Email] Confirmação enviada para dev@exemplo.com. Transação: TXN-...
// Pedido finalizado! Transação: TXN-...
// Entrega em 5 dias.
// Frete: R$14.90
Quando usar
- Para simplificar o acesso a um subsistema complexo: quando o código cliente precisa interagir com muitas classes de um subsistema em uma sequência específica, a Facade encapsula essa orquestração.
- Para criar camadas de abstração entre subsistemas: em arquiteturas em camadas (ex.: Clean Architecture, Hexagonal), a Facade é a fronteira entre a camada de aplicação e os subsistemas de infraestrutura.
- Para reduzir dependências transitivas: sem Facade, cada cliente do subsistema teria que importar e conhecer todas as classes internas. Com Facade, os clientes importam apenas ela.
- Para isolar a complexidade de bibliotecas de terceiros: uma Facade sobre uma SDK externa protege o código da aplicação de mudanças na API da biblioteca (semelhante ao Adapter, mas com foco em simplificação de múltiplas chamadas, não em compatibilidade de interface).
Quando evitar
- Quando o subsistema já é simples: uma Facade sobre um único serviço com um ou dois métodos é burocracia sem benefício.
- Quando se torna um "God Object": se a Facade acumula lógica de negócio em vez de apenas orquestrar, ela viola o Princípio da Responsabilidade Única. A Facade deve delegar — não decidir.
- Quando engessa a flexibilidade: se os clientes sempre precisam de controle fino sobre os subsistemas, a camada de abstração da Facade atrapalha mais do que ajuda.
Prós e contras
Prós
- Simplifica o uso do subsistema para os casos de uso mais comuns.
- Reduz o acoplamento entre clientes e os detalhes internos do subsistema.
- Facilita a substituição ou evolução interna do subsistema — os clientes da Facade não precisam mudar.
- Melhora a legibilidade: o método
finalizarPedido()comunica o fluxo de negócio sem expor a mecânica interna. - Ponto único para adicionar cross-cutting concerns (logging, métricas, transações) ao fluxo orquestrado.
Contras
- Pode tornar-se um "God Object" se absorver lógica de negócio além da orquestração.
- Pode esconder complexidade que o cliente às vezes precisa ver e controlar.
- Uma Facade que cobre apenas parte do subsistema força o cliente a conhecer tanto a Facade quanto o subsistema diretamente — inconsistência de abstração.
Armadilhas comuns
1. Facade vira God Object com lógica de negócio
A armadilha mais frequente: ao longo do tempo, regras de negócio migram para dentro da Facade ("se o pedido for acima de R$200, frete grátis", "se for cliente premium, aplica desconto"). A Facade passa a acumular responsabilidades que pertencem ao domínio. Mantenha a Facade como orquestradora pura — as regras de negócio pertencem a entidades e serviços de domínio, não à camada de fachada.
2. Confundir Facade com Adapter
O Adapter converte a interface de uma única classe para outra interface esperada pelo cliente. A Facade provê uma interface simplificada para um conjunto de classes. A Facade não precisa que as interfaces internas sejam incompatíveis — ela simplifica, não adapta.
3. Facade que não esconde nada
Atenção: uma Facade que é só um pass-through (apenas
repassa chamadas sem nenhuma orquestração ou simplificação) é uma camada
de indireção desnecessária. Se o método da Facade é apenas
return this.servico.metodo(params), sem adicionar nenhuma
coordenação, remova a Facade e acesse o serviço diretamente.
4. Testar a Facade em vez dos subsistemas
Testes unitários devem testar os subsistemas individualmente — não a Facade inteira. A Facade deve ter um teste de integração (ou de orquestração) que verifica a sequência de chamadas com mocks dos subsistemas. Não escreva testes unitários que executam os subsistemas reais pela Facade — isso mistura níveis de teste e torna os testes frágeis.
Padrões relacionados
O Facade interage com outros padrões estruturais e de criação:
O Adapter muda a interface de uma classe para outra; a Facade provê uma interface simplificada para um subsistema — as intenções são distintas, mas ambas criam um ponto de entrada único. O Decorator acrescenta responsabilidades a um objeto mantendo a mesma interface; a Facade pode internamente compor Decorators sobre os subsistemas, mas a Facade em si não precisa manter a interface dos subsistemas. O Singleton é frequentemente combinado com a Facade: faz sentido que exista apenas uma instância da camada de orquestração na aplicação. O Mediator também centraliza a comunicação entre objetos, mas com foco diferente: o Mediator gerencia a interação bidirecional entre componentes que se conhecem mutuamente (reduzindo dependências cruzadas); a Facade gerencia o acesso unidirecional de clientes externos a um subsistema que não conhece o cliente.