Arquitetura orientada a eventos: desacoplando microsserviços
1. Fundamentos da Arquitetura Orientada a Eventos
1.1. Conceitos básicos: eventos, produtores, consumidores e barramentos
Na arquitetura orientada a eventos (EDA), a comunicação entre microsserviços ocorre por meio de eventos — registros imutáveis que descrevem algo que aconteceu no sistema. Um produtor publica eventos em um barramento (message broker), sem conhecer quem irá consumi-los. Os consumidores se inscrevem para receber eventos relevantes e reagem de forma assíncrona.
Exemplo de evento em JSON:
{
"eventId": "e7b8c9d0-1234-5678-abcd-ef0123456789",
"eventType": "PedidoCriado",
"timestamp": "2025-04-08T10:30:00Z",
"data": {
"pedidoId": "ORD-98765",
"clienteId": "CLI-54321",
"valorTotal": 250.00
}
}
1.2. Diferenças entre comunicação síncrona e assíncrona
Na comunicação síncrona via REST/gRPC, o serviço chamador bloqueia até receber uma resposta. Isso cria acoplamento temporal e fragilidade: se o serviço destino falha, a cadeia inteira é afetada. Já na comunicação assíncrona por eventos, o produtor publica o evento e continua seu fluxo imediatamente. O consumidor processa quando estiver disponível.
// Comunicação síncrona (REST) - acoplada
Serviço A -> POST /api/pedidos -> Serviço B (bloqueia até resposta)
// Comunicação assíncrona (eventos) - desacoplada
Serviço A -> publica evento "PedidoCriado" -> Barramento
Serviço B -> consome evento "PedidoCriado" -> processa quando puder
1.3. Benefícios do desacoplamento
O desacoplamento proporciona:
- Escalabilidade independente: cada microsserviço escala conforme sua demanda
- Resiliência: falhas em consumidores não afetam produtores
- Evolução independente: times podem modificar serviços sem coordenação rígida
2. Padrões de Troca de Eventos
2.1. Evento de notificação vs. Evento de estado
Eventos de notificação carregam apenas um identificador, exigindo que o consumidor busque dados adicionais. Eventos de estado (event-carried state transfer) incluem todos os dados necessários para processamento, reduzindo chamadas subsequentes.
// Evento de notificação (apenas ID)
{ "eventType": "PedidoCriado", "pedidoId": "ORD-98765" }
// Evento de estado (dados completos)
{ "eventType": "PedidoCriado", "pedidoId": "ORD-98765", "cliente": {...}, "itens": [...] }
2.2. Coreografia vs. Orquestração
Na coreografia, cada serviço reage a eventos e publica novos eventos, formando uma dança descentralizada. Na orquestração, um serviço central coordena o fluxo. A coreografia é mais desacoplada, mas exige maior cuidado com consistência.
2.3. Tipos de eventos
- Eventos de domínio: representam fatos do negócio (ex: PedidoEntregue)
- Eventos de integração: usados para comunicação entre sistemas (ex: DadosClienteAtualizados)
- Eventos de auditoria: registram mudanças para compliance e debugging
3. Infraestrutura de Mensageria e Barramentos
3.1. Message Brokers vs. Event Stores
Message Brokers (Kafka, RabbitMQ, NATS) distribuem eventos entre consumidores. Event Stores (EventStoreDB, Axon) persistem eventos como fonte única da verdade, permitindo Event Sourcing.
3.2. Tópicos, partições e garantias de entrega
Kafka organiza eventos em tópicos divididos em partições para paralelismo. As garantias de entrega variam:
- At-least-once: evento pode ser entregue mais de uma vez (exige idempotência)
- Exactly-once: entrega única, mas com maior custo de desempenho
3.3. Estratégias de roteamento
- Publish-subscribe: um evento é enviado a todos os assinantes
- Filas de trabalho: um evento é consumido por apenas um worker
- Stream processing: eventos são processados em ordem, com estado mantido
4. Desenho de Contratos e Esquemas de Eventos
4.1. Versionamento de eventos
Eventos evoluem ao longo do tempo. Estratégias de versionamento:
- Backward-compatible: novos consumidores entendem eventos antigos
- Forward-compatible: consumidores antigos ignoram campos novos
4.2. Schema Registry
Ferramentas como Confluent Schema Registry validam contratos usando Avro, Protobuf ou JSON Schema, garantindo que produtores e consumidores estejam alinhados.
// Schema Avro para evento PedidoCriado
{
"type": "record",
"name": "PedidoCriado",
"fields": [
{"name": "pedidoId", "type": "string"},
{"name": "clienteId", "type": "string"},
{"name": "valorTotal", "type": "double"}
]
}
4.3. Eventos imutáveis e Event Sourcing
Eventos são registros imutáveis — uma vez publicados, não podem ser alterados. No padrão Event Sourcing, o estado atual de uma entidade é derivado da reprodução de todos os eventos passados.
5. Tratamento de Falhas e Consistência Eventual
5.1. Retentativas com backoff exponencial
Quando um consumidor falha ao processar um evento, a retentativa deve usar backoff exponencial para evitar sobrecarga:
Tentativa 1: espera 1 segundo
Tentativa 2: espera 2 segundos
Tentativa 3: espera 4 segundos
Tentativa 4: espera 8 segundos
... (até limite configurado)
5.2. Dead Letter Queue (DLQ)
Eventos que excedem o limite de retentativas são movidos para uma DLQ para análise manual ou reprocessamento futuro.
5.3. Sagas coreografadas
Para transações distribuídas, sagas coreografadas usam eventos compensatórios. Exemplo em e-commerce:
1. Serviço Pedidos publica "PedidoCriado"
2. Serviço Pagamentos consome, processa e publica "PagamentoAprovado"
3. Serviço Estoque consome, reserva e publica "EstoqueReservado"
4. Se falha no estoque, publica "EstoqueFalhou"
5. Serviço Pagamentos reage com "PagamentoEstornado"
6. Implementação Prática e Código Exemplo
6.1. Produtor de eventos (Python com Kafka)
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers=['localhost:9092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
evento = {
"eventId": "e7b8c9d0-1234-5678-abcd-ef0123456789",
"eventType": "PedidoCriado",
"timestamp": "2025-04-08T10:30:00Z",
"data": {
"pedidoId": "ORD-98765",
"clienteId": "CLI-54321",
"valorTotal": 250.00
}
}
producer.send('pedidos', value=evento)
producer.flush()
6.2. Consumidor com idempotência
from kafka import KafkaConsumer
import json
consumer = KafkaConsumer(
'pedidos',
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
enable_auto_commit=False,
group_id='grupo-pagamentos'
)
eventos_processados = set()
for mensagem in consumer:
evento = mensagem.value
# Idempotência: verificar se já processou
if evento['eventId'] in eventos_processados:
consumer.commit()
continue
# Processar evento
processar_pagamento(evento['data'])
# Registrar como processado
eventos_processados.add(evento['eventId'])
consumer.commit()
6.3. Coreografia de e-commerce
Fluxo completo de pedido com coreografia:
1. [Serviço Pedidos] publica "PedidoCriado"
2. [Serviço Pagamentos]
- Consome "PedidoCriado"
- Processa pagamento
- Publica "PagamentoAprovado" ou "PagamentoRecusado"
3. [Serviço Estoque]
- Consome "PagamentoAprovado"
- Reserva itens
- Publica "EstoqueReservado" ou "EstoqueInsuficiente"
4. [Serviço Pedidos]
- Consome "EstoqueReservado" -> atualiza status para "Confirmado"
- Consome "PagamentoRecusado" -> publica "PedidoCancelado"
5. [Serviço Pagamentos]
- Consome "PedidoCancelado" -> publica "PagamentoEstornado"
7. Monitoramento, Rastreamento e Observabilidade
7.1. Correlation IDs
Cada fluxo de eventos deve carregar um correlationId único para rastrear a jornada completa:
{
"eventId": "...",
"correlationId": "abc-123-def",
"eventType": "PedidoCriado",
"data": {...}
}
7.2. Métricas essenciais
- Latência: tempo entre publicação e consumo
- Throughput: eventos processados por segundo
- Tamanho da fila: eventos pendentes por tópico
- Taxa de erro: eventos que foram para DLQ
7.3. Logging estruturado
Incluir eventId, correlationId e timestamp em todos os logs para debugging de pipelines assíncronos.
8. Anti-patterns e Boas Práticas
8.1. Evitar acoplamento temporal
Produtores não devem esperar que consumidores estejam disponíveis. Use filas persistentes e configure tempos de retenção adequados.
8.2. Cuidados com explosão de eventos
Evite publicar eventos para cada pequena mudança. Agrupe mudanças relacionadas em um único evento de domínio.
8.3. Testabilidade de fluxos assíncronos
Use testes de contrato para validar que produtores e consumidores concordam com o schema. Simule eventos em ambientes de teste para verificar coreografias completas.
Exemplo de teste de contrato:
- Produtor publica evento "PedidoCriado" com schema v1
- Consumidor valida que entende schema v1
- Teste falha se contrato for quebrado
Referências
- Confluent Documentation - Event-Driven Architecture — Guia oficial sobre conceitos de EDA e implementação com Apache Kafka
- Martin Fowler - Event Sourcing — Artigo clássico sobre padrão Event Sourcing e imutabilidade de eventos
- AWS - What is Event-Driven Architecture? — Visão geral da AWS sobre benefícios e padrões de EDA
- RabbitMQ Documentation - Work Queues — Tutorial prático sobre filas de trabalho e distribuição de eventos
- Microsoft - Saga Pattern for Distributed Transactions — Guia da Microsoft sobre implementação de sagas coreografadas
- Red Hat - Event-Driven Architecture Patterns — Catálogo de padrões EDA com exemplos de coreografia e orquestração