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

🏗️ Backend for Frontend (BFF) – The pattern that saved my microservices (and my sanity)

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? 🚀

嘿,开发者!你是否曾不得不解释为什么移动端前端需要的数据与 Web 端不同?或者更糟糕的是:你是否曾为了适配三个不同的客户端而修改一个单体 API,最终导致一个接口返回了 50 个字段,而每个前端实际上只用了 10 个?没错,微服务世界虽然美好,但当你需要为每种类型的客户端提供个性化体验时,问题就来了。移动端 App 不需要和桌面端网站一样的数据,平板电脑可能需要一个中间版本。而核心后端呢?它只想做好自己的后端工作,而不必关心每个前端的琐碎需求。就在那时,我发现了“后端为前端”(Backend for Frontend, BFF)模式——它改变了我的生活(以及我的架构)。让我们通过实际示例和代码来深入了解这个模式吧!🚀


🔍 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.

🔍 到底什么是 BFF?

BFF(Backend for Frontend)是一种架构模式,其核心是为特定的前端(或具有相似需求的一组前端)创建一个专门的后端层。换句话说:与其拥有一个试图满足所有前端(Web、移动端、平板、智能手表等)的单一后端,不如为每个前端创建一个后端。每个 BFF 都是根据该特定客户端的需求量身定制的。

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.

经典示例:

  • Web BFF: 返回丰富的数据,包含部分 HTML、Meta 标签,对 SEO 友好。
  • 移动端 BFF: 返回精简的 Payload,仅包含必要信息以节省带宽。
  • 管理端 BFF: 返回包含高级权限、报表等的数据。

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

最酷的是:如果多个前端有相似的需求,一个 BFF 也可以服务于多个前端,特别是在使用 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.

🤔 为什么你应该关注 BFF?

每个人都经历过的痛点:想象一个电商系统:

  • Web 前端 (React): 需要:名称、价格、描述、评价、高清图片、库存、运费。
  • 移动端前端 (React Native): 需要:名称、简略价格、缩略图、库存状态。
  • 管理端前端 (Angular): 需要:上述所有内容 + 日志、指标、价格历史、供应商数据。

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);
}

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.

单体架构(每个人都在犯的错误):

@GetMapping("/produtos/{id}")
public ProdutoCompleto getProduto(@PathVariable Long id) {
    // 返回 50 个字段,但每个前端只用 10 个
    return produtoService.buscarCompleto(id);
}

结果: 过度获取(数据冗余)、获取不足(管理端缺数据)、Payload 巨大、移动端加载缓慢,且后端根本不知道自己在为谁服务。

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]

BFF 架构(你应该做的): [Web 前端] → [Web BFF] → [核心后端] [移动端前端] → [移动端 BFF] → [核心后端] [管理端前端] → [管理端 BFF] → [核心后端]

Cada BFF é responsável por: Buscar os dados do backend principal, transformar, agregar e filtrar conforme a necessidade daquele frontend, e retornar apenas o que aquele frontend precisa.

每个 BFF 负责:从核心后端获取数据,根据前端需求进行转换、聚合和过滤,并仅返回该前端所需的内容。


🛠️ 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.

🛠️ 如何在实践中实现 BFF(使用 Spring Boot)

让我们为移动端前端构建一个 BFF,它需要产品数据 + 评价 + 库存可用性。

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

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;
}

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

2. 移动端专用的 DTO:

public class ProdutoMobileDTO {
    private String id;
    private String nome;
    private String preco;
    private String imagemMiniatura;
    private boolean disponivel;
    private Double avaliacaoMedia;
    private Integer totalAvaliacoes;
}

注意:只包含移动端必要的内容。没有长描述,没有管理属性。

3. O Service que orquestra as chamadas:

@Service
public class ProdutoMobileService {
    // ... (WebClient e Services injetados)

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

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

        // 3. Busca disponibilidade em estoque (em paralelo)
        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(...);
    }
}

3. 编排调用的 Service:

@Service
public class ProdutoMobileService {
    // ... (注入 WebClient 和 Services)

    public ProdutoMobileDTO buscarProdutoMobile(String produtoId) {
        // 1. 从核心后端获取产品
        Produto produto = webClient.get().uri("/api/produtos/" + produtoId)...

        // 2. 获取评价(并行处理)
        CompletableFuture<Avaliacao> futureAvaliacao = CompletableFuture.supplyAsync(() -> avaliacaoService.buscarAvaliacao(produtoId));

        // 3. 获取库存(并行处理)
        CompletableFuture<Estoque> futureEstoque = CompletableFuture.supplyAsync(() -> estoqueService.buscarDisponibilidade(produtoId));

        // 4. 聚合所有数据
        Avaliacao avaliacao = futureAvaliacao.join();
        Estoque estoque = futureEstoque.join();

        // 5. 组装移动端专用 DTO
        return new ProdutoMobileDTO(...);
    }
}

O que aconteceu aqui? O BFF fez três chamadas para diferentes serviços, 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. 😎

这里发生了什么?BFF 向不同的服务发起了三次调用,聚合了数据,将其转换为移动端期望的格式,并返回了一个精简且快速的 Payload。移动端甚至不知道后台存在三个不同的服务,它只接收它需要的东西。😎