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:
ProfessionalRegisteredEventOrderCreatedEventPaymentProcessedEventRequestAcceptedEventUserProfileUpdatedEvent
Componentes da Arquitetura

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.registeredsantamao.orders.createdsantamao.orders.completedsantamao.payments.processedsantamao.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 cadastraOrderCreatedEvent→ Quando um pedido é criadoPaymentProcessedEvent→ Quando um pagamento é confirmadoRequestAcceptedEvent→ Quando um profissional aceita uma solicitaçãoUserProfileUpdatedEvent→ 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

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:

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:

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:

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:
Created→Processing→Confirmed - ✅ 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
ProfessionalIdvã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:

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:
- CQRS Pattern → Documentação CQRS
- DDD Pattern → Documentação DDD
- Outbox Pattern → Outbox Pattern
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:

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

Em Conjunto com Outros Padrões
A arquitetura completa do Santa Mão combina:

- 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:
- CQRS Pattern - Como separamos comandos de consultas
- DDD Pattern - Como organizamos o domínio de negócio
- 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