Cloud e plataforma

Composição de API e BFF (Backend for Frontend): Orquestrando Microsserviços para Melhor UX

Aplicações frontend chamando múltiplos microsserviços diretamente enfrentam over-fetching, under-fetching e dados fragmentados. Padrões BFF centralizam composição de API, reduzem complexidade do cliente e permitem otimizações específicas de plataforma.

15/03/20269 min de leituraCloud
Composição de API e BFF (Backend for Frontend): Orquestrando Microsserviços para Melhor UX

Resumo executivo

Aplicações frontend chamando múltiplos microsserviços diretamente enfrentam over-fetching, under-fetching e dados fragmentados. Padrões BFF centralizam composição de API, reduzem complexidade do cliente e permitem otimizações específicas de plataforma.

Ultima atualizacao: 15/03/2026

Resumo executivo

A abordagem ingênua de microsserviços—frontend chamando múltiplos serviços backend diretamente—cria problemas significativos: over-fetching e under-fetching de dados, problemas de consulta N+1 em fronteiras de serviço, tratamento de erro fragmentado e lógica duplicada em plataformas frontend.

Padrões Backend for Frontend (BFF) resolvem esses problemas introduzindo uma camada de composição que agrega dados de múltiplos serviços backend, normaliza respostas e fornece APIs específicas de plataforma para web, mobile e outros clientes.

Em 2026, composição de API evoluiu de agregação REST simples para federação GraphQL sofisticada, edge computing e estratégias inteligentes de caching. Times que implementam padrões BFF reduzem complexidade frontend, melhoram experiência do usuário e permitem evolução independente de serviços frontend e backend.

O problema com comunicação direta frontend-para-microsserviço

Anti-padrão: Frontend chamando múltiplos serviços

Aplicação Frontend
    ↓
[Serviço Usuário] → GET /users/123
    ↓
[Serviço Pedidos] → GET /users/123/orders
    ↓
[Serviço Pagamentos] → GET /orders/456/payments
    ↓
[Serviço Catálogo] → GET /products/789

Problemas:

  1. Over-fetching: Frontend recebe mais dados do que precisa
  2. Under-fetching: Frontend deve fazer múltiplas chamadas para dados completos
  3. Consultas N+1: Buscando dados relacionados em fronteiras de serviço
  4. Tratamento de erro fragmentado: Cada serviço tem formato de erro diferente
  5. Necessidades específicas de plataforma: Web e mobile precisam de formatos de dados diferentes
  6. Complexidade de caching: Cada serviço deve ser cacheado independentemente

A solução BFF

Aplicações Frontend
    ↓
[BFF Web] ────────┐
    ↓               │
[BFF Mobile] ──────┼──→ [Serviço Usuário]
    ↓               │       [Serviço Pedidos]
[BFF Desktop] ─────┤       [Serviço Pagamentos]
    ↓               │       [Serviço Catálogo]
                [Camada de Caching]

Benefícios:

  • Única requisição do frontend
  • Dados agregados de múltiplos serviços
  • Otimização específica de plataforma
  • Tratamento de erro centralizado
  • Estratégia de caching unificada

Padrões de arquitetura BFF

Padrão 1: Composição baseada em REST

Agregar dados de múltiplos serviços REST:

typescript// Serviço BFF para dashboard de usuário
interface DashboardData {
  user: User;
  recentOrders: Order[];
  activeSubscription: Subscription | null;
  recommendations: Product[];
  notifications: Notification[];
}

class DashboardBFF {
  constructor(
    private userService: UserService,
    private orderService: OrderService,
    private subscriptionService: SubscriptionService,
    private catalogService: CatalogService,
    private notificationService: NotificationService
  ) {}

  async getDashboard(userId: string, platform: 'web' | 'mobile' | 'desktop'): Promise<DashboardData> {
    // Chamadas paralelas para serviços backend
    const [user, orders, subscription, notifications] = await Promise.allSettled([
      this.userService.getUser(userId),
      this.orderService.getRecentOrders(userId, 5),
      this.subscriptionService.getActiveSubscription(userId),
      this.notificationService.getNotifications(userId)
    ]);

    // Tratar falhas parciais graciosamente
    const dashboard: DashboardData = {
      user: this.resolveOrDefault(user, { id: userId, name: 'Desconhecido' }),
      recentOrders: this.resolveOrDefault(orders, []),
      activeSubscription: this.resolveOrDefault(subscription, null),
      notifications: this.resolveOrDefault(notifications, [])
    };

    // Busca de dados específica de plataforma
    if (platform === 'web') {
      dashboard.recommendations = await this.catalogService.getRecommendations(userId, 20);
    } else if (platform === 'mobile') {
      dashboard.recommendations = await this.catalogService.getRecommendations(userId, 10);
    }

    return dashboard;
  }

  private resolveOrDefault<T>(
    resultado: PromiseSettledResult<T>,
    defaultValue: T
  ): T {
    if (resultado.status === 'fulfilled') {
      return resultado.value;
    }

    // Log erro mas não falhar requisição inteira
    console.error(`Agregação BFF falhou: ${resultado.reason}`);

    return defaultValue;
  }
}

// Rota Express
app.get('/api/dashboard', async (req: Request, res: Response) => {
  const userId = req.user.id;
  const platform = req.headers['x-platform'] || 'web';

  const dashboard = await dashboardBFF.getDashboard(userId, platform);

  res.json(dashboard);
});

Padrão 2: Composição baseada em GraphQL

Usar GraphQL para fornecer composição flexível de dados:

typescriptimport { ApolloServer, gql } from 'apollo-server-express';

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    subscription: Subscription
  }

  type Order {
    id: ID!
    createdAt: DateTime!
    total: Float!
    status: OrderStatus!
    items: [OrderItem!]!
  }

  type Product {
    id: ID!
    name: String!
    price: Float!
  }

  type Subscription {
    id: ID!
    plan: String!
    status: String!
    renewsAt: DateTime!
  }

  type Dashboard {
    user: User!
    recentOrders: [Order!]!
    activeSubscription: Subscription
  }

  type Query {
    dashboard(userId: ID!, platform: String!): Dashboard!
  }
`;

const resolvers = {
  Query: {
    async dashboard(_: any, { userId, platform }: any, context: any) {
      // Busca de dados paralela
      const [user, orders, subscription] = await Promise.all([
        context.dataSources.userService.getUser(userId),
        context.dataSources.orderService.getRecentOrders(userId, 5),
        context.dataSources.subscriptionService.getActiveSubscription(userId)
      ]);

      return { user, recentOrders: orders, activeSubscription: subscription };
    }
  },
  User: {
    subscription: (user: User) => {
      return user.subscriptionId
        ? context.dataSources.subscriptionService.getSubscription(user.subscriptionId)
        : null;
    }
  },
  Order: {
    items: (order: Order) => {
      return context.dataSources.orderService.getOrderItems(order.id);
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  dataSources: {
    userService: new UserService(),
    orderService: new OrderService(),
    subscriptionService: new SubscriptionService()
  }
});

Padrão 3: Composição em nível de Gateway

Implementar composição em nível de API Gateway:

yaml# Configuração Kong API Gateway
services:
  - name: dashboard-service
    url: http://dashboard-bff:8080
    routes:
      - name: dashboard-route
        paths:
          - /api/dashboard
    plugins:
      - name: request-transformer
        config:
          add:
            headers:
              - x-platform:$(http_x_platform)
      - name: rate-limiting
        config:
          minute: 100
          policy: local
      - name: prometheus
        config:
          per_consumer: true

  - name: user-service
    url: http://user-service:8080
    routes:
      - name: user-route
        paths:
          - /api/users

  - name: order-service
    url: http://order-service:8080
    routes:
      - name: order-route
        paths:
          - /api/orders

Otimização específica de plataforma

BFF Web

Otimizar para navegadores web com payloads maiores e UI complexa:

typescriptclass DashboardBFFWeb {
  async getDashboard(userId: string): Promise<DadosDashboardWeb> {
    const [user, orders, subscription, notifications] = await Promise.allSettled([
      this.userService.getUser(userId),
      this.orderService.getRecentOrders(userId, 10),  // Mais pedidos para web
      this.subscriptionService.getActiveSubscription(userId),
      this.notificationService.getNotifications(userId, 20)  // Mais notificações
    ]);

    return {
      user: this.resolveOrDefault(user),
      recentOrders: this.resolveOrDefault(orders),
      activeSubscription: this.resolveOrDefault(subscription),
      notifications: this.resolveOrDefault(notifications),
      recommendations: await this.catalogService.getRecommendations(userId, 30),  // Mais recomendações
      analytics: await this.analyticsService.getUserAnalytics(userId),  // Analytics exclusivo web
      featureFlags: await this.featureFlagService.getUserFlags(userId)
    };
  }
}

BFF Mobile

Otimizar para mobile com payloads menores e dados limitados:

typescriptclass DashboardBFFMobile {
  async getDashboard(userId: string): Promise<DadosDashboardMobile> {
    const [user, orders, subscription] = await Promise.allSettled([
      this.userService.getUser(userId),
      this.orderService.getRecentOrders(userId, 3),  // Menos pedidos para mobile
      this.subscriptionService.getActiveSubscription(userId)
    ]);

    return {
      user: this.resolveOrDefault(user),
      recentOrders: this.resolveOrDefault(orders),
      activeSubscription: this.resolveOrDefault(subscription),
      quickActions: this.getQuickActionsMobile(),  // Ações exclusivas mobile
      offlineCache: await this.getOfflineCacheData(userId),  // Dados offline-first
      notificationsCount: await this.notificationService.getUnreadCount(userId)  // Apenas contagem
    };
  }

  private getQuickActionsMobile(): QuickAction[] {
    return [
      { type: 'scan_qr_code', label: 'Escanear QR Code' },
      { type: 'view_wallet', label: 'Visualizar Carteira' },
      { type: 'support_chat', label: 'Suporte' }
    ];
  }

  private async getOfflineCacheData(userId: string): Promise<OfflineCache> {
    return {
      userProfile: await this.userService.getUser(userId),
      recentOrders: await this.orderService.getRecentOrders(userId, 3),
      // Dados especificamente otimizados para acesso offline
    };
  }
}

Estratégias de caching para BFF

Estratégia 1: Caching de resposta

Cache de respostas agregadas em nível BFF:

typescriptclass DashboardBFFComCache {
  private cache = new Map<string, CacheEntry>();
  private readonly CACHE_TTL = 300000;  // 5 minutos

  async getDashboard(userId: string, platform: string): Promise<DashboardData> {
    const cacheKey = `dashboard:${userId}:${platform}`;
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() < cached.expiresAt) {
      return cached.data;
    }

    // Buscar dados frescos
    const data = await this.fetchDashboard(userId, platform);

    // Cache de resposta
    this.cache.set(cacheKey, {
      data,
      expiresAt: Date.now() + this.CACHE_TTL
    });

    return data;
  }

  private async fetchDashboard(userId: string, platform: string): Promise<DashboardData> {
    // Implementação como mostrado em exemplos anteriores
  }
}

Estratégia 2: Caching parcial com chaves de cache

Cache de respostas individuais de serviço e composição sob demanda:

typescriptclass DashboardBFFCacheParcial {
  private cache = new Map<string, CacheEntry>();

  async getDashboard(userId: string, platform: string): Promise<DashboardData> {
    // Chaves de cache para dados individuais
    const userCacheKey = `user:${userId}`;
    const ordersCacheKey = `orders:${userId}`;
    const subscriptionCacheKey = `subscription:${userId}`;

    // Verificar cache para cada ponto de dados
    const [user, orders, subscription] = await Promise.all([
      this.getCached(userCacheKey, () => this.userService.getUser(userId)),
      this.getCached(ordersCacheKey, () => this.orderService.getRecentOrders(userId, 5)),
      this.getCached(subscriptionCacheKey, () => this.subscriptionService.getActiveSubscription(userId))
    ]);

    return {
      user,
      recentOrders: orders,
      activeSubscription: subscription,
      // Dados específicos de plataforma não cacheados
      recommendations: await this.catalogService.getRecommendations(userId, platform === 'web' ? 20 : 10)
    };
  }

  private async getCached<T>(
    cacheKey: string,
    fetchFn: () => Promise<T>
  ): Promise<T> {
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() < cached.expiresAt) {
      return cached.data;
    }

    const data = await fetchFn();

    this.cache.set(cacheKey, {
      data,
      expiresAt: Date.now() + this.getTTL(cacheKey)
    });

    return data;
  }

  private getTTL(cacheKey: string): number {
    // TTL diferente para diferentes tipos de dados
    if (cacheKey.startsWith('user:')) {
      return 300000;  // 5 minutos
    } else if (cacheKey.startsWith('orders:')) {
      return 60000;  // 1 minuto
    } else if (cacheKey.startsWith('subscription:')) {
      return 3600000;  // 1 hora
    }

    return 60000;  // Default 1 minuto
  }
}

Tratamento de erro e resiliência

Padrão: Degradação graciosa

typescriptclass DashboardBFFResiliente {
  async getDashboard(userId: string, platform: string): Promise<DashboardData> {
    const results = await Promise.allSettled([
      this.userService.getUser(userId),
      this.orderService.getRecentOrders(userId, 5),
      this.subscriptionService.getActiveSubscription(userId),
      this.catalogService.getRecommendations(userId, 10),
      this.notificationService.getNotifications(userId, 5)
    ]);

    const dashboard: Partial<DashboardData> = {};

    // Tratar cada resultado de serviço independentemente
    if (results[0].status === 'fulfilled') {
      dashboard.user = results[0].value;
    } else {
      console.error('Serviço usuário falhou:', results[0].reason);
      dashboard.user = this.getDefaultUser();
    }

    if (results[1].status === 'fulfilled') {
      dashboard.recentOrders = results[1].value;
    } else {
      console.error('Serviço pedidos falhou:', results[1].reason);
      dashboard.recentOrders = [];
    }

    if (results[2].status === 'fulfilled') {
      dashboard.activeSubscription = results[2].value;
    } else {
      console.error('Serviço assinatura falhou:', results[2].reason);
      dashboard.activeSubscription = null;
    }

    // Features opcionais podem falhar silenciosamente
    if (results[3].status === 'fulfilled') {
      dashboard.recommendations = results[3].value;
    } else {
      console.warn('Serviço recomendações indisponível');
      dashboard.recommendations = [];
    }

    return dashboard as DashboardData;
  }

  private getDefaultUser(): User {
    return {
      id: '',
      name: 'Convidado',
      email: 'convidado@example.com',
      subscriptionId: null
    };
  }
}

Melhores práticas de implementação

1. Versionar APIs BFF independentemente

/api/v1/dashboard → BFF Web v1
/api/v2/dashboard → BFF Web v2
/api/v1/mobile/dashboard → BFF Mobile v1

2. Usar detecção de plataforma

typescriptfunction detectarPlataforma(requisicao: Request): Platform {
  const userAgent = requisicao.headers['user-agent'] || '';

  if (userAgent.includes('Android') || userAgent.includes('iPhone')) {
    return 'mobile';
  }

  if (userAgent.includes('Electron') || userAgent.includes('Desktop')) {
    return 'desktop';
  }

  return 'web';
}

3. Implementar respostas de erro adequadas

typescriptinterface BFFErrorResponse {
  error: {
    code: string;
    message: string;
    details?: any;
    requestId: string;
    timestamp: string;
  };
}

function criarErrorResponse(
  code: string,
  message: string,
  details?: any
): BFFErrorResponse {
  return {
    error: {
      code,
      message,
      details,
      requestId: generateRequestId(),
      timestamp: new Date().toISOString()
    }
  };
}

// Uso
app.get('/api/dashboard', async (req: Request, res: Response) => {
  try {
    const userId = req.user.id;
    const platform = detectarPlataforma(req);

    const dashboard = await dashboardBFF.getDashboard(userId, platform);

    res.json(dashboard);
  } catch (error) {
    if (error instanceof ServiceUnavailableError) {
      res.status(503).json(criarErrorResponse(
        'SERVICO_INDISPONIVEL',
        'Alguns serviços estão temporariamente indisponíveis',
        { available: error.availableServices }
      ));
    } else {
      res.status(500).json(criarErrorResponse(
        'ERRO_INTERNO',
        'Ocorreu um erro inesperado'
      ));
    }
  }
});

4. Monitorar e medir performance BFF

typescriptclass MetricasBFF {
  async trackRequisicao(
    operacao: string,
    platform: string,
    duracao: number,
    sucesso: boolean,
    latenciasServico: Record<string, number>
  ): Promise<void> {
    await metrics.track({
      operacao,
      platform,
      duracao,
      sucesso,
      timestamp: new Date(),
      latenciasServico
    });

    // Alertar sobre agregações lentas
    if (duracao > 2000) {  // 2 segundos
      await alerting.notify({
        message: 'Requisição BFF lenta',
        details: { operacao, platform, duracao }
      });
    }
  }
}

Conclusão

Composição de API e padrões BFF tornaram-se essenciais para arquiteturas de microsserviços em 2026. Ao introduzir uma camada de composição entre frontend e serviços backend, times podem reduzir complexidade de cliente, otimizar experiência do usuário para cada plataforma e permitir evolução independente de serviços.

A organização madura não escolhe entre REST, GraphQL ou APIs específicas de plataforma—elas implementam camadas BFF que podem combinar múltiplas abordagens baseadas em necessidades específicas de cada plataforma frontend. BFF não é apenas sobre agregação—é sobre fornecer dados certos, no formato certo, para a plataforma certa.


Precisa implementar padrões BFF ou otimizar sua arquitetura de API para melhor UX? Fale com a Imperialis sobre arquitetura de composição de API, implementação BFF e padrões de integração frontend-backend.

Fontes

Leituras relacionadas