Comunicação síncrona entre serviços: REST e gRPC

1. Fundamentos da comunicação síncrona em sistemas distribuídos

A comunicação síncrona entre serviços baseia-se no modelo requisição-resposta: um serviço cliente envia uma solicitação e aguarda bloqueantemente até receber a resposta do serviço servidor. Esse padrão cria um acoplamento temporal forte, pois ambos os serviços precisam estar disponíveis simultaneamente para que a troca ocorra.

Os trade-offs em relação à comunicação assíncrona são claros:

  • Latência: chamadas síncronas adicionam latência diretamente ao fluxo do cliente, enquanto mensageria assíncrona permite processamento em background.
  • Consistência: síncrono favorece consistência imediata (CP no teorema CAP), enquanto assíncrono tende à consistência eventual.
  • Resiliência: cadeias síncronas longas amplificam falhas (efeito cascata), exigindo padrões como circuit breakers.

Cenários típicos onde a comunicação síncrona é preferível incluem consultas de dados em tempo real, autenticação e autorização, e transações curtas que exigem confirmação imediata.

2. REST: Design e contratos baseados em recursos

REST (Representational State Transfer) é um estilo arquitetural que modela serviços como recursos identificados por URIs, manipulados através de verbos HTTP padronizados (GET, POST, PUT, DELETE, PATCH). O princípio de statelessness exige que cada requisição contenha toda informação necessária, sem depender de estado armazenado no servidor.

Exemplo de contrato REST para um serviço de pedidos:

GET /api/v1/orders/{orderId}
Accept: application/json

Resposta 200 OK:
{
  "id": "12345",
  "customerId": "67890",
  "items": [
    { "productId": "A1", "quantity": 2, "price": 29.90 }
  ],
  "status": "confirmed",
  "createdAt": "2025-01-15T10:30:00Z"
}

Versionamento de APIs: estratégias comuns incluem versionamento por URI (/api/v1/orders), por header customizado (Accept: application/vnd.myapi.v1+json) ou via hypermedia (HATEOAS). O versionamento por URI é o mais simples e adotado na prática.

Tratamento de erros: códigos de status HTTP devem ser usados semanticamente:
- 2xx: sucesso (200 OK, 201 Created, 204 No Content)
- 4xx: erro do cliente (400 Bad Request, 404 Not Found, 422 Unprocessable Entity)
- 5xx: erro do servidor (500 Internal Server Error, 503 Service Unavailable)

Exemplo de resposta de erro padronizada:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "error": {
    "code": "INVALID_ORDER",
    "message": "O pedido deve conter ao menos um item",
    "details": [
      { "field": "items", "reason": "array vazio" }
    ]
  }
}

3. gRPC: Contratos fortes e eficiência binária

gRPC é um framework de RPC (Remote Procedure Call) desenvolvido pelo Google, que utiliza Protocol Buffers (protobuf) para definição de contratos e serialização binária, e HTTP/2 como transporte subjacente.

Definição de serviço em arquivo .proto:

syntax = "proto3";

package orders;

service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc ListOrders (ListOrdersRequest) returns (ListOrdersResponse);
  rpc CreateOrder (stream OrderItem) returns (OrderConfirmation);
}

message GetOrderRequest {
  string order_id = 1;
}

message Order {
  string id = 1;
  string customer_id = 2;
  repeated OrderItem items = 3;
  string status = 4;
  string created_at = 5;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  double price = 3;
}

gRPC suporta quatro tipos de streaming:
- Unário: requisição única, resposta única (RPC tradicional)
- Server-streaming: cliente envia uma requisição, servidor retorna um stream de respostas
- Client-streaming: cliente envia um stream de requisições, servidor retorna uma resposta única
- Bidirecional: ambos os lados enviam streams independentes

A serialização binária com protobuf é significativamente mais rápida e compacta que JSON, e o HTTP/2 permite multiplexação de múltiplas chamadas sobre uma única conexão TCP, reduzindo latência.

4. Comparação arquitetural: REST vs gRPC

Característica REST gRPC
Formato de dados JSON (textual, legível) Protobuf (binário, compacto)
Transporte HTTP/1.1 ou HTTP/2 HTTP/2 (obrigatório)
Contrato Implícito (documentação) Explícito (arquivo .proto)
Performance Moderada Alta (3-10x mais rápido)
Tamanho payload Maior Menor (até 60% menor)
Streaming Limitado (SSE, WebSocket) Nativo (4 tipos)
Ferramentas de debug curl, Postman, navegador grpcurl, BloomRPC

Desempenho: gRPC supera REST em latência e throughput devido à serialização binária e multiplexação HTTP/2. Em benchmarks, gRPC pode processar 5-10x mais requisições por segundo que REST com JSON.

Clareza de contrato: REST depende de documentação externa (OpenAPI/Swagger) para definir contratos. gRPC gera automaticamente stubs de cliente e servidor a partir do arquivo .proto, garantindo type safety em tempo de compilação.

Ecossistema: REST possui ferramentas de debugging maduras (Postman, Insomnia) e é suportado nativamente por navegadores. gRPC requer ferramentas especializadas como grpcurl ou interfaces web como grpc-web.

5. Padrões de resiliência em chamadas síncronas

Chamadas síncronas entre serviços introduzem riscos de falhas em cascata. Padrões essenciais:

Timeouts: definir limites máximos de espera para cada chamada, evitando que um serviço lento bloqueie recursos indefinidamente.

// Exemplo conceitual de timeout em REST
GET /api/v1/orders/12345
Timeout: 5s

Retries com backoff: repetir chamadas falhas com intervalos crescentes (exponential backoff) para evitar sobrecarga.

Tentativa 1: aguardar 100ms
Tentativa 2: aguardar 200ms
Tentativa 3: aguardar 400ms
Máximo de 3 tentativas

Circuit Breaker: interromper chamadas quando a taxa de falhas excede um limiar, permitindo recuperação do serviço downstream.

Bulkhead: isolar recursos por serviço ou tipo de chamada, evitando que uma falha consuma todo o pool de conexões.

Idempotência: endpoints devem produzir o mesmo resultado quando chamados múltiplas vezes com o mesmo payload. Em REST, usa-se o verbo PUT ou headers de idempotência:

POST /api/v1/orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

Em gRPC, o cliente pode gerar e enviar um identificador único no metadado da requisição.

6. Considerações de governança e evolução

Versionamento e compatibilidade: em REST, adicionar campos ao JSON é backward-compatible, mas remover campos ou alterar tipos é breaking change. gRPC com protobuf oferece regras claras de evolução:

  • Backward compatibility: novos campos podem ser adicionados com números de campo únicos
  • Forward compatibility: clientes antigos ignoram campos desconhecidos
  • Breaking changes: renomear campos, alterar tipos, reutilizar números de campo

Gerenciamento de breaking changes em gRPC: utilize números de campo reservados (reserved 2, 3;) e evite alterar tipos de mensagens já publicadas. Para mudanças maiores, crie novas versões de serviço.

Monitoramento: tracing distribuído (OpenTelemetry, Jaeger) é crucial para rastrear chamadas síncronas entre serviços. Métricas de SLA incluem latência (p50, p95, p99), taxa de erro e throughput.

7. Quando escolher REST, gRPC ou ambos em uma arquitetura

Critérios de decisão:

Cenário Recomendação
APIs públicas expostas a terceiros REST (ubiquidade, facilidade de consumo)
Comunicação interna entre microsserviços gRPC (performance, contratos fortes)
Clientes mobile ou browser REST ou gRPC-Web (limitações de HTTP/2)
Streaming de dados em tempo real gRPC (streaming nativo)
Equipe com baixa maturidade técnica REST (curva de aprendizado menor)

Estratégias de coexistência: um API Gateway pode expor REST para clientes externos e traduzir internamente para gRPC:

Cliente externo (REST)
       ↓
API Gateway (tradução REST ↔ gRPC)
       ↓
Serviços internos (gRPC)

Essa abordagem combina a acessibilidade do REST com a eficiência do gRPC, permitindo que cada protocolo seja usado onde melhor se aplica.

Referências