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
- Microservices.io - Patterns for Communication — Catálogo completo de padrões de comunicação entre microservices, incluindo REST, mensageria e eventos.
- Google Cloud - gRPC Documentation — Documentação oficial do gRPC com guias de implementação para contratos Protocol Buffers e streaming.
- RabbitMQ - Tutorials — Tutoriais práticos sobre filas, exchanges e padrões de mensageria com RabbitMQ.
- Pact.io - Contract Testing — Documentação oficial do Pact para testes de contrato entre consumidores e provedores de API.
- OpenTelemetry - Distributed Tracing — Guia completo sobre tracing distribuído para observabilidade em sistemas de microservices.
- Istio - Service Mesh — Introdução ao service mesh Istio e como ele gerencia comunicação, segurança e observabilidade entre serviços.
- Martin Fowler - Event Sourcing — Artigo fundamental sobre Event Sourcing e CQRS como base para comunicação eventualmente consistente.