Skip to content

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"