A maioria dos desenvolvedores não escolhe a arquitetura que herda. Se você está se perguntando como consumir múltiplas APIs e microsserviços no frontend de forma escalável e sem gerar uma bagunça de código, você está no lugar certo. Um dia você está consumindo dados de uma única API REST monolítica e, no outro, precisa orquestrar requisições para cinco serviços diferentes — cada um com seu próprio contrato.
Um dia você está consumindo dados de uma única API REST monolítica e, no outro, precisa orquestrar requisições para cinco serviços diferentes — cada um com seu próprio contrato de dados, latência e modos de falha.
Enquanto o time de backend se preocupa com contextos delimitados (bounded contexts), consistência eventual e service meshes, o seu desafio no cliente é outro: gerenciar dados desatualizados (stale data), orquestrar estados de carregamento e evitar que o fluxo do usuário quebre quando o serviço de inventário sofre um timeout.
Este artigo é um guia prático para engenheiros de software que trabalham com microsserviços no frontend. Aqui, você aprenderá a consumir múltiplas APIs sem criar uma base de código insustentável. Vamos explorar padrões de arquitetura modernos, como a implementação de um Backend-for-Frontend (BFF), estratégias para lidar com falhas parciais de forma elegante na UI, o gerenciamento de estados distribuídos e como definir contratos de API sólidos com o backend.
O objetivo não é transformá-lo em um engenheiro de backend, mas fornecer os modelos mentais e padrões de resiliência que tornam o desenvolvimento frontend em ecossistemas distribuídos muito mais escalável e menos doloroso.
Pré-requisitos
Para aproveitar ao máximo este artigo, você deve estar familiarizado com:
- React ou um framework de componentes similar (os exemplos usam React e TypeScript)
- Compreensão básica de APIs REST e HTTP
- Experiência em buscar dados em aplicações frontend (fetch, Axios, ou React Query)
- Consciência geral do que são microsserviços (você não precisa ter construído um)
-
Dominar como consumir múltiplas APIs e microsserviços no frontend exige não apenas conhecimento de requisições HTTP, mas uma mudança de mentalidade sobre como sua UI reage a dados distribuídos.
Sumário
- O Problema do Frontend com Microsserviços
- Padrão 1: O Backend-for-Frontend (BFF)
- Padrão 2: Lidando com Falhas Parciais na UI
- Padrão 3: Gerenciando Estado Distribuído
- Padrão 4: Domando Múltiplos Contratos de API
- Padrão 5: Limites de Tempo (Timeout) para Montagem de Página
- Padrão 6: Error Boundaries por Serviço
- Trabalhando com Times de Backend nos Contratos
- Quando Questionar
- Conclusão
O Desafio de Como Consumir Múltiplas APIs e Microsserviços no Frontend
Em uma arquitetura monolítica, o frontend se comunica com uma API. Essa API detém o banco de dados, lida com a lógica de negócio e retorna exatamente o formato de dados que a UI precisa. A vida é simples.
Em uma arquitetura de microsserviços, essa API única se fragmenta em muitas:
Monólito:
Navegador → API → Banco de Dados
Microsserviços:
Navegador → API Gateway → Serviço de Usuário
→ Serviço de Pedido
→ Serviço de Inventário
→ Serviço de Pagamento
→ Serviço de Notificação
Cada um desses serviços pertence a um time diferente, é implantado independentemente e pode usar diferentes formatos de dados ou convenções. Como engenheiro de frontend, agora você tem vários novos problemas:
- Múltiplos contratos: Cada serviço tem seu próprio formato de API. Um “produto” no serviço de inventário tem campos diferentes de um “produto” no serviço de catálogo.
- Falhas parciais: O serviço de pedido pode responder em 50 ms enquanto o serviço de recomendação atinge o timeout. Sua UI precisa lidar com ambos.
- Consistência de dados: Um usuário atualiza seu endereço, mas o serviço de pedido ainda mostra o antigo porque ainda não sincronizou.
- Latência aumentada: Montar uma única página pode exigir três ou quatro chamadas de API em vez de uma.
Estes não são problemas de backend que afetam o frontend por acaso. Eles são fundamentalmente problemas de frontend que requerem soluções de frontend.
Padrão 1: O Backend-for-Frontend (BFF)
O padrão de maior impacto para times de frontend em um mundo de microsserviços é o Backend-for-Frontend. Um BFF é uma camada fina de API que fica entre o navegador e os microsserviços. Pertence ao time de frontend e existe para atender às necessidades específicas do frontend.
Sem BFF:
Navegador → Serviço de Usuário (chamada 1)
Navegador → Serviço de Pedido (chamada 2)
Navegador → Serviço de Inventário (chamada 3)
3 viagens de ida e volta, 3 contratos para gerenciar
Com BFF:
Navegador → BFF → Serviço de Usuário
→ Serviço de Pedido
→ Serviço de Inventário
1 3 round trips, 1 contrato para gerenciar
O BFF agrega chamadas, transforma respostas nos formatos que seus componentes precisam e lida com preocupações entre serviços, como encaminhamento de token de autenticação.
// Endpoint do BFF: GET /api/order-summary/:orderId
// Agrega dados de três serviços em uma resposta amigável ao frontend
import express from "express";
const router = express.Router();
router.get("/api/order-summary/:orderId", async (req, res) => {
const { orderId } = req.params;
const token = req.headers.authorization;
try {
const [order, customer, shipment] = await Promise.allSettled([
fetch(`{{ORDER_SERVICE}}/orders/{{orderId}}`, {
headers: { Authorization: token },
}).then((r) => r.json()),
fetch(`{{USER_SERVICE}}/users/{{req.userId}}`, { // userId definido pelo middleware de autenticação
headers: { Authorization: token },
}).then((r) => r.json()),
fetch(`{{SHIPPING_SERVICE}}/shipments?orderId={{orderId}}`, {
headers: { Authorization: token },
}).then((r) => r.json()),
]);
res.json({
order: order.status === "fulfilled" ? order.value : null,
customer: customer.status === "fulfilled" ? customer.value : null,
shipment: shipment.status === "fulfilled" ? shipment.value : null,
errors: [order, customer, shipment]
.filter((r) => r.status === "rejected")
.map((r) => r.reason.message),
});
} catch (error) {
res.status(500).json({ error: "Falha ao montar o resumo do pedido" });
}
});
Observe o uso de Promise.allSettled em vez de Promise.all. Isso é crítico em um ambiente de microsserviços. Promise.all falha rapidamente: se qualquer serviço estiver fora do ar, toda a requisição falha. Promise.allSettled permite retornar dados parciais, o que leva diretamente ao próximo padrão.
Quando Usar um BFF
Um BFF vale o investimento quando:
- Seu frontend agrega dados de três ou mais serviços por página
- Clientes diferentes (web, mobile, admin) precisam de formatos de dados diferentes dos mesmos serviços
- Você quer que o time de frontend controle os formatos de resposta sem esperar pelos times de backend
Um BFF não é necessário quando:
- Você tem um API gateway que já lida com a agregação (por exemplo, Apollo Federation para GraphQL)
- Você consome apenas um ou dois serviços
- Seus times de backend já fornecem endpoints otimizados para o frontend
Padrão 2: Lidando com Falhas Parciais na UI
Em um monólito, uma requisição ou é bem-sucedida ou falha. Em um mundo de microsserviços, ela pode ser parcialmente bem-sucedida. Os dados do pedido carregam bem, mas o serviço de recomendação está fora do ar. Os detalhes do produto estão disponíveis, mas o serviço de avaliação está lento.
Sua UI precisa lidar com isso de forma elegante. O princípio chave: nunca deixe uma falha de serviço não crítico quebrar um fluxo de usuário crítico.
// Tipos para carregamento parcial de dados
interface ServiceResult<T> {
data: T | null;
status: "loaded" | "error" | "loading";
error?: string;
}
interface OrderPageData {
order: ServiceResult<Order>;
recommendations: ServiceResult<Product[]>;
reviews: ServiceResult<Review[]>;
}
Construa seus componentes para renderizar independentemente com base em quais dados estão disponíveis:
function OrderPage({ orderId }: { orderId: string }) {
const { order, recommendations, reviews } = useOrderPageData(orderId);
// Crítico: o pedido deve carregar ou a página não faz sentido
if (order.status === "loading") return <OrderSkeleton />;
if (order.status === "error") return <ErrorPage message={order.error} />;
return (
<div>
{/* Seção crítica: sempre renderizada */}
<OrderDetails order={order.data} />
{/* Não crítico: degrada-se de forma elegante*/}
<section aria-label="Recomendações">
{recommendations.status === "loaded" ? (
<RecommendationCarousel products={recommendations.data} />
) : recommendations.status === "error" ? (
<EmptyState message="Recomendações Indisponíveis" />
) : (
<CarouselSkeleton />
)}
</section>
{/* Não crítico: degrada-se de forma elegante */}
<section aria-label="Avaliações de clientes">
{reviews.status === "loaded" ? (
<ReviewList reviews={reviews.data} />
) : reviews.status === "error" ? (
<EmptyState message="Avaliações indisponíveis no momento" />
) : (
<ReviewSkeleton />
)}
</section>
</div>
);
}
Classificando Dados Críticos vs. Não Críticos
Nem todos os dados em uma página são igualmente importantes. Antes de construir qualquer página que extraia dados de múltiplos serviços, classifique cada fonte de dados:
| Fonte de Dados | Crítico? | Estratégia de Falha |
|---|---|---|
| Detalhes do pedido | Sim | Mostrar página de erro, bloquear a visão inteira |
| Informação do cliente | Sim | Mostrar página de erro |
| Recomendações | Não | Esconder a seção, mostrar estado vazio |
| Avaliações | Não | Mostrar mensagem “avaliações indisponíveis” |
| Vistos recentemente | Não | Esconder silenciosamente |
Esta classificação deve ser uma decisão consciente tomada com seu time de produto, não algo que você descobre quando um serviço cai em produção.
Padrão 3: Gerenciando Estado Distribuído
Em um mundo monolítico, o servidor é a única fonte da verdade. Em um mundo de microsserviços, a verdade é distribuída. O serviço de usuário conhece o endereço atual do usuário. O serviço de pedido tem um snapshot do endereço no momento do pedido. Estes podem não corresponder.
Dados Desatualizados e Limites de Cache
Quando seu frontend faz cache de dados de múltiplos serviços, você precisa pensar sobre limites de cache. Dados de diferentes serviços tornam-se desatualizados em taxas diferentes.
// Configurar tempos de cache baseados em quão frequentemente os dados subjacentes mudam
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000, // Padrão: 30 segundos
},
},
});
// Catálogo de produtos: muda com pouca frequência
function useProduct(productId: string) {
return useQuery({
queryKey: ["product", productId],
queryFn: () => fetchProduct(productId),
staleTime: 5 * 60_000, // 5 minutos: atualizações de catálogo são raras
});
}
// Níveis de inventário: mudam constantemente
function useStockLevel(productId: string) {
return useQuery({
queryKey: ["stock", productId],
queryFn: () => fetchStockLevel(productId),
staleTime: 10_000, // 10 segundos: estoque muda a cada compra
refetchInterval: 30_000, // Fazer polling a cada 30 segundos em páginas ativas
});
}
// Pedido do próprio usuário: deve refletir o estado mais recente
function useOrder(orderId: string) {
return useQuery({
queryKey: ["order", orderId],
queryFn: () => fetchOrder(orderId),
staleTime: 0, // Sempre refazer fetch: usuário espera ver sua última ação
});
}
O erro é tratar todos os dados cacheados da mesma forma. Informações de produto do serviço de catálogo podem ser cacheadas por minutos. Níveis de estoque do serviço de inventário precisam ser atualizados com muito mais frequência. Os dados do pedido do próprio usuário devem estar sempre frescos porque ele acabou de realizar uma ação e espera ver o resultado.
Invalidação Entre Serviços
A parte mais complicada do estado distribuído é saber quando invalidar. Quando um usuário faz um pedido, você precisa:
- Invalidar a lista de pedidos (serviço de pedido)
- Invalidar o nível de estoque (serviço de inventário)
- Invalidar os pontos de fidelidade do usuário (serviço de usuário)
// Após a colocação bem-sucedida de um pedido, invalidar através das fronteiras de serviço
async function placeOrder(cart: Cart): Promise<Order> {
const order = await api.post("/api/orders", { items: cart.items });
// Invalidar dados de múltiplos serviços que esta ação afetou
queryClient.invalidateQueries({ queryKey: ["orders"] });
queryClient.invalidateQueries({ queryKey: ["stock"] });
queryClient.invalidateQueries({ queryKey: ["loyalty-points"] });
// Atualizar otimisticamente o carrinho (pertencente ao frontend)
queryClient.setQueryData(["cart"], { items: [] });
return order;
}
Isso é manual e propenso a erros. Toda vez que um novo serviço se importa com eventos de pedido, você precisa se lembrar de adicionar uma invalidação aqui.
Para alternativas mais robustas, você pode usar server-sent events ou conexões WebSocket para permitir que o backend envie sinais de invalidação para o frontend, ou adotar um padrão pub/sub dentro da sua camada de estado do lado do cliente, onde chaves de cache se inscrevem em eventos de domínio.
Essas abordagens estão além do escopo deste artigo, mas vale a pena explorar uma vez que sua tabela de invalidação cresça para mais de uma dúzia de entradas.
Enquanto isso, documentar essas dependências entre serviços em uma tabela ajuda:
| Ação do Usuário | Serviços Afetados | Chaves de Cache para Invalidar |
|---|---|---|
| Fazer pedido | Pedido, Inventário, Usuário | orders, stock, loyalty-points, cart |
| Atualizar endereço | Usuário, Entrega | user-profile, shipping-estimates |
| Escrever avaliação | Avaliações, Produto | reviews, product (mudança de nota) |
Padrão 4: Domando Múltiplos Contratos de API
Em um mundo de microsserviços, cada serviço define seu próprio contrato de API. O serviço de usuário retorna firstName e lastName. O serviço de pedido retorna customerName como uma única string. O serviço de notificação espera fullName. Mesmo conceito, três nomes de campo diferentes.
A Camada Adaptadora
Crie uma camada adaptadora que traduz a resposta de cada serviço em um modelo de domínio consistente que seus componentes usam:
// Modelos de domínio: com o que o frontend realmente trabalha
interface User {
id: string;
fullName: string;
email: string;
address: Address;
}
// Adaptador para o Serviço de Usuário
function adaptUserServiceResponse(raw: UserServiceResponse): User {
return {
id: raw.userId,
fullName: `{{raw.firstName}} {{raw.lastName}}`,
email: raw.emailAddress,
address: {
line1: raw.address.street,
city: raw.address.city,
postcode: raw.address.zipCode,
country: raw.address.countryCode,
},
};
}
// Adaptador para o Serviço de Pedido (que incorpora um formato de usuário diferente)
function adaptOrderCustomer(raw: OrderServiceCustomer): User {
return {
id: raw.customerId,
fullName: raw.customerName,
email: raw.email,
address: {
line1: raw.shippingAddress.addressLine1,
city: raw.shippingAddress.city,
postcode: raw.shippingAddress.postalCode,
country: raw.shippingAddress.country,
},
};
}
Seus componentes trabalham apenas com o tipo User. Eles nunca veem as respostas brutas do serviço. Quando um serviço muda sua API, você atualiza um adaptador, não cada componente que exibe o nome de um usuário.
Onde Colocar a Camada Adaptadora
Se você tem um BFF, os adaptadores residem lá. O navegador nunca vê a resposta bruta do serviço. Se você está chamando serviços diretamente do frontend, coloque os adaptadores em sua camada de busca de dados, entre a chamada HTTP e o cache:
// O adaptador executa antes dos dados entrarem no cache
function useUser(userId: string) {
return useQuery({
queryKey: ["user", userId],
queryFn: async () => {
const raw = await fetch(`/api/users/${userId}`).then((r) => r.json());
return adaptUserServiceResponse(raw);
},
});
}
Padrão 5: Limites de Tempo (Timeout) para Montagem de Página
Quando uma página depende de múltiplos serviços, você precisa de uma estratégia de timeout. Sem uma, o tempo de carregamento da sua página é determinado pelo serviço mais lento, e em um mundo de microsserviços, sempre há um serviço lento.
Um orçamento de timeout aloca um tempo máximo para montar todos os dados que uma página precisa. Se um serviço não crítico não responder dentro do seu orçamento, você renderiza sem ele.
Na prática, este utilitário reside em uma camada de serviço compartilhada (por exemplo, lib/api.ts) em vez de inline com a lógica de montagem de cada página. Aqui está a implementação:
// lib/api.ts: utilitário de timeout compartilhado
async function fetchWithTimeout<T>(
url: string,
options: RequestInit,
timeoutMs: number
): Promise<T | null> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
return response.json();
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") {
console.warn(`Requisição para {{url}} expirou após {{timeoutMs}}ms`);
}
return null;
} finally {
clearTimeout(timeout);
}
}
// Montagem de página com timeouts em camadas
async function assembleProductPage(productId: string): Promise<ProductPageData> {
// Dados críticos: timeout mais longo, página falha sem eles
const product = await fetchWithTimeout<Product>(
`/api/products/${productId}`,
{},
3000 // Orçamento de 3 segundos para dados críticos
);
if (!product) {
throw new Error("Produto não encontrado");
}
// Dados não críticos: timeout mais curto, página renderiza sem eles
const [reviews, recommendations, relatedProducts] = await Promise.all([
fetchWithTimeout<Review[]>(
`/api/reviews?productId=${productId}`,
{},
1500 // Orçamento de 1.5 segundos
),
fetchWithTimeout<Product[]>(
`/api/recommendations?productId=${productId}`,
{},
1000 // Orçamento de 1 segundo: bom ter
),
fetchWithTimeout<Product[]>(
`/api/products/${productId}/related`,
{},
1000
),
]);
return {
product,
reviews: reviews ?? [],
recommendations: recommendations ?? [],
relatedProducts: relatedProducts ?? [],
};
}
Observe os diferentes orçamentos. Dados críticos (o produto em si) recebem 3 segundos. Dados não críticos (avaliações, recomendações) recebem 1–1.5 segundos. Se as recomendações estiverem lentas, você mostra o produto sem elas. O usuário não espera por um serviço que ele talvez nem veja.
Padrão 6: Error Boundaries por Serviço
Error boundaries do React são especialmente poderosas em um frontend de microsserviços. Em vez de uma error boundary no nível da página, coloque boundaries em torno de seções que mapeiam para diferentes serviços de backend.
Se você ainda não usou error boundaries, aqui está uma implementação mínima. Error boundaries devem ser componentes de classe, React ainda não as suporta como componentes de função (veja a documentação do React para mais detalhes):
class ErrorBoundary extends React.Component<
{ fallback: React.ReactNode; children: React.ReactNode },
{ hasError: boolean } > {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error("ErrorBoundary capturou:", error, info);
}
render() {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}
Com isso pronto, limite o escopo de suas boundaries para seções de serviço individuais:
function ProductPage({ productId }: { productId: string }) {
return (
<div>
{/* Se o serviço de produto falhar, mostrar erro de página inteira */}
<ErrorBoundary fallback={<ProductErrorPage />}>
<Suspense fallback={<ProductSkeleton />}>
<ProductDetails productId={productId} />
</Suspense>
</ErrorBoundary>
{/* Se o serviço de avaliação falhar, apenas esconder avaliações */}
<ErrorBoundary fallback={<EmptyState message="Avaliações indisponíveis" />}>
<Suspense fallback={<ReviewSkeleton />}>
<ProductReviews productId={productId} />
</Suspense>
</ErrorBoundary>
{/* Se recomendações falharem, esconder silenciosamente */}
<ErrorBoundary fallback={null}>
<Suspense fallback={<CarouselSkeleton />}>
<Recommendations productId={productId} />
</Suspense>
</ErrorBoundary>
</div>
);
}
Cada boundary captura erros de sua própria fonte de dados independentemente. A falha do serviço de avaliação não afeta os detalhes do produto. O timeout do serviço de recomendação não mostra nenhum erro – a seção simplesmente não renderiza.
Isso mapeia diretamente para sua classificação de crítico/não crítico. Serviços críticos recebem error boundaries com UI de erro visível. Serviços não críticos recebem boundaries que se degradam silenciosamente ou mostram um estado vazio mínimo.
Trabalhando com Times de Backend nos Contratos
Os padrões técnicos acima resolvem sintomas. A causa raiz da maioria das dores do frontend em ambientes de microsserviços é a comunicação ruim entre times de frontend e backend sobre contratos de API.
Conversas sobre Contrato para se Ter Cedo
1. Quais campos o frontend realmente usará?
Os serviços de backend frequentemente expõem todo o seu modelo de dados. O frontend usa três campos. Se o time de backend sabe de quais campos você depende, eles podem manter esses campos com mais cuidado e depreciar aqueles que ninguém usa.
2. Qual é o orçamento de latência esperado para este endpoint?
Se a página do produto tem um orçamento total de 2 segundos e o serviço de recomendação tem uma média de 1.8 segundos, você tem um problema antes mesmo de escrever qualquer código frontend. Traga isso à tona cedo.
3. O que acontece quando este serviço está degradado?
Pergunte a cada time de backend: “Se o seu serviço responder com erros 500 por uma hora, o que o frontend deve mostrar?” Esta pergunta frequentemente revela que ninguém pensou nisso, o que é exatamente por que você precisa perguntar.
4. Como vocês comunicarão Quebras de contrato (breaking changes)?
Acordem um processo. Seja diffs de especificação OpenAPI em pull requests, um canal do Slack para mudanças de API, ou endpoints versionados, escolham algo e cobrem um do outro.
Contratos de API como Artefatos Compartilhados
Insista em contratos legíveis por máquina. Especificações OpenAPI, esquemas GraphQL ou definições Protocol Buffer servem como uma fonte de verdade compartilhada entre times de frontend e backend. Eles possibilitam:
Geração automatizada de tipos: Ferramentas como openapi-typescript geram tipos TypeScript a partir de especificações OpenAPI. Quando o backend muda um campo, seu build falha imediatamente, não em produção.
Teste de contrato: Ferramentas como Pact permitem definir os pares esperados de requisição/resposta da perspectiva do frontend. O backend executa esses testes em seu pipeline de CI. Se suas mudanças quebram as expectativas do frontend, o pipeline falha.
Mock servers: Mocks gerados a partir da especificação permitem construir o frontend antes que o backend esteja pronto. Quando o serviço real é entregue, seu código já funciona.
// Tipos gerados da especificação OpenAPI, sempre em sincronia com o backend
import type { components } from "./generated/inventory-api";
type Product = components["schemas"]["Product"];
type StockLevel = components["schemas"]["StockLevel"];
// Se o backend renomear "available" para "inStock",
// este código falha em tempo de compilação, não em produção
function formatStockMessage(stock: StockLevel): string {
if (stock.available > 10) return "Em Estoque";
if (stock.available > 0) return `Apenas ${stock.available} restantes`;
return "Fora de Estoque";
}
Testando Contra Múltiplos Serviços
O teste de contrato captura quebras de contrato do lado do backend, mas você também precisa testar o comportamento do seu frontend quando os serviços respondem de maneiras inesperadas. Mock Service Worker (MSW) permite criar manipuladores de mock por serviço em seu ambiente de teste:
import { setupServer } from "msw/node";
import { http, HttpResponse } from "msw";
// Mockar cada serviço independentemente
const server = setupServer(
http.get("/api/products/:id", () =>
HttpResponse.json({ productId: "abc-123", name: "Widget", price: 49.99 })
),
http.get("/api/reviews", () =>
HttpResponse.json([{ rating: 5, body: "Ótimo produto" }])
)
);
// Teste: o que acontece quando o serviço de avaliação está fora do ar?
test("renderiza página de produto quando serviço de avaliações falha", async () => {
server.use(
http.get("/api/reviews", () => HttpResponse.error())
);
render(<ProductPage productId="abc-123" />);
expect(await screen.findByText("Widget")).toBeInTheDocument();
expect(await screen.findByText("Avaliações indisponíveis")).toBeInTheDocument();
});
Isso permite simular os cenários de falha parcial do Padrão 2 em sua suíte de testes. Teste sua camada adaptadora (Padrão 4) com testes unitários contra fixtures de resposta de serviço brutas e use MSW para testes de integração que verificam se a página completa é montada corretamente quando serviços individuais estão lentos, fora do ar ou retornam formatos inesperados.
Quando Questionar
Nem todo problema de microsserviços tem uma solução de frontend. Às vezes, a resposta certa é questionar a arquitetura.
Recuse quando o frontend está fazendo mais de 5 chamadas de API para uma única página. Isso é um sinal de que ou os serviços são muito granulares ou está faltando uma camada de agregação. A correção é um BFF ou uma API composta, não mais chamadas Promise.all no navegador.
Recuse quando dois serviços retornam dados conflitantes sobre a mesma entidade. Se o serviço de usuário diz que o nome do usuário é “Jane” e o serviço de pedido diz que é “Janet,” este é um problema de consistência de dados que o frontend não pode resolver. Precisa ser corrigido na fonte, seja através de sincronização orientada a eventos entre serviços ou estabelecendo um serviço como a fonte autoritativa para aquele campo.
Recuse quando times de backend fazem quebras de contratos sem aviso. Se sua aplicação em produção quebra porque um serviço renomeou um campo em uma atualização de versão menor, isso é uma falha de processo. Advogue por APIs versionadas, avisos de depreciação e teste de contrato.
Você não é apenas um consumidor de APIs. Você é um stakeholder em como essas APIs são projetadas. Quanto mais cedo você participar das conversas de design de API, menos surpresas você enfrentará em produção.
Conclusão: O Frontend como Protagonista na Arquitetura
A arquitetura de microsserviços pode nascer como uma decisão de backend, mas suas consequências colaterais são sentidas de forma mais aguda no frontend. Os padrões que exploramos neste artigo não farão a complexidade inerente de sistemas distribuídos desaparecer, mas fornecem um arsenal estruturado para que sua interface não seja refém dessa complexidade.
Para recapitular os pilares de um frontend resiliente em microsserviços:
-
Assuma a propriedade da agregação: A implementação de um BFF (Backend-for-Frontend) devolve ao time de cliente o controle sobre o payload, reduzindo round trips e mitigando gargalos de rede.
-
Isole as falhas de forma elegante: Classifique as fontes de dados entre críticas e secundárias. Use Error Boundaries e limites de timeout precisos para garantir que a lentidão de um microsserviço de recomendação não impeça o usuário de finalizar uma compra.
-
Defenda os contratos de API: Tipagem automatizada (OpenAPI/GraphQL) e testes de contrato (como o Pact) são a sua rede de segurança contra breaking changes não documentadas.
-
Saiba quando questionar a arquitetura: Se a sua aplicação precisa de dezenas de chamadas HTTP no navegador apenas para renderizar a tela inicial, o problema não está no seu código React ou Next.js — falta uma camada de agregação no servidor.
A transição de um monólito para um ecossistema distribuído exige uma mudança de mindset. Você deixa de ser apenas um consumidor de endpoints para se tornar um orquestrador de resiliência e experiência do usuário.
Você não é apenas um consumidor de APIs. Você é um stakeholder em como essas APIs são projetadas. Entender como consumir múltiplas APIs e microsserviços no frontend significa saber a hora certa de participar das conversas de design de API para evitar surpresas em produção.
Se este guia ajudou a clarear a sua visão sobre arquitetura no frontend, não esqueça de se inscrever na nossa newsletter para mais conteúdos profundos sobre desenvolvimento full stack, performance e engenharia de software.
Continue impulsionando sua carreira:
Se você gostou deste conteúdo sobre arquitetura de frontend, separamos materiais avançados no portal para você dominar a stack completa:
-
- Como Dominar o Refero Styles e Google Stitch em 2026
Neste artigo, exploramos como o Refero Styles está mudando o jogo com o conceito de DESIGN.md e o uso de MCP (Model Context Protocol) para alimentar agentes de codificação.
- Como Dominar o Refero Styles e Google Stitch em 2026
Perguntas Frequentes: Frontend e Microsserviços
O que é o padrão BFF (Backend-for-Frontend)?
+
O BFF é uma camada de arquitetura que atua como um intermediário entre o cliente (navegador/mobile) e os microsserviços. Pertencente ao time de frontend, ele agrega chamadas HTTP, adapta modelos de dados e reduz round trips, entregando um payload otimizado para a UI.
Como lidar com lentidão e falhas parciais em microsserviços?
+
A regra principal é separar dados críticos de não críticos. Para dados não essenciais (ex: recomendações), utilize limites de timeout curtos e permita que a UI degrade de forma elegante, ocultando a seção silenciosamente. Para proteger o fluxo, isole as requisições em componentes usando Error Boundaries no React.
Quando o frontend deve questionar a arquitetura de backend?
+
O engenheiro de frontend deve dizer não quando for necessário fazer mais de 5 chamadas concorrentes para montar uma única tela, quando APIs introduzirem breaking changes sem aviso prévio, ou quando houver conflitos de estado distribuído entre serviços. Nesses cenários, é preciso exigir uma camada de agregação ou testes de contrato (ex: Pact).
