Padrões de comunicação entre microservices

1. Fundamentos da comunicação em microservices

A comunicação entre microservices é o coração de qualquer arquitetura distribuída. O princípio fundamental é o acoplamento flexível — serviços devem trocar informações sem depender de detalhes internos uns dos outros. A comunicação assíncrona é preferida sempre que possível, pois permite que serviços operem de forma independente, mesmo quando outros estão temporariamente indisponíveis.

Existem duas grandes famílias de comunicação:

Comunicação síncrona (REST, gRPC): o serviço chamador aguarda a resposta imediatamente. É simples de implementar, mas introduz acoplamento temporal e pode criar cascatas de falhas.

Comunicação assíncrona (mensageria): o emissor envia uma mensagem e continua seu fluxo sem esperar resposta. Isso aumenta a resiliência, mas adiciona complexidade de consistência eventual.

Os trade-offs são claros:
- Latência: síncrona tem latência previsível; assíncrona pode ter atrasos variáveis.
- Consistência: síncrona oferece consistência imediata; assíncrona exige lidar com dados eventualmente consistentes.
- Disponibilidade: assíncrona tolera melhor falhas de serviços downstream.

// Exemplo: Chamada síncrona REST vs mensagem assíncrona

// Síncrona - serviço de pedidos chama serviço de pagamento
POST /api/pagamentos
{
  "pedido_id": "12345",
  "valor": 150.00,
  "metodo": "cartao_credito"
}
Resposta: 200 OK { "status": "aprovado", "transacao_id": "abc-123" }

// Assíncrona - serviço de pedidos publica evento
Evento: "PedidoCriado"
Payload: { "pedido_id": "12345", "valor": 150.00 }
Serviço de pagamento consome o evento e processa assincronamente

2. Comunicação síncrona: REST e gRPC

RESTful APIs continuam sendo o padrão mais comum. Para microservices, é crucial adotar:
- HATEOAS: incluir links nos responses que guiam o cliente sobre as próximas ações possíveis.
- Versionamento: usar header Accept-Version ou prefixo na URL (/v1/pedidos).

gRPC emerge como alternativa de alto desempenho, usando Protocol Buffers para contratos fortemente tipados e suporte nativo a streaming bidirecional. É ideal para comunicação interna entre serviços que exigem baixa latência.

Boas práticas para comunicação síncrona:
- Timeouts: sempre configurar timeouts agressivos (ex: 5 segundos para REST, 2 segundos para gRPC).
- Circuit breakers: interromper chamadas quando um serviço está falhando repetidamente.
- Retry com backoff: tentar novamente com intervalos exponenciais.

// Contrato gRPC (Protocol Buffers)
syntax = "proto3";

service PagamentoService {
  rpc ProcessarPagamento (PagamentoRequest) returns (PagamentoResponse);
  rpc AcompanharStatus (StatusRequest) returns (stream StatusUpdate);
}

message PagamentoRequest {
  string pedido_id = 1;
  double valor = 2;
  string metodo = 3;
}

message PagamentoResponse {
  string transacao_id = 1;
  string status = 2;
}

3. Comunicação assíncrona com mensageria

Mensageria é a espinha dorsal de sistemas resilientes. Os padrões principais:

  • Filas: cada mensagem é consumida por um único worker (ex: processamento de pagamentos).
  • Tópicos publish-subscribe: uma mensagem é entregue a todos os assinantes (ex: evento "PedidoCriado" notifica estoque, faturamento, logística).

Garantia de entrega:
- At-least-once: mensagem pode ser entregue mais de uma vez — exige idempotência no consumidor.
- Exactly-once: ideal mas difícil de implementar; Kafka oferece com configurações específicas.

Dead-letter queues (DLQ): mensagens que falharam após repetidas tentativas são movidas para uma fila separada para análise manual.

// Publicação de evento no RabbitMQ
// Produtor (serviço de pedidos)
channel.basicPublish(
  exchange: "pedidos.exchange",
  routingKey: "pedido.criado",
  body: JSON.stringify({
    pedido_id: "12345",
    itens: ["SKU-001", "SKU-002"],
    total: 250.00
  })
);

// Consumidor (serviço de estoque)
channel.consume("estoque.queue", (msg) => {
  const evento = JSON.parse(msg.content.toString());
  // Atualizar estoque com idempotência
  if (!jaProcessado(evento.pedido_id)) {
    reservarEstoque(evento.itens);
    marcarComoProcessado(evento.pedido_id);
  }
  channel.ack(msg);
});

4. Padrão de coreografia e orquestração

Coreografia: cada serviço reage a eventos e publica novos eventos sem um coordenador central. É descentralizada e permite evolução autônoma, mas o fluxo completo fica implícito e difícil de rastrear.

Orquestração: um serviço central (orchestrator) coordena o fluxo, chamando cada serviço e gerenciando compensações em caso de falha. O padrão Saga é comum para transações longas.

// Saga orquestrada - fluxo de pedido
Orchestrator -> ServiçoPedidos: "CriarPedido"
Orchestrator -> ServiçoPagamento: "ProcessarPagamento"
Orchestrator -> ServiçoEstoque: "ReservarEstoque"

// Se pagamento falha:
Orchestrator -> ServiçoPedidos: "CancelarPedido" (compensação)

// Coreografia - mesmo fluxo baseado em eventos
ServiçoPedidos -> Evento: "PedidoCriado"
ServiçoPagamento <- Evento: "PedidoCriado"
ServiçoPagamento -> Evento: "PagamentoAprovado"
ServiçoEstoque <- Evento: "PagamentoAprovado"
ServiçoEstoque -> Evento: "EstoqueReservado"

Quando usar cada abordagem:
- Coreografia: fluxos simples, times autônomos, baixa necessidade de rastreabilidade central.
- Orquestração: fluxos complexos com muitas compensações, necessidade de visibilidade central, transações financeiras.

5. Padrão de contratos e evolução de APIs

Design-first: definir o contrato (OpenAPI para REST, proto para gRPC) antes de implementar. Isso força o design explícito e gera documentação automaticamente.

Code-first: gerar contrato a partir do código. Mais rápido no início, mas pode levar a APIs inconsistentes.

Versionamento semântico: MAJOR.MINOR.PATCH — mudanças incompatíveis incrementam MAJOR, adições compatíveis incrementam MINOR.

Testes de contrato com Pact: o provedor e o consumidor definem expectativas em testes que rodam independentemente, verificando se o contrato é respeitado.

// Teste de contrato Pact (consumidor)
@Test
void testConsumidorPedidos() {
  pact
    .given("existe um pedido com ID 12345")
    .uponReceiving("uma requisição de detalhes do pedido")
    .path("/api/pedidos/12345")
    .method("GET")
    .willRespondWith()
    .status(200)
    .body(new PactDslJsonBody()
      .stringType("id", "12345")
      .decimalType("total", 150.00)
      .array("itens")
        .object()
          .stringType("sku", "SKU-001")
          .integerType("quantidade", 2)
        .closeObject()
      .closeArray()
    );
}

6. Segurança e governança na comunicação

Autenticação entre serviços:
- mTLS: certificados mútuos garantem que ambos os lados são quem dizem ser.
- JWT: tokens assinados carregam claims de identidade e permissões.
- OAuth2 com client credentials: fluxo específico para comunicação machine-to-machine.

Rate limiting: proteger serviços contra abuso, seja de clientes externos ou de outros serviços com bugs.

Observabilidade:
- Tracing distribuído (OpenTelemetry): rastrear uma requisição através de múltiplos serviços.
- Logging centralizado: agregar logs de todos os serviços em uma plataforma única (ELK, Loki).

// Configuração de tracing com OpenTelemetry
// Header propagado entre serviços
traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"

// Cada serviço extrai e propaga esse header
// Jaeger ou Zipkin montam o grafo completo da requisição

7. Padrões avançados e anti-padrões

Event Sourcing + CQRS: armazenar eventos como fonte da verdade (event store) e ter modelos de leitura separados. A comunicação entre serviços se torna baseada em eventos de domínio.

Anti-padrões a evitar:
- Acoplamento temporal: serviços que precisam estar online simultaneamente para funcionar.
- Polling excessivo: consumidor perguntando repetidamente por atualizações — prefira webhooks ou mensageria.
- Shared database: serviços compartilhando o mesmo banco de dados — viola o princípio de bounded context.

Tendências futuras: service mesh (Istio, Linkerd) abstrai a comunicação para uma camada de infraestrutura, permitindo políticas de tráfego, segurança e observabilidade sem modificar o código dos serviços.

// Configuração de service mesh Istio
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: pagamento-service
spec:
  hosts:
  - pagamento-service
  http:
  - timeout: 3s
    retries:
      attempts: 3
      perTryTimeout: 1s
    fault:
      delay:
        percentage: 10
        fixedDelay: 5s

Referências