Pular para conteúdo

Event-Driven Architecture (EDA)

Visão Geral

A Event-Driven Architecture (EDA) ou Arquitetura Orientada a Eventos é um padrão arquitetural no qual a comunicação entre diferentes partes do sistema ocorre através da produção, detecção e reação a eventos. No contexto do Santa Mão, utilizamos esta arquitetura para desacoplar componentes do sistema e permitir processamento assíncrono de operações.

Conceitos Fundamentais

O que é um Evento?

Um evento é uma notificação de que algo aconteceu no sistema. É sempre representado no tempo passado, indicando uma mudança de estado já ocorrida.

Características de um Evento:

  • Imutável: Uma vez criado, não pode ser modificado
  • Temporal: Representa algo que já aconteceu
  • Informativo: Carrega dados sobre a mudança de estado
  • Desacoplado: O produtor não sabe quem consumirá o evento

Exemplos de Eventos no Santa Mão:

  • ProfessionalRegisteredEvent
  • OrderCreatedEvent
  • PaymentProcessedEvent
  • RequestAcceptedEvent
  • UserProfileUpdatedEvent

Componentes da Arquitetura

Componentes da Arquitetura EDA

Infraestrutura no Santa Mão

Redpanda como Event Broker

O Santa Mão utiliza o Redpanda como plataforma centralizada para gerenciamento de eventos.

URL do Ambiente de Desenvolvimento:

dev.redpanda.santamao.com

O que é o Redpanda?

O Redpanda é uma plataforma de streaming de dados compatível com a API do Kafka, mas otimizada para:

  • Performance: Menor latência e maior throughput
  • Simplicidade: Configuração e operação mais simples que o Kafka tradicional
  • Recursos: Menor consumo de memória e CPU
  • Compatibilidade: 100% compatível com API do Kafka

Vantagens do Redpanda

  • Alta Performance: Escrito em C++ para máxima eficiência
  • Baixa Latência: Processamento de eventos em tempo real
  • Escalabilidade: Suporta milhões de eventos por segundo
  • Durabilidade: Garantia de entrega de mensagens
  • Multi-tenancy: Isolamento entre diferentes domínios

Topologia de Tópicos

Os eventos são organizados em tópicos do Kafka, geralmente seguindo a estrutura:

santamao.{dominio}.{evento}

Exemplos de Tópicos:

  • santamao.professionals.registered
  • santamao.orders.created
  • santamao.orders.completed
  • santamao.payments.processed
  • santamao.notifications.sent

Implementação no Santa Mão

MassTransit como Abstração

O Santa Mão utiliza o MassTransit como camada de abstração sobre o Kafka/Redpanda. O MassTransit é um framework de mensageria distribuída para .NET que simplifica a publicação e consumo de eventos.

Vantagens do MassTransit:

  • ✅ Abstração sobre diferentes brokers (Kafka, RabbitMQ, Azure Service Bus)
  • ✅ Configuração simplificada e conventions automáticas
  • ✅ Suporte nativo a retry, circuit breaker e compensação
  • ✅ Integração com Dependency Injection do .NET
  • ✅ Serialização automática de mensagens
  • ✅ Middlewares para logging, validação e tratamento de erros

1. Publicação de Eventos (API .NET)

Fluxo Prático de Publicação

No Santa Mão, quando uma ação importante acontece, publicamos um evento que será consumido por outros componentes:

Cenário Real: Cadastro de um Profissional

1. Cliente faz requisição POST /api/professionals
2. API valida os dados
3. Command Handler cria o profissional no banco de dados
4. Domain Event é publicado via MassTransit
5. API retorna sucesso imediatamente ao cliente
6. Workers processam o evento em background

Eventos que a API publica:

  • ProfessionalRegisteredEvent → Quando um novo profissional se cadastra
  • OrderCreatedEvent → Quando um pedido é criado
  • PaymentProcessedEvent → Quando um pagamento é confirmado
  • RequestAcceptedEvent → Quando um profissional aceita uma solicitação
  • UserProfileUpdatedEvent → Quando dados do usuário são alterados

Como funciona na prática:

O MassTransit é injetado nos handlers e publica automaticamente o evento para o Redpanda:

// MassTransit cuida de:
// - Serializar o evento para JSON
// - Enviar para o tópico correto no Redpanda
// - Adicionar metadados (timestamp, correlation-id, etc)
// - Garantir a entrega (retry automático em caso de falha)

2. Consumo de Eventos (Workers .NET)

Fluxo Prático de Consumo

Os Workers são serviços .NET separados da API que ficam "ouvindo" eventos no Redpanda e executam ações específicas.

Cenário Real: Envio de Notificações

1. ProfessionalRegisteredEvent é publicado no Redpanda
2. Worker está inscrito nesse evento
3. MassTransit entrega o evento para o Consumer
4. Worker envia email de boas-vindas
5. Worker confirma processamento (commit)
6. MassTransit marca o evento como processado

Como os Workers funcionam

1. Worker fica aguardando eventos:

Worker iniciado → Conecta no Redpanda → Inscreve nos tópicos → Aguarda mensagens

2. Evento chega:

MassTransit recebe → Deserializa JSON → Chama o Consumer → Executa lógica de negócio

3. Processamento bem-sucedido:

Ação executada → Worker confirma → MassTransit faz commit → Evento removido da fila

4. Processamento com erro:

Exceção lançada → MassTransit não confirma → Retry automático → Se falhar 3x → Dead Letter Queue

Exemplo Prático: Fluxo Completo

Situação: Cliente cria um pedido no app

Fluxo Completo de um Pedido

Todos processam o MESMO evento, mas executam ações DIFERENTES!

3. Configuração do MassTransit

No Santa Mão, a configuração do MassTransit é centralizada e reutilizada em API e Workers:

Conexão com Redpanda:

dev.redpanda.santamao.com:9092

Padrão de nomes de tópicos:

santamao.{contexto}.{evento}

Consumer Groups: - Cada worker tem seu próprio grupo - Permite múltiplas instâncias do mesmo worker processarem eventos em paralelo - Exemplo: santamao-notifications-worker-group

Padrões e Práticas no Santa Mão

Saga Pattern (Processos Distribuídos)

Quando uma operação envolve múltiplos contextos, usamos o padrão Saga para coordenar.

Exemplo Prático: Conclusão de um Pedido

Fluxo Normal (Happy Path):
1. OrderCreatedEvent → Worker valida disponibilidade do profissional
2. ProfessionalAvailableEvent → Worker processa pagamento
3. PaymentProcessedEvent → Worker notifica profissional e cliente
4. NotificationsSentEvent → Worker marca pedido como confirmado

Fluxo com Erro (Compensação):
1. OrderCreatedEvent → Worker valida disponibilidade
2. ProfessionalUnavailableEvent → Worker cancela pedido
3. OrderCancelledEvent → Worker estorna pagamento (se houver)
4. PaymentRefundedEvent → Worker notifica cliente sobre cancelamento

No Santa Mão, cada Worker:

  • É responsável por UMA parte do processo
  • Publica eventos sobre o resultado (sucesso ou falha)
  • Outros workers reagem aos eventos publicados
  • Compensação automática em caso de erro

Retry e Dead Letter Queue

MassTransit gerencia automaticamente:

Política de Retry no Santa Mão:

1ª tentativa → Falhou? Aguarda 5 segundos
2ª tentativa → Falhou? Aguarda 30 segundos
3ª tentativa → Falhou? Aguarda 2 minutos
Falhou 3x → Envia para Dead Letter Queue (DLQ)

Dead Letter Queue:

  • Eventos que falharam repetidamente vão para santamao.dlq
  • Equipe de desenvolvimento é alertada
  • Permite investigação e reprocessamento manual
  • Evita perda de eventos importantes

Benefícios da Arquitetura EDA

1. Desacoplamento

Os componentes não precisam conhecer uns aos outros:

Desacoplamento

2. Escalabilidade

  • Múltiplos consumers podem processar eventos em paralelo
  • Cada worker pode escalar independentemente
  • Partições do Kafka permitem processamento distribuído

3. Resiliência

  • Se um worker falhar, outros continuam processando
  • Eventos não processados permanecem na fila
  • Retry automático em caso de falhas

4. Rastreabilidade

  • Todos os eventos ficam armazenados no Redpanda
  • Histórico completo de mudanças de estado
  • Auditoria e debugging facilitados

5. Processamento Assíncrono

  • API responde rapidamente ao usuário
  • Processamento pesado ocorre em background
  • Melhor experiência do usuário

Casos de Uso Reais no Santa Mão

1. Notificações Multi-Canal

Trigger: ProfessionalRegisteredEvent, OrderCreatedEvent, RequestAcceptedEvent

Fluxo:

Notificações Multi-Canal

Benefício: Cliente recebe confirmação imediata (API responde rápido), mas notificações são enviadas em background sem bloquear a requisição.

2. Integração com Gateway de Pagamento

Trigger: OrderCreatedEvent

Fluxo:

1. Cliente cria pedido → API retorna sucesso
2. PaymentWorker consome evento
3. Worker chama API do gateway (Stripe/PagSeguro)
4. Publica PaymentProcessedEvent ou PaymentFailedEvent
5. NotificationWorker notifica resultado ao cliente

Benefício: Integração com serviço externo não trava a API principal. Se o gateway estiver lento, não afeta a experiência do cliente.

3. Processamento de Imagens de Perfil

Trigger: ProfilePictureUploadedEvent

Fluxo:

1. Cliente faz upload da foto → API salva temporariamente
2. ImageWorker consome evento
3. Worker redimensiona (thumbnail, medium, large)
4. Worker otimiza e comprime
5. Worker envia para CDN (Cloudflare/AWS S3)
6. Publica ProfilePictureProcessedEvent
7. API atualiza URL da imagem no perfil

Benefício: Upload da imagem é rápido, processamento pesado ocorre em background.

4. Análise e Business Intelligence

Trigger: Qualquer evento de atividade do usuário

Fluxo:

Análise e Business Intelligence

Benefício: Dados analíticos são processados sem impactar performance da aplicação principal.

5. Sincronização de Cache Distribuído

Trigger: ProfessionalProfileUpdatedEvent, SkillsChangedEvent

Fluxo:

1. Profissional atualiza perfil → API salva no banco
2. CacheWorker consome evento
3. Worker invalida cache Redis das chaves afetadas
4. Próxima leitura busca dados atualizados do banco

Benefício: Cache sempre consistente sem precisar de invalidação manual.

6. Matchmaking de Profissionais

Trigger: RequestCreatedEvent

Fluxo:

1. Cliente cria solicitação de serviço
2. MatchmakingWorker consome evento
3. Worker busca profissionais compatíveis:
   - Por localização (geo-spatial)
   - Por habilidades necessárias
   - Por disponibilidade
   - Por rating
4. Worker publica MatchFoundEvent para cada profissional
5. NotificationWorker notifica profissionais sobre nova oportunidade

Benefício: Algoritmo complexo de matchmaking roda em background, API responde instantaneamente ao cliente.

Desafios e Soluções Práticas

1. Consistência Eventual

Desafio:

Quando um evento é publicado, não há garantia de que todos os workers já processaram.

Exemplo Prático:

1. Cliente cria pedido (10:00:00)
2. API retorna sucesso imediatamente
3. NotificationWorker processa em 500ms (10:00:00.5)
4. Cliente pode não ter recebido email ainda se consultar muito rápido

Como lidamos no Santa Mão:

  • ✅ UI mostra mensagens como "Você receberá um email em breve"
  • ✅ Status do pedido tem estados intermediários: CreatedProcessingConfirmed
  • ✅ Webhooks notificam o frontend quando processamento finaliza
  • ✅ Polling ou SignalR atualiza UI em tempo real

2. Ordenação de Eventos

Desafio:

Eventos podem chegar fora de ordem se estiverem em partições diferentes.

Exemplo Problemático:

ProfileUpdatedEvent (partição 1) - 10:00:01
ProfilePictureUploadedEvent (partição 2) - 10:00:00

Se processados fora de ordem, foto pode ser associada ao perfil antigo!

Solução no Santa Mão:

  • Usar mesma chave de partição para eventos relacionados
  • Todos eventos de um ProfessionalId vão para mesma partição
  • MassTransit garante ordem dentro da partição
  • Incluir versão/timestamp nos eventos
  • Worker pode detectar e descartar eventos atrasados
  • Idempotência (ver próximo tópico)

3. Idempotência

Desafio:

O mesmo evento pode ser processado múltiplas vezes (retry, rebalanceamento de partições, etc).

Exemplo Problemático:

OrderCreatedEvent processado 2x → Cliente recebe 2 emails!
PaymentProcessedEvent processado 2x → Cobrança duplicada!

Como garantimos idempotência no Santa Mão:

Estratégia 1: Verificar se já processado

Antes de processar:
- Consultar tabela ProcessedEvents com EventId
- Se já existe → Log warning e retorna
- Se não existe → Processa + salva EventId

Estratégia 2: Operações naturalmente idempotentes

- UPDATE SET status = 'Confirmed' WHERE id = X
  (Executar 10x tem mesmo efeito que 1x)

- Invalidar cache (invalidar 10x = invalidar 1x)

Estratégia 3: Tokens de idempotência

- Usar EventId como chave única em operações externas
- Gateway de pagamento aceita idempotency key
- Mesmo evento = mesma resposta, sem duplicação

Relacionamento com Outros Padrões

EDA + CQRS + DDD: A Tríade do Santa Mão

Esses três padrões trabalham juntos de forma complementar:

Integração EDA + CQRS + DDD

Na Prática:

1. CQRS define o que fazer:

CreateOrderCommand → Criar pedido
UpdateProfileCommand → Atualizar perfil
ProcessPaymentCommand → Processar pagamento

2. DDD define como fazer:

Order.Create() → Valida regras de negócio
               → Calcula valores
               → Gera OrderCreatedEvent

3. EDA define o que acontece depois:

OrderCreatedEvent → Notifica cliente
                  → Processa pagamento
                  → Atualiza analytics

Exemplo Completo: Criar Pedido

1. Cliente: POST /api/orders
   └─→ API recebe requisição

2. CQRS: CreateOrderCommand enviado
   └─→ CreateOrderCommandHandler executa

3. DDD: Order Aggregate é criado
   └─→ Valida: profissional disponível?
   └─→ Valida: valor correto?
   └─→ Aplica: regras de desconto
   └─→ Gera: OrderCreatedEvent (Domain Event)

4. Persistência: Salva Order no banco
   └─→ Unit of Work commit

5. EDA: MassTransit publica OrderCreatedEvent
   └─→ Redpanda recebe evento

6. Workers processam em paralelo:
   ├─→ NotificationWorker: Envia email
   ├─→ PaymentWorker: Inicia cobrança
   ├─→ AnalyticsWorker: Registra métrica
   └─→ CacheWorker: Atualiza cache do profissional

7. API retorna: 201 Created para cliente
   └─→ Cliente recebe confirmação imediata
   └─→ Processamento continua em background

Benefícios da Combinação:

  • Separação de responsabilidades (CQRS)
  • Lógica de negócio rica e protegida (DDD)
  • Processamento assíncrono e escalável (EDA)
  • Código organizado e testável
  • Sistema resiliente e de fácil manutenção

Relação com Outbox Pattern

O Outbox Pattern garante que eventos sejam publicados mesmo se o MassTransit/Redpanda falhar.

Como funciona:

1. Salva pedido no banco ✅
2. Salva evento na tabela Outbox ✅ (mesma transação)
3. Commit da transação ✅
4. Background worker lê Outbox e publica no Redpanda
5. Retry automático até conseguir publicar

Ver mais: Outbox Pattern

Relacionamentos:

Melhores Práticas no Santa Mão

1. Nomenclatura de Eventos

Sempre no passado (algo que JÁ aconteceu):

Correto:

ProfessionalRegisteredEvent
OrderCreatedEvent
PaymentProcessedEvent
ProfilePictureUploadedEvent
RequestAcceptedEvent

Incorreto:

RegisterProfessionalEvent  (parece comando)
CreateOrderEvent           (parece comando)
ProcessPaymentEvent        (parece comando)

Por quê? - Evento = fato consumado, não uma intenção - Diferencia claramente de Commands (que são imperativos) - Facilita entendimento do fluxo

2. Eventos Ricos vs Eventos Magros

No Santa Mão, preferimos eventos RICOS em informação:

Evento Rico (Preferido):

public class OrderCreatedEvent
{
    public Guid OrderId { get; set; }
    public Guid CustomerId { get; set; }
    public string CustomerName { get; set; }
    public string CustomerEmail { get; set; }
    public Guid ProfessionalId { get; set; }
    public string ProfessionalName { get; set; }
    public decimal TotalAmount { get; set; }
    public List<OrderItemDto> Items { get; set; }
    public AddressDto ServiceAddress { get; set; }
    public DateTime ScheduledFor { get; set; }
}

Vantagens: - ✅ Worker tem TUDO que precisa no evento - ✅ Não precisa fazer queries no banco - ✅ Mais rápido (menos latência) - ✅ Worker pode funcionar mesmo se API estiver fora

Evento Magro (Evitar):

public class OrderCreatedEvent
{
    public Guid OrderId { get; set; }
    // Worker precisa consultar API/banco para obter dados
}

Desvantagens: - ❌ Worker depende da API/banco estar disponível - ❌ Mais lento (query adicional) - ❌ Acoplamento entre worker e estrutura do banco

Quando usar evento magro: - Dados sensíveis (PCI, LGPD) que não devem ficar no Kafka - Evento muito grande (> 1MB) que impactaria performance

3. Versionamento de Eventos

Eventos mudam ao longo do tempo. Como lidar?

Estratégia 1: Adicionar campos opcionais

// Versão 1 (original)
public class OrderCreatedEvent
{
    public Guid OrderId { get; set; }
    public decimal TotalAmount { get; set; }
}

// Versão 2 (com desconto)
public class OrderCreatedEvent
{
    public Guid OrderId { get; set; }
    public decimal TotalAmount { get; set; }
    public string? DiscountCode { get; set; }  // ← Novo campo opcional
    public decimal? DiscountAmount { get; set; } // ← Novo campo opcional
}

Vantagem: Workers antigos continuam funcionando

Estratégia 2: Criar novo evento versionado

public class OrderCreatedEventV2 : OrderCreatedEvent
{
    public int Version => 2;
    public PaymentMethodDto PaymentMethod { get; set; }
}

Quando usar: Mudança quebraria compatibilidade

4. Tamanho de Eventos

Limites práticos:

  • Ideal: < 10 KB
  • ⚠️ Aceitável: 10-100 KB
  • 🚨 Problemático: > 100 KB
  • Evitar: > 1 MB

Se evento ficar muito grande:

// ❌ Ruim: Incluir foto base64 no evento
public class ProfileUpdatedEvent
{
    public string ProfilePictureBase64 { get; set; } // 2 MB!
}

// ✅ Bom: Incluir apenas URL
public class ProfileUpdatedEvent
{
    public string ProfilePictureUrl { get; set; } // 50 bytes
}

5. Tópicos e Particionamento

Convenção de nomes:

santamao.{contexto}.{entidade}.{ação}

Exemplos:
- santamao.professionals.registered
- santamao.orders.created
- santamao.orders.completed
- santamao.payments.processed

Chave de partição:

Garantir ordem para eventos do mesmo agregado usando PartitionKey com o ID do agregado. Todos eventos do mesmo ID vão para mesma partição, garantindo processamento em ordem.

6. Tratamento de Erros

Categorizar erros:

Erros Transientes (Retry Faz Sentido):

- API externa timeout
- Banco de dados momentaneamente indisponível
- Rede instável
- Rate limit temporário

Ação: MassTransit faz retry automático (3x)

Erros Permanentes (Retry Inútil):

- Dados inválidos no evento
- Regra de negócio violada
- Recurso não existe mais
- Permissão negada

Ação: Log + DLQ imediatamente (sem retry)

7. Correlation ID e Distributed Tracing

Todo evento deve ter um Correlation ID para rastreamento completo:

Correlation ID e Distributed Tracing

Isso permite rastrear todo o fluxo distribuído com um único ID.

8. Idempotência nos Consumers

Conclusão

A Event-Driven Architecture no Santa Mão é um dos pilares fundamentais que permite:

Por que EDA é Essencial no Santa Mão?

1. Escalabilidade Horizontal

Black Friday: 10x mais pedidos
Solução: Adicionar mais instâncias de workers
Resultado: Sistema absorve a carga sem alterar código

2. Resiliência

Gateway de pagamento fora do ar:
- API continua funcionando ✅
- Eventos ficam na fila
- Quando gateway volta, workers processam automaticamente
- Nenhum pedido perdido

3. Evolução Sem Breaking Changes

Nova funcionalidade: Enviar SMS além de email
- Criar novo worker (SMSWorker)
- Inscrever no OrderCreatedEvent
- Deploy independente
- Nenhum código existente alterado

4. Auditoria e Debugging

Cliente reclama: "Não recebi confirmação"
- Consultar Redpanda Console
- Ver exatamente qual evento foi publicado e quando
- Rastrear processamento pelos workers
- Identificar problema em minutos

5. Desacoplamento de Domínios

Contexto de Pedidos não precisa conhecer:
- Como funciona o sistema de notificações
- Como funciona o processamento de pagamentos
- Como funciona a análise de dados

Cada contexto é independente e pode evoluir separadamente

Stack Tecnológica EDA no Santa Mão

Stack Tecnológica EDA

Em Conjunto com Outros Padrões

A arquitetura completa do Santa Mão combina:

Combinação de Padrões

  • CQRS → Separa leitura de escrita, otimiza queries
  • DDD → Protege regras de negócio, organiza domínio
  • EDA → Desacopla processamento, permite escalabilidade

Quando NÃO Usar EDA?

Evitar EDA se:

  • Operação precisa de resposta IMEDIATA e síncrona
  • Sistema pequeno com poucos usuários (overhead desnecessário)
  • Equipe não tem experiência com mensageria distribuída
  • Requisitos de consistência forte (transações ACID)

EDA é ideal quando:

  • Operações podem ser assíncronas
  • Precisa escalar horizontalmente
  • Múltiplos consumidores para mesmo evento
  • Integração com sistemas externos
  • Auditoria e rastreabilidade são importantes

Próximos Passos

Para entender melhor a arquitetura completa do Santa Mão:

  1. CQRS Pattern - Como separamos comandos de consultas
  2. DDD Pattern - Como organizamos o domínio de negócio
  3. Outbox Pattern - Como garantimos que eventos sejam publicados

Recursos Adicionais

Documentação Oficial: - MassTransit Documentation - Redpanda Documentation - Apache Kafka Documentation

Artigos e Referências: - Event-Driven Architecture - Martin Fowler - Domain Events - Microsoft - MassTransit with Kafka


Última atualização: Novembro 2025
Maintainers: Equipe de Arquitetura Santa Mão
Dúvidas? Entre em contato com #architecture no Slack