Padrões de API Composition para Agregação de Dados entre Múltiplos Serviços

1. Fundamentos da API Composition em Arquiteturas Distribuídas

1.1 Definição e motivação

Em arquiteturas de microsserviços, cada serviço gerencia seu próprio domínio de dados. Quando uma aplicação cliente precisa exibir informações que combinam dados de múltiplos serviços — como detalhes de um pedido (serviço de pedidos) com dados do cliente (serviço de clientes) e status de entrega (serviço de logística) — surge a necessidade de um padrão de composição de APIs. A API composition é o mecanismo que coordena chamadas a múltiplos serviços e combina suas respostas em um único resultado coeso.

1.2 Problemas comuns

O principal desafio é o problema N+1 requests: para cada item de uma lista, o cliente precisa fazer uma requisição adicional para obter dados relacionados. Isso gera latência cumulativa e degradação da experiência do usuário. Outro problema é a inconsistência temporal — dados lidos de diferentes serviços em momentos distintos podem estar dessincronizados.

1.3 Trade-offs entre abordagens

A composição pode ocorrer em três camadas: no cliente (maior latência, acoplamento), no gateway (ponto único de orquestração) ou no backend (serviço dedicado de composição). Cada abordagem tem implicações diferentes em termos de acoplamento, performance e manutenibilidade.

2. Padrão API Gateway Composition

2.1 Implementação clássica

O gateway atua como orquestrador central, realizando chamadas paralelas aos serviços downstream. Exemplo de implementação:

GET /api/orders/composite/123

// Gateway faz chamadas paralelas:
// 1. Serviço de Pedidos: GET /orders/123
// 2. Serviço de Clientes: GET /customers/456
// 3. Serviço de Logística: GET /shipments/789

// Resposta agregada:
{
  "orderId": 123,
  "customer": { "name": "Maria Silva", "email": "maria@email.com" },
  "items": [
    { "productId": 1, "name": "Notebook", "quantity": 1, "price": 4500.00 }
  ],
  "shipment": { "status": "entregue", "trackingCode": "BR123456789" }
}

2.2 Estratégias de paralelismo

O fan-out controlado distribui as chamadas simultaneamente, mas com limites de concorrência para evitar sobrecarga nos serviços downstream:

// Configuração de fan-out com limite de 5 chamadas paralelas
// Timeout global: 2 segundos
// Timeout por serviço: 1 segundo
// Se um serviço falhar, retorna fallback parcial

2.3 Gerenciamento de erros parciais

// Exemplo de resposta com erro parcial:
{
  "orderId": 123,
  "customer": { "name": "Maria Silva", "email": "maria@email.com" },
  "items": [ { "productId": 1, "name": "Notebook", "quantity": 1 } ],
  "shipment": null,
  "_errors": [
    { "service": "logistics", "message": "Serviço temporariamente indisponível" }
  ]
}

3. Padrão Backend-for-Frontend (BFF) para Composition

3.1 BFF como camada dedicada

O BFF é um serviço específico para cada tipo de cliente (web, mobile, IoT), que realiza a composição de dados de forma otimizada para aquele contexto. Isso evita que o gateway genérico precise lidar com todas as variações de resposta.

3.2 Customização de respostas

// BFF para cliente mobile retorna apenas campos essenciais:
{
  "orderId": 123,
  "customerName": "Maria Silva",
  "total": 4500.00,
  "status": "entregue"
}

// BFF para cliente web retorna dados completos:
{
  "orderId": 123,
  "customer": { "name": "Maria Silva", "email": "maria@email.com", "phone": "(11) 99999-9999" },
  "items": [ { "productId": 1, "name": "Notebook", "specs": { "ram": "16GB", "storage": "512GB" } } ],
  "shipment": { "status": "entregue", "trackingCode": "BR123456789", "history": [...] }
}

3.3 Cache de composição

// Cache de agregados pré-calculados (TTL: 5 minutos)
// Chave: composite:order:123
// Valor: objeto JSON completo do pedido agregado
// Invalidação: quando serviço de pedidos notifica alteração via evento

4. Padrão GraphQL como Motor de Composition

4.1 Resolvedores encadeados com DataLoader

GraphQL permite que o cliente especifique exatamente quais dados precisa, e o servidor utiliza resolvedores para buscar dados de múltiplas fontes. O DataLoader evita o problema N+1 ao agrupar chamadas:

// Query GraphQL:
query {
  order(id: 123) {
    id
    customer { name email }
    items { product { name price } }
  }
}

// Resolvedor de customer usa DataLoader para agrupar chamadas:
// Em vez de N chamadas individuais, faz 1 chamada em lote

4.2 Batching e deduplicação

// DataLoader agrupa múltiplas requisições de customer:
// customersLoader.load(1), customersLoader.load(2), customersLoader.load(1)
// Resulta em: chamada única GET /customers?ids=1,2
// Deduplicação: customer 1 é carregado apenas uma vez

4.3 Limitações

Queries aninhadas profundamente podem gerar explosão de chamadas. É fundamental implementar limites de profundidade e análise de custo de queries.

5. Padrão de Composition com Event-Driven Architecture

5.1 Materialized views

Em vez de buscar dados sob demanda, serviços podem publicar eventos que alimentam agregados pré-construídos:

// Serviço de Pedidos publica evento: order.created
// Serviço de Clientes publica evento: customer.updated
// Serviço de Logística publica evento: shipment.status_changed

// Serviço de Composição escuta todos os eventos e mantém view materializada:
// Tabela: order_composite_view
// Atualizada via stream de eventos (Kafka, RabbitMQ)

5.2 CQRS e read models

Separação entre comandos (escrita) e consultas (leitura) permite otimizar o modelo de leitura para agregação:

// Read model otimizado para consulta de pedidos:
{
  "orderId": 123,
  "customerSummary": { "name": "Maria Silva", "tier": "gold" },
  "itemCount": 3,
  "totalValue": 7200.00,
  "deliveryEstimate": "2024-01-15"
}

5.3 Consistência eventual vs. consistência forte

Para dashboards e relatórios, consistência eventual é aceitável. Para transações financeiras, consistência forte é necessária.

6. Estratégias de Otimização e Resiliência

6.1 Técnicas de compressão e projeção

// Resposta comprimida com campos projetados:
// - Remover campos nulos
// - Abreviar nomes de campos (ex: "cust" em vez de "customer")
// - Paginação de listas grandes
// - Compressão gzip em trânsito

6.2 Políticas de retry

// Política de retry com backoff exponencial:
// Tentativa 1: espera 100ms
// Tentativa 2: espera 200ms
// Tentativa 3: espera 400ms
// Máximo: 3 tentativas, timeout de 2s por tentativa
// Idempotência: usar idempotency-key para evitar duplicação

6.3 Monitoramento

// Métricas essenciais:
// - Latência p50, p95, p99 por endpoint de composição
// - Taxa de sucesso parcial vs. completo
// - Número de chamadas downstream por requisição
// - Cache hit ratio

7. Padrão de Composition com Async/Await e Streams

7.1 Agregação reativa

// Fluxo reativo usando streams assíncronos:
// 1. Cliente solicita dados do pedido
// 2. Serviço inicia composição e emite dados parciais conforme disponíveis
// 3. Primeiro: dados do pedido (rápido)
// 4. Segundo: dados do cliente (médio)
// 5. Terceiro: dados de logística (lento)
// 6. Cliente recebe atualizações incrementais

7.2 Combinação de protocolos

// Composição híbrida:
// - REST para dados de cliente (GET /customers/456)
// - gRPC para dados de estoque (stream bidirecional)
// - Mensageria para notificações de status (fila RabbitMQ)

7.3 Tratamento de timeouts parciais

// Se serviço de logística não responder em 500ms:
// - Retornar dados parciais com status "pendente"
// - Agendar retry assíncrono
// - Notificar cliente via webhook quando dados estiverem completos

8. Boas Práticas e Anti-Patterns

8.1 Evitar composition em cascata

// Anti-pattern: composição aninhada profunda
// Serviço A chama Serviço B que chama Serviço C
// Ideal: Serviço A faz chamadas paralelas para B e C

// Limite máximo de profundidade: 2 níveis
// Preferir fan-out paralelo a encadeamento sequencial

8.2 Versionamento de contratos

// Versionamento semântico dos endpoints de composição:
// /api/v1/composite/orders/123
// /api/v2/composite/orders/123

// Evolução gradual: novos campos são opcionais
// Deprecação: manter versão antiga por 6 meses

8.3 Documentação

Cada endpoint de composição deve documentar quais serviços são chamados, quais campos são opcionais, e exemplos de payloads agregados.

Referências