🏗️ 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。移动端甚至不知道后台存在三个不同的服务,它只接收它需要的东西。😎