DEV Community

Cover image for 🏗️ Backend for Frontend (BFF) – o padrão que salvou meus microsserviços (e minha sanidade)
Fabio Rocha
Fabio Rocha

Posted on

🏗️ Backend for Frontend (BFF) – o padrão que salvou meus microsserviços (e minha sanidade)

E aí, dev que já teve que explicar por que o frontend mobile precisa de dados diferentes do frontend web? Ou pior: já precisou adaptar uma API monolítica para atender três clientes diferentes e acabou com um endpoint que devolve 50 campos, sendo que cada frontend só usa 10?

Pois é. O mundo dos microsserviços é lindo até você precisar entregar uma experiência personalizada para cada tipo de cliente. O app mobile não quer os mesmos dados que o site desktop. O tablet quer uma versão intermediária. E o backend principal? Ele só quer ser um backend, sem se preocupar com as frescuras de cada frontend.

Foi aí que eu descobri o Backend for Frontend (BFF) – e minha vida (e minha arquitetura) mudou. Bora entender esse padrão com exemplos práticos e muito código? 🚀


🔍 O que é esse tal de BFF?

O Backend for Frontend (BFF) é um padrão arquitetural que consiste em criar uma camada de backend dedicada especificamente para um frontend específico (ou um grupo de frontends com necessidades semelhantes).

Traduzindo: em vez de ter um backend único que tenta atender todos os frontends (web, mobile, tablet, smartwatch, etc.), você cria um backend por frontend. Cada BFF é desenhado sob medida para as necessidades daquele cliente específico.

Exemplo clássico:

  • BFF para Web → retorna dados ricos, com HTML parcial, meta tags, SEO-friendly
  • BFF para Mobile → retorna payloads enxutos, com apenas o essencial para economizar banda
  • BFF para Admin → retorna dados com permissões avançadas, relatórios, etc.

E o mais legal: um BFF pode atender múltiplos frontends se eles tiverem requisitos similares, especialmente quando você usa ferramentas como GraphQL.


🤔 Por que você deveria se importar com BFF?

O problema que todo mundo já viveu

Imagine um sistema de e-commerce com:

  • Frontend Web (React) → precisa de: nome, preço, descrição, avaliações, imagens em alta resolução, estoque, frete
  • Frontend Mobile (React Native) → precisa de: nome, preço resumido, uma imagem miniatura, disponibilidade
  • Frontend Admin (Angular) → precisa de: tudo acima + logs, métricas, histórico de preços, dados de fornecedor

Abordagem monolítica (o que todo mundo faz errado):

@GetMapping("/produtos/{id}")
public ProdutoCompleto getProduto(@PathVariable Long id) {
    // Retorna 50 campos, sendo que cada frontend usa só 10
    return produtoService.buscarCompleto(id);
}
Enter fullscreen mode Exit fullscreen mode

Resultado: over-fetching (dados demais), under-fetching (faltou dado pro admin), payload gigante, lentidão no mobile, e um backend que não sabe para quem está servindo.

Abordagem com BFF (o que você deveria fazer):

[Frontend Web] → [BFF Web] → [Backend Principal]
[Frontend Mobile] → [BFF Mobile] → [Backend Principal]
[Frontend Admin] → [BFF Admin] → [Backend Principal]
Enter fullscreen mode Exit fullscreen mode

Cada BFF é responsável por:

  • Buscar os dados do backend principal
  • Transformar, agregar e filtrar conforme a necessidade daquele frontend
  • Retornar apenas o que aquele frontend precisa

🛠️ Como implementar um BFF na prática (com Spring Boot)

Vamos construir um BFF para um frontend mobile que precisa de dados de produto + avaliações + disponibilidade em estoque.

1. Estrutura do projeto

bff-mobile/
├── src/main/java/com/example/bffmobile/
│   ├── controller/
│   │   └── ProdutoController.java
│   ├── service/
│   │   ├── ProdutoService.java
│   │   ├── AvaliacaoService.java
│   │   └── EstoqueService.java
│   ├── dto/
│   │   └── ProdutoMobileDTO.java
│   └── config/
│       └── WebClientConfig.java
Enter fullscreen mode Exit fullscreen mode

2. O DTO específico para o mobile

public class ProdutoMobileDTO {
    private String id;
    private String nome;
    private String preco;
    private String imagemMiniatura;
    private boolean disponivel;
    private Double avaliacaoMedia;
    private Integer totalAvaliacoes;
    // getters e setters
}
Enter fullscreen mode Exit fullscreen mode

Perceba: apenas o necessário para o mobile. Nada de descrição longa, nada de atributos administrativos.

3. O Service que orquestra as chamadas

@Service
public class ProdutoMobileService {

    private final WebClient webClient;
    private final AvaliacaoService avaliacaoService;
    private final EstoqueService estoqueService;

    public ProdutoMobileDTO buscarProdutoMobile(String produtoId) {
        // 1. Busca o produto no backend principal
        Produto produto = webClient.get()
            .uri("/api/produtos/" + produtoId)
            .retrieve()
            .bodyToMono(Produto.class)
            .block();

        // 2. Busca avaliações (em paralelo, porque somos modernos)
        CompletableFuture<Avaliacao> futureAvaliacao = 
            CompletableFuture.supplyAsync(() -> 
                avaliacaoService.buscarAvaliacao(produtoId));

        // 3. Busca disponibilidade em estoque (em paralelo também)
        CompletableFuture<Estoque> futureEstoque = 
            CompletableFuture.supplyAsync(() -> 
                estoqueService.buscarDisponibilidade(produtoId));

        // 4. Agrega tudo
        Avaliacao avaliacao = futureAvaliacao.join();
        Estoque estoque = futureEstoque.join();

        // 5. Monta o DTO específico para o mobile
        return new ProdutoMobileDTO()
            .setId(produto.getId())
            .setNome(produto.getNome())
            .setPreco(formatarPreco(produto.getPreco()))
            .setImagemMiniatura(produto.getImagens().get(0).getThumbnail())
            .setDisponivel(estoque.getQuantidade() > 0)
            .setAvaliacaoMedia(avaliacao.getMedia())
            .setTotalAvaliacoes(avaliacao.getTotal());
    }
}
Enter fullscreen mode Exit fullscreen mode

4. O Controller (bem enxuto)

@RestController
@RequestMapping("/api/mobile/produtos")
public class ProdutoController {

    private final ProdutoMobileService produtoService;

    @GetMapping("/{id}")
    public ResponseEntity<ProdutoMobileDTO> getProduto(@PathVariable String id) {
        ProdutoMobileDTO dto = produtoService.buscarProdutoMobile(id);
        return ResponseEntity.ok(dto);
    }
}
Enter fullscreen mode Exit fullscreen mode

O que aconteceu aqui? O BFF fez três chamadas para diferentes serviços (produto, avaliação, estoque), agregou os dados, transformou no formato que o mobile espera, e retornou um payload enxuto e rápido. O mobile nem sabe que existem três serviços diferentes. Ele só recebe o que precisa. 😎


📊 A arquitetura completa (com diagrama)

Aqui está a arquitetura típica de um sistema com BFF:

                    ┌─────────────────┐
                    │   Frontend Web  │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │    BFF Web      │
                    └────────┬────────┘
                             │
┌────────────────────────────┼────────────────────────────┐
│                    ┌───────▼───────┐                    │
│                    │   Backend     │                    │
│                    │   Principal   │                    │
│                    └───────┬───────┘                    │
│                            │                            │
│              ┌─────────────┼─────────────┐              │
│              │             │             │              │
│       ┌──────▼──────┐ ┌────▼────┐ ┌─────▼─────┐        │
│       │  Serviço A  │ │Serviço B│ │ Serviço C │        │
│       └─────────────┘ └─────────┘ └───────────┘        │
└─────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

E os BFFs podem ser implementados em tecnologias diferentes:

  • BFF Web → Spring Boot (Java)
  • BFF Mobile → Node.js (JavaScript) – mais leve para payloads menores
  • BFF Admin → Python (FastAPI) – para integrações com ferramentas de dados

Framework Independence é um dos grandes benefícios: cada BFF pode usar a tecnologia mais adequada para aquele frontend.


✅ Benefícios que vão fazer você amar BFF

1. 🚀 Otimização de rede

O BFF entende exatamente o que o frontend precisa e só envia o necessário. Adeus, over-fetching e under-fetching. Seu mobile agradece (e a conta de dados também).

2. 🔒 Segurança granulada

Cada BFF pode ter suas próprias regras de autenticação e autorização. O BFF mobile pode exigir um token diferente do BFF web. E o backend principal fica isolado, expondo apenas o necessário.

3. 🧪 Testes mais focados

Com BFF, você pode testar cenários específicos de cada frontend. Os testes ficam mais relevantes e o feedback é mais rápido. E como o BFF geralmente é escrito em frameworks que incentivam testes unitários, a qualidade sobe.

4. 🔄 Evolução independente

O frontend pode mudar sua interface sem afetar o backend principal. E vice-versa. O BFF atua como uma camada de amortecimento entre as duas pontas.

5. 🧩 Separação de responsabilidades

O backend principal foca em regras de negócio e persistência. O BFF foca em orquestração e transformação para o frontend. Cada um no seu quadrado.


⚠️ Os trade-offs que ninguém conta

1. 👥 Equipes multidisciplinares

Se o BFF usa uma tecnologia diferente do frontend, você vai precisar de desenvolvedores que conheçam ambos os mundos – ou times que colaborem muito.

2. 🧠 Risco de vazamento de lógica de negócio

É tentador colocar regras de negócio dentro do BFF. Não faça isso! O BFF é para orquestração, não para lógica de domínio. Senão você vai ter a mesma regra duplicada em vários BFFs.

3. 🔁 Duplicação de testes

Você pode acabar testando a mesma coisa no backend principal, no BFF e no frontend. Isso aumenta o tempo de build e a manutenção.

4. 📈 Complexidade operacional

Mais serviços = mais coisas para monitorar, logar, escalar e dar deploy. Se você não tem uma boa infraestrutura (Kubernetes, observabilidade, CI/CD), o BFF pode virar um pesadelo.

5. ⚡ Risco de chamadas desnecessárias

Se o BFF não for bem desenhado, ele pode fazer chamadas em cascata que geram congestionamento de rede. Use paralelismo (como no exemplo com CompletableFuture) para minimizar isso.


🧭 Quando usar BFF? (e quando NÃO usar)

✅ Use BFF quando:

  • Você tem múltiplos frontends (web, mobile, tablet, etc.) com necessidades diferentes
  • Cada frontend precisa de dados específicos e você quer evitar over-fetching
  • Você quer isolar a evolução do frontend do backend
  • Você precisa de segurança diferenciada por tipo de cliente
  • Você quer otimizar a performance de cada frontend individualmente

❌ Evite BFF quando:

  • Você tem apenas um frontend – um backend único resolve
  • Seu time é pequeno e não tem capacidade de manter múltiplos serviços
  • Sua aplicação é simples e não justifica a complexidade adicional
  • Você não tem infraestrutura para gerenciar múltiplos deployments

💡 Dicas quentes para implementar BFF sem sofrer

1. Use GraphQL no BFF (se possível)

O GraphQL permite que o frontend especifique exatamente quais campos quer. Isso reduz ainda mais o over-fetching e dá poder ao frontend.

2. Paralelize chamadas

Use CompletableFuture, Project Reactor ou Virtual Threads (JDK 21+) para fazer chamadas paralelas aos serviços downstream. Seu BFF vai voar.

3. Cache inteligente

Cacheie dados que não mudam com frequência (ex: catálogo de produtos) no BFF. Reduza a carga no backend principal.

4. Monitoramento é obrigatório

Cada BFF deve ter logs estruturados, métricas (latência, erro, throughput) e tracing distribuído. Você precisa saber onde está o gargalo.

5. Versionamento da API

Sua API de BFF pode mudar conforme o frontend evolui. Versione (ex: /api/v1/mobile/produtos) para não quebrar clientes antigos.


🎯 Conclusão

O padrão Backend for Frontend (BFF) não é novidade, mas ganhou força com a popularização dos microsserviços e a diversificação de frontends. Ele resolve um problema real: como entregar a experiência certa para cada cliente sem transformar seu backend num monstro de 50 endpoints diferentes.

Claro, tem trade-offs. Você vai ter mais serviços para gerenciar, mais complexidade operacional e times que precisam se alinhar. Mas os benefícios – performance, segurança, evolução independente – geralmente compensam.

A chave é começar pequeno. Implemente um BFF para o frontend que mais sofre (geralmente o mobile) e veja o resultado. Depois, se fizer sentido, expanda para os outros.

Já usou BFF em algum projeto? Ou ainda está no mundo do backend monolítico que atende todo mundo? Conta aí nos comentários! 👇

BFF #BackendForFrontend #Arquitetura #Microsserviços #SpringBoot #Java


Quer mais? No próximo post vou mostrar como implementar um BFF com GraphQL e Spring Boot, e como fazer deploy no Kubernetes. Até lá! 🚀

Top comments (0)