Arquitetura Orientada a Eventos (Event-Driven Architecture)
📋 Visão Geral
Arquitetura Orientada a Eventos (EDA) é um padrão arquitetural onde componentes se comunicam através de eventos de forma assíncrona. Promove desacoplamento, escalabilidade e resiliência em sistemas distribuídos.
🏗️ Conceitos Fundamentais
Event (Evento)
O que é: Representação de algo que aconteceu no sistema.
Características:
- Imutável: Não pode ser alterado após criação
- Timestamp: Momento exato da ocorrência
- Idempotente: Pode ser processado múltiplas vezes
- Desacoplado: Não depende de outros eventos
csharp
public abstract class DomainEvent
{
public Guid Id { get; } = Guid.NewGuid();
public DateTime OccurredOn { get; } = DateTime.UtcNow;
public string AggregateId { get; set; }
public long Version { get; set; }
}
public class OrderCreatedEvent : DomainEvent
{
public Guid OrderId { get; }
public Guid CustomerId { get; }
public decimal TotalAmount { get; }
public List<OrderItem> Items { get; }
public OrderCreatedEvent(Guid orderId, Guid customerId, decimal totalAmount, List<OrderItem> items)
{
OrderId = orderId;
CustomerId = customerId;
TotalAmount = totalAmount;
Items = items;
}
}
public class OrderStatusChangedEvent : DomainEvent
{
public Guid OrderId { get; }
public OrderStatus OldStatus { get; }
public OrderStatus NewStatus { get; }
public string Reason { get; }
public OrderStatusChangedEvent(Guid orderId, OrderStatus oldStatus, OrderStatus newStatus, string reason)
{
OrderId = orderId;
OldStatus = oldStatus;
NewStatus = newStatus;
Reason = reason;
}
}Event Producer (Produtor de Eventos)
O que é: Componente que publica eventos no sistema.
Responsabilidades:
- Criar eventos
- Validar eventos
- Publicar eventos
- Garantir entrega
csharp
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly IEventBus _eventBus;
public OrderService(IOrderRepository orderRepository, IEventBus eventBus)
{
_orderRepository = orderRepository;
_eventBus = eventBus;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
var order = new Order
{
Id = Guid.NewGuid(),
CustomerId = request.CustomerId,
Items = request.Items,
Status = OrderStatus.Created,
CreatedAt = DateTime.UtcNow
};
await _orderRepository.SaveAsync(order);
// Publicar evento
var @event = new OrderCreatedEvent(
order.Id,
order.CustomerId,
order.TotalAmount,
order.Items);
await _eventBus.PublishAsync(@event);
return order;
}
public async Task ChangeOrderStatusAsync(Guid orderId, OrderStatus newStatus, string reason)
{
var order = await _orderRepository.GetByIdAsync(orderId);
var oldStatus = order.Status;
order.Status = newStatus;
order.UpdatedAt = DateTime.UtcNow;
await _orderRepository.SaveAsync(order);
// Publicar evento
var @event = new OrderStatusChangedEvent(orderId, oldStatus, newStatus, reason);
await _eventBus.PublishAsync(@event);
}
}Event Consumer (Consumidor de Eventos)
O que é: Componente que processa eventos publicados.
Responsabilidades:
- Receber eventos
- Processar eventos
- Executar lógica de negócio
- Confirmar processamento
csharp
public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IEmailService _emailService;
private readonly IInventoryService _inventoryService;
private readonly INotificationService _notificationService;
public OrderCreatedEventHandler(
IEmailService emailService,
IInventoryService inventoryService,
INotificationService notificationService)
{
_emailService = emailService;
_inventoryService = inventoryService;
_notificationService = notificationService;
}
public async Task HandleAsync(OrderCreatedEvent @event)
{
// Enviar email de confirmação
await _emailService.SendOrderConfirmationAsync(@event.CustomerId, @event.OrderId);
// Atualizar inventário
await _inventoryService.ReserveItemsAsync(@event.OrderId, @event.Items);
// Enviar notificação push
await _notificationService.SendOrderNotificationAsync(@event.CustomerId, @event.OrderId);
// Log do evento
Console.WriteLine($"Order {@event.OrderId} created for customer {@event.CustomerId}");
}
}
public class OrderStatusChangedEventHandler : IEventHandler<OrderStatusChangedEvent>
{
private readonly IEmailService _emailService;
private readonly IAnalyticsService _analyticsService;
public OrderStatusChangedEventHandler(IEmailService emailService, IAnalyticsService analyticsService)
{
_emailService = emailService;
_analyticsService = analyticsService;
}
public async Task HandleAsync(OrderStatusChangedEvent @event)
{
// Enviar email de atualização
await _emailService.SendStatusUpdateAsync(@event.OrderId, @event.NewStatus);
// Registrar analytics
await _analyticsService.TrackOrderStatusChangeAsync(@event);
// Log do evento
Console.WriteLine($"Order {@event.OrderId} status changed from {@event.OldStatus} to {@event.NewStatus}");
}
}🏗️ Padrões de Event-Driven Architecture
1. Event Sourcing
O que é: Armazenar todas as mudanças como uma sequência de eventos.
Vantagens:
- Auditoria completa
- Time travel
- Replay de eventos
- Desacoplamento
csharp
public class EventStore
{
private readonly IEventRepository _eventRepository;
public EventStore(IEventRepository eventRepository)
{
_eventRepository = eventRepository;
}
public async Task SaveEventsAsync(string aggregateId, IEnumerable<DomainEvent> events, long expectedVersion)
{
var eventList = events.ToList();
var currentVersion = expectedVersion;
foreach (var @event in eventList)
{
currentVersion++;
@event.AggregateId = aggregateId;
@event.Version = currentVersion;
await _eventRepository.SaveAsync(@event);
}
}
public async Task<IEnumerable<DomainEvent>> GetEventsAsync(string aggregateId)
{
return await _eventRepository.GetEventsAsync(aggregateId);
}
public async Task<T> GetAggregateAsync<T>(string aggregateId) where T : AggregateRoot, new()
{
var events = await GetEventsAsync(aggregateId);
var aggregate = new T();
foreach (var @event in events)
{
aggregate.Apply(@event);
}
return aggregate;
}
}
public abstract class AggregateRoot
{
private readonly List<DomainEvent> _uncommittedEvents = new();
public long Version { get; private set; }
protected void Apply(DomainEvent @event)
{
When(@event);
Version++;
}
protected void AddEvent(DomainEvent @event)
{
_uncommittedEvents.Add(@event);
}
public IEnumerable<DomainEvent> GetUncommittedEvents()
{
return _uncommittedEvents.AsReadOnly();
}
public void MarkEventsAsCommitted()
{
_uncommittedEvents.Clear();
}
protected abstract void When(DomainEvent @event);
}
public class Order : AggregateRoot
{
public Guid Id { get; private set; }
public Guid CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
public decimal TotalAmount { get; private set; }
public List<OrderItem> Items { get; private set; } = new();
public Order(Guid id, Guid customerId)
{
Id = id;
CustomerId = customerId;
Status = OrderStatus.Created;
AddEvent(new OrderCreatedEvent(id, customerId, 0, new List<OrderItem>()));
}
public void AddItem(Product product, int quantity)
{
var item = new OrderItem(product.Id, product.Name, product.Price, quantity);
Items.Add(item);
TotalAmount = Items.Sum(i => i.TotalPrice);
AddEvent(new OrderItemAddedEvent(Id, item));
}
public void Confirm()
{
Status = OrderStatus.Confirmed;
AddEvent(new OrderConfirmedEvent(Id, CustomerId, TotalAmount));
}
protected override void When(DomainEvent @event)
{
switch (@event)
{
case OrderCreatedEvent e:
Id = e.OrderId;
CustomerId = e.CustomerId;
break;
case OrderItemAddedEvent e:
Items.Add(e.Item);
TotalAmount = Items.Sum(i => i.TotalPrice);
break;
case OrderConfirmedEvent e:
Status = OrderStatus.Confirmed;
break;
}
}
}2. CQRS (Command Query Responsibility Segregation)
O que é: Separar operações de leitura e escrita.
csharp
// Commands
public class CreateOrderCommand : ICommand
{
public Guid CustomerId { get; set; }
public List<OrderItemRequest> Items { get; set; }
}
public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand>
{
private readonly IOrderRepository _orderRepository;
private readonly IEventBus _eventBus;
public CreateOrderCommandHandler(IOrderRepository orderRepository, IEventBus eventBus)
{
_orderRepository = orderRepository;
_eventBus = eventBus;
}
public async Task HandleAsync(CreateOrderCommand command)
{
var order = new Order(Guid.NewGuid(), command.CustomerId);
foreach (var item in command.Items)
{
order.AddItem(new Product(item.ProductId, item.Name, item.Price), item.Quantity);
}
await _orderRepository.SaveAsync(order);
foreach (var @event in order.GetUncommittedEvents())
{
await _eventBus.PublishAsync(@event);
}
order.MarkEventsAsCommitted();
}
}
// Queries
public class GetOrderQuery : IQuery<OrderDto>
{
public Guid OrderId { get; set; }
}
public class GetOrderQueryHandler : IQueryHandler<GetOrderQuery, OrderDto>
{
private readonly IOrderReadRepository _orderReadRepository;
public GetOrderQueryHandler(IOrderReadRepository orderReadRepository)
{
_orderReadRepository = orderReadRepository;
}
public async Task<OrderDto> HandleAsync(GetOrderQuery query)
{
return await _orderReadRepository.GetOrderDtoAsync(query.OrderId);
}
}3. Saga Pattern
O que é: Gerenciar transações distribuídas através de eventos.
csharp
public interface ISagaStep
{
Task ExecuteAsync();
Task CompensateAsync();
}
public class CreateOrderSaga
{
private readonly List<ISagaStep> _steps = new();
private readonly List<ISagaStep> _executedSteps = new();
public CreateOrderSaga AddStep(ISagaStep step)
{
_steps.Add(step);
return this;
}
public async Task ExecuteAsync()
{
try
{
foreach (var step in _steps)
{
await step.ExecuteAsync();
_executedSteps.Add(step);
}
}
catch (Exception)
{
await CompensateAsync();
throw;
}
}
private async Task CompensateAsync()
{
for (int i = _executedSteps.Count - 1; i >= 0; i--)
{
await _executedSteps[i].CompensateAsync();
}
}
}
public class ReserveInventoryStep : ISagaStep
{
private readonly IInventoryService _inventoryService;
private readonly Guid _orderId;
private readonly List<OrderItem> _items;
public ReserveInventoryStep(IInventoryService inventoryService, Guid orderId, List<OrderItem> items)
{
_inventoryService = inventoryService;
_orderId = orderId;
_items = items;
}
public async Task ExecuteAsync()
{
await _inventoryService.ReserveItemsAsync(_orderId, _items);
}
public async Task CompensateAsync()
{
await _inventoryService.ReleaseItemsAsync(_orderId, _items);
}
}🔧 Implementação em .NET
Event Bus
csharp
public interface IEventBus
{
Task PublishAsync<T>(T @event) where T : DomainEvent;
Task SubscribeAsync<T>(IEventHandler<T> handler) where T : DomainEvent;
}
public class InMemoryEventBus : IEventBus
{
private readonly IServiceProvider _serviceProvider;
private readonly Dictionary<Type, List<Type>> _handlers = new();
public InMemoryEventBus(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Subscribe<TEvent, THandler>()
where TEvent : DomainEvent
where THandler : IEventHandler<TEvent>
{
var eventType = typeof(TEvent);
var handlerType = typeof(THandler);
if (!_handlers.ContainsKey(eventType))
_handlers[eventType] = new List<Type>();
_handlers[eventType].Add(handlerType);
}
public async Task PublishAsync<T>(T @event) where T : DomainEvent
{
var eventType = @event.GetType();
if (_handlers.ContainsKey(eventType))
{
foreach (var handlerType in _handlers[eventType])
{
var handler = _serviceProvider.GetService(handlerType);
if (handler is IEventHandler<T> eventHandler)
{
await eventHandler.HandleAsync(@event);
}
}
}
}
}
public class RabbitMQEventBus : IEventBus
{
private readonly IConnection _connection;
private readonly IModel _channel;
public RabbitMQEventBus(IConnectionFactory connectionFactory)
{
_connection = connectionFactory.CreateConnection();
_channel = _connection.CreateModel();
_channel.ExchangeDeclare("events", ExchangeType.Topic);
}
public async Task PublishAsync<T>(T @event) where T : DomainEvent
{
var message = JsonSerializer.Serialize(@event);
var body = Encoding.UTF8.GetBytes(message);
_channel.BasicPublish(
exchange: "events",
routingKey: @event.GetType().Name,
basicProperties: null,
body: body);
await Task.CompletedTask;
}
public Task SubscribeAsync<T>(IEventHandler<T> handler) where T : DomainEvent
{
var queueName = $"queue_{typeof(T).Name}";
_channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false);
_channel.QueueBind(queueName, "events", typeof(T).Name);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var @event = JsonSerializer.Deserialize<T>(message);
await handler.HandleAsync(@event);
_channel.BasicAck(ea.DeliveryTag, false);
};
_channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
return Task.CompletedTask;
}
}Event Handlers
csharp
public interface IEventHandler<T> where T : DomainEvent
{
Task HandleAsync(T @event);
}
public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IEmailService _emailService;
private readonly IInventoryService _inventoryService;
private readonly ILogger<OrderCreatedEventHandler> _logger;
public OrderCreatedEventHandler(
IEmailService emailService,
IInventoryService inventoryService,
ILogger<OrderCreatedEventHandler> logger)
{
_emailService = emailService;
_inventoryService = inventoryService;
_logger = logger;
}
public async Task HandleAsync(OrderCreatedEvent @event)
{
try
{
_logger.LogInformation("Processing order created event for order {OrderId}", @event.OrderId);
// Enviar email de confirmação
await _emailService.SendOrderConfirmationAsync(@event.CustomerId, @event.OrderId);
// Atualizar inventário
await _inventoryService.ReserveItemsAsync(@event.OrderId, @event.Items);
_logger.LogInformation("Successfully processed order created event for order {OrderId}", @event.OrderId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing order created event for order {OrderId}", @event.OrderId);
throw;
}
}
}Configuration
csharp
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Registrar event handlers
services.AddScoped<IEventHandler<OrderCreatedEvent>, OrderCreatedEventHandler>();
services.AddScoped<IEventHandler<OrderStatusChangedEvent>, OrderStatusChangedEventHandler>();
// Registrar event bus
services.AddSingleton<IEventBus, InMemoryEventBus>();
// Configurar RabbitMQ
services.AddSingleton<IConnectionFactory>(provider =>
{
return new ConnectionFactory
{
HostName = "localhost",
UserName = "guest",
Password = "guest"
};
});
// Registrar sagas
services.AddScoped<CreateOrderSaga>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Configurar event subscriptions
var eventBus = app.ApplicationServices.GetService<IEventBus>();
var orderCreatedHandler = app.ApplicationServices.GetService<IEventHandler<OrderCreatedEvent>>();
eventBus.SubscribeAsync(orderCreatedHandler);
}
}📊 Vantagens e Desvantagens
Vantagens
- Desacoplamento: Componentes não conhecem uns aos outros
- Escalabilidade: Processamento assíncrono
- Resiliência: Falhas isoladas
- Flexibilidade: Fácil adicionar novos handlers
- Auditoria: Rastreamento completo de eventos
Desvantagens
- Complexidade: Debugging mais difícil
- Eventual Consistency: Dados podem estar temporariamente inconsistentes
- Event Ordering: Ordem dos eventos pode ser importante
- Testing: Testes mais complexos
- Monitoring: Observabilidade mais desafiadora
📚 Recursos Adicionais
Frameworks .NET
- MediatR: Implementação de mediator pattern
- MassTransit: Message bus para .NET
- NServiceBus: Enterprise service bus
- Rebus: Simple and lean service bus
Ferramentas
- EventStore: Database para event sourcing
- Apache Kafka: Event streaming platform
- RabbitMQ: Message broker
- Azure Event Hubs: Cloud event streaming
Livros Recomendados
- "Building Event-Driven Microservices"
- "Domain-Driven Design"
- "Event Sourcing and CQRS"