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