Aula 18 - Arquitetura Orientada a Eventos (EDA) e Mensageria ⚡
Objetivo Pedagógico
Objetivo: Conceber sistemas distribuídos altamente desacoplados e resilientes aplicando Arquitetura Orientada a Eventos (EDA), mensageria assíncrona (RabbitMQ vs Kafka), padrões Event Sourcing e CQRS (Command Query Responsibility Segregation).
📑 1. Fundamentos Teóricos & Análise Técnica
Sistemas construídos exclusivamente sobre chamadas síncronas ponto a ponto (HTTP/REST) criam acoplamento temporal rígido: se um serviço downstream falhar ou sofrer lentidão, toda a cadeia de chamadas cascata em lentidão e falhas generalizadas.
A Arquitetura Orientada a Eventos (Event-Driven Architecture - EDA) resolve esse acoplamento através do paradigma produtor-consumidor: 1. Eventos de Domínio e Assincronismo: - Um evento representa um fato imutável ocorrido no passado do negócio (PedidoCriado, PagamentoConfirmado). - O produtor publica o evento no broker de mensagens sem qualquer conhecimento sobre quem consumirá aquele evento ou quantos consumidores existem. 2. RabbitMQ (Message Broker Tradicional) vs. Apache Kafka (Event Streaming Platform): - RabbitMQ: Focado em roteamento sofisticado de mensagens (Exchange direct, topic, fanout), confirmações individuais (ACK) e descarte da mensagem após o consumo. Ideal para filas transacionais de tarefas e comandos. - Apache Kafka: Log de eventos distribuído, append-only e persistente em disco particionado. Múltiplos consumidores independentes leem o log em suas próprias velocidades através de seus próprios ponteiros (Offsets). Permite reprocessamento histórico (Event Replay). 3. Padrões Avançados: CQRS e Event Sourcing: - CQRS: Separa os modelos de gravação (Comandos que alteram estado) dos modelos de leitura (Consultas altamente otimizadas e desnormalizadas). - Event Sourcing: O estado da aplicação não é gravado como uma linha mutável de banco de dados, mas reconstruído pelo somatório histórico de todos os eventos que já ocorreram na vida do sistema.
📐 Arquitetura Conceitual & Diagrama de Fluxo
graph LR
Produtor["Serviço de Checkout (Order Service)"] -->|Publica PedidoCriado| Kafka["Broker Kafka: Tópico 'orders.events'"]
Kafka -->|Consumo Assíncrono Partição 0| Estoque["Serviço de Estoque (Separação)"]
Kafka -->|Consumo Assíncrono Partição 0| Faturamento["Serviço Fiscal (Nota Fiscal)"]
Kafka -->|Consumo Assíncrono Partição 0| Notificacao["Serviço de Push / E-mail"]
Note["Desacoplamento Temporal: Se o Fiscal cair, Checkout continua funcionando normalmente!"]
style Produtor fill:#e1f5fe,stroke:#01579b
style Kafka fill:#fff3e0,stroke:#e65100
style Estoque fill:#f3e5f5,stroke:#7b1fa2
style Faturamento fill:#ffebee,stroke:#c62828
style Notificacao fill:#e8f5e9,stroke:#2e7d32 🔍 Pilares e Diretrizes Técnicas
Nesta unidade, aprofundamos os seguintes conceitos fundamentais: - Desacoplamento Temporal e Espacial: Produtores e consumidores operam em momentos e servidores completamente independentes. - Garantias de Entrega (At-least-once / Exactly-once): Configuração rigorosa de confirmações para evitar perda ou duplicação de eventos críticos. - Padrão Outbox Transacional: Gravação atômica da alteração no banco de dados local conjuntamente com a tabela de saída de eventos para evitar inconsistência distribuída. - Escalabilidade Horizontal por Partições: Distribuição paralela de carga do Kafka permitindo milhões de eventos por segundo.
🛠️ 2. Implementação Prática em Arquitetura Orientada a Eventos, Kafka e RabbitMQ
Abaixo está a implementação técnica de referência, estruturada com padrões de engenharia de software e foco em robustez:
// order_event_publisher.ts (Publicação Confiável de Eventos com KafkaJS)
import { Kafka, Partitioners } from 'kafkajs';
const kafka = new Kafka({
clientId: 'order-service',
brokers: ['kafka-broker-1:9092', 'kafka-broker-2:9092'],
});
const producer = kafka.producer({
createPartitioner: Partitioners.DefaultPartitioner,
idempotent: true, // Garante entrega exatamente-uma-vez sem duplicações
});
interface OrderCreatedEvent {
orderId: string;
customerId: string;
totalAmount: number;
timestamp: string;
}
export async function publishOrderCreated(event: OrderCreatedEvent) {
await producer.connect();
await producer.send({
topic: 'orders.events',
messages: [
{
key: event.customerId, // Garante que eventos do mesmo cliente caiam na mesma partição!
value: JSON.stringify(event),
headers: {
'event-type': 'OrderCreated',
'source-app': 'ecommerce-checkout',
},
},
],
});
console.log(`[EVENT PUBLISHED] Evento do Pedido ${event.orderId} emitido com sucesso.`);
}
💡 Análise Passo a Passo do Código
- Uso de Chave de Particionamento (
key: event.customerId): Garante que todos os eventos do mesmo cliente sejam processados estritamente em ordem na mesma partição do Kafka. - Configuração
idempotent: true: Habilita garantias nativas no broker para evitar duplicações causadas por retentativas de rede. - Metadados em Headers: Permite que roteadores e filtros downstream inspecionem o tipo de evento sem deserializar todo o payload.
🎯 3. Próximos Passos & Sequência Didática
-
Slides da Aula
-
Quiz de Fixação
-
Exercícios Práticos
-
Desafio de Projeto