Backend for Frontend (BFF)

1. Introdução ao Padrão BFF

O padrão Backend for Frontend (BFF) emergiu como resposta direta aos problemas enfrentados por equipes que tentavam servir múltiplos tipos de clientes (web, mobile, IoT) com uma única API monolítica. Originalmente documentado pela equipe do SoundCloud em 2015, o BFF propõe uma abordagem radicalmente simples: criar uma camada de backend dedicada e específica para cada tipo de frontend.

Diferentemente de um API Gateway tradicional, que atua como ponto único de entrada para todos os serviços, o BFF é desenhado para entender as necessidades específicas de um cliente particular. Enquanto o Gateway lida com preocupações transversais como autenticação e rate limiting, o BFF foca em orquestrar dados e lógica de apresentação para um frontend específico.

2. Motivações e Problemas Resolvidos

Sobrecarga de Dados

APIs monolíticas frequentemente retornam dados demais para um cliente e de menos para outro. Um aplicativo mobile pode precisar apenas de:

GET /api/user/profile
Resposta:
{
  "id": 123,
  "nome": "Maria",
  "ultimo_acesso": "2024-01-15"
}

Enquanto uma versão web precisa de:

GET /bff-web/user/profile
Resposta:
{
  "id": 123,
  "nome": "Maria",
  "ultimo_acesso": "2024-01-15",
  "preferencias": { "tema": "escuro", "idioma": "pt-BR" },
  "notificacoes_nao_lidas": 3,
  "permissoes": ["admin", "editor"]
}

Lógica de Apresentação

Sem BFF, a lógica de formatação de dados vaza para o frontend ou polui serviços de domínio. O BFF resolve isso centralizando transformações específicas:

// BFF Mobile: transforma dados de múltiplos serviços
function montarFeedParaMobile(usuarioId) {
  const posts = await servicoPosts.buscarRecentes(usuarioId);
  const amigos = await servicoAmigos.buscarOnline(usuarioId);

  return {
    feed: posts.map(p => ({
      id: p.id,
      texto_resumido: p.texto.substring(0, 100),
      autor: p.autor.nome,
      tempo_relativo: calcularTempoRelativo(p.criadoEm)
    })),
    amigos_online: amigos.length,
    sugestoes: await servicoRecomendacao.buscar(usuarioId)
  };
}

Ciclos de Deploy Independentes

Cada BFF pode evoluir e ser deployado independentemente do backend central e de outros BFFs. Uma equipe mobile pode ajustar seu BFF sem esperar pela equipe web.

3. Estrutura e Implementação do BFF

Organização por Cliente

bff-web/
├── src/
│   ├── routes/
│   ├── aggregators/
│   └── transformers/
bff-mobile/
├── src/
│   ├── routes/
│   ├── aggregators/
│   └── transformers/
bff-iot/
└── src/

Camadas Internas

Um BFF típico possui três camadas:

  1. Roteamento: Expõe endpoints específicos para o frontend
  2. Agregação: Combina dados de múltiplos serviços de backend
  3. Transformação: Adapta o formato dos dados para o cliente
// Exemplo de agregador no BFF Mobile
async function obterDetalhesProduto(produtoId, usuarioId) {
  const [produto, estoque, recomendacoes] = await Promise.all([
    servicoCatalogo.buscarProduto(produtoId),
    servicoEstoque.verificarDisponibilidade(produtoId),
    servicoRecomendacao.buscarSimilares(produtoId, usuarioId)
  ]);

  return {
    ...produto,
    disponivel: estoque.quantidade > 0,
    prazo_entrega: estoque.prazoMedio,
    sugestoes: recomendacoes.slice(0, 3)
  };
}

4. BFF vs. API Gateway: Comparação e Coexistência

Responsabilidades Distintas

Aspecto API Gateway BFF
Escopo Global, todos os clientes Específico por frontend
Autenticação Sim, centralizada Delegada ao Gateway
Rate Limiting Sim Não
Orquestração Mínima Intensiva
Transformação Genérica Específica

Arquitetura Híbrida

[Cliente Web] --> [API Gateway] --> [BFF Web] --> [Serviço A]
                                                  --> [Serviço B]

[Cliente Mobile] --> [API Gateway] --> [BFF Mobile] --> [Serviço A]
                                                      --> [Serviço C]

Nesta configuração, o API Gateway lida com segurança e roteamento inicial, enquanto cada BFF faz a orquestração fina para seu cliente.

5. Estratégias de Dados e Performance

Agregação Inteligente

// BFF com cache específico para mobile
async function buscarDadosIniciais(usuarioId) {
  const cacheKey = `inicio_mobile_${usuarioId}`;
  const cache = await redis.get(cacheKey);

  if (cache) return JSON.parse(cache);

  const dados = await agregarDadosIniciais(usuarioId);
  await redis.setex(cacheKey, 300, JSON.stringify(dados));

  return dados;
}

Lazy Loading Adaptado

O BFF mobile pode implementar prefetching baseado em padrões de navegação:

// Prefetching no BFF Mobile
async function buscarProximosDados(ultimaAcao) {
  if (ultimaAcao === 'visualizar_perfil') {
    // Pré-carrega dados de feed
    backgroundJob.agendar('feed', usuarioId);
  }
}

6. Segurança e Governança no BFF

Isolamento de Autenticação

O BFF não deve implementar autenticação própria, mas sim validar tokens recebidos do API Gateway:

// Middleware de validação no BFF
function validarToken(req, res, next) {
  const token = req.headers['authorization'];

  if (!token || !token.startsWith('Bearer ')) {
    return res.status(401).json({ erro: 'Token ausente' });
  }

  // Valida com serviço de autenticação central
  servicoAuth.validar(token)
    .then(dados => {
      req.usuario = dados;
      next();
    })
    .catch(() => res.status(401).json({ erro: 'Token inválido' }));
}

Sanitização Específica

Cada BFF deve sanitizar entradas conforme as necessidades do frontend:

function sanitizarEntradaMobile(dados) {
  // Mobile aceita coordenadas GPS, web não
  return {
    texto: sanitizarTexto(dados.texto),
    localizacao: validarCoordenadas(dados.lat, dados.lng),
    anexo: validarTamanhoArquivo(dados.anexo, 5 * 1024 * 1024) // 5MB
  };
}

7. Desafios e Boas Práticas

Evitando Duplicação de Lógica

Crie bibliotecas compartilhadas para lógica comum, mas mantenha a especialização:

// lib-comum/formatacao.js
function formatarData(data) { /* ... */ }

// bff-web/transformadores.js
function formatarParaWeb(produto) {
  return {
    ...produto,
    preco_formatado: formatarMoeda(produto.preco),
    data_lancamento: formatarData(produto.criadoEm, 'dd/MM/yyyy')
  };
}

// bff-mobile/transformadores.js
function formatarParaMobile(produto) {
  return {
    ...produto,
    preco_simples: produto.preco,
    tempo_relativo: calcularTempoRelativo(produto.criadoEm)
  };
}

Versionamento de Contratos

Mantenha contratos claros entre frontend e BFF:

// Contrato BFF Mobile v2
interface FeedResponse {
  versao: 2;
  itens: FeedItem[];
  paginacao: {
    cursor: string;
    tem_mais: boolean;
  };
}

Resiliência com Circuit Breaker

async function buscarDadosResiliente(servico, fallback) {
  try {
    if (circuitBreaker.estaAberto()) {
      return await fallback();
    }
    return await servico();
  } catch (erro) {
    circuitBreaker.registrarFalha();
    return await fallback();
  }
}

Conclusão

O padrão BFF oferece uma abordagem pragmática para lidar com a diversidade de clientes em sistemas modernos. Quando implementado corretamente, reduz a complexidade dos frontends, melhora a performance e permite que equipes evoluam independentemente. A chave está em reconhecer onde a especialização traz mais valor do que a duplicação, e onde o custo de manter múltiplos BFFs se justifica pelos ganhos de qualidade e velocidade de desenvolvimento.

Referências