.NET CQRS Event Sourcing .NET RabbitMQ microsserviços arquitetura de software mensageria DDD

Implementando padrões de CQRS e Event Sourcing com .NET e RabbitMQ

Aprenda a implementar CQRS e Event Sourcing em microsserviços com .NET e RabbitMQ, separando leitura e escrita e garantindo rastreabilidade robusta das operações.

4 min de leitura
Implementando padrões de CQRS e Event Sourcing com .NET e RabbitMQ

Em projetos de microsserviços, especialmente aqueles que demandam alta escalabilidade, rastreabilidade e separação clara entre comandos e consultas, implementar padrões como CQRS (Command Query Responsibility Segregation) e Event Sourcing pode melhorar significativamente a qualidade e a manutenibilidade do sistema.

O problema que quero resolver

Imagine um sistema financeiro que precisa registrar todas as operações de débito e crédito com total rastreabilidade e, ao mesmo tempo, responder rapidamente a consultas do histórico de movimentos. Uma arquitetura tradicional pode ficar confusa, misturando lógica de leitura e escrita, dificultando auditorias e escalabilidade.

Conceito funcionando antes de aprofundar

Vou começar com um exemplo simples, mostrando as duas partes do CQRS separadas:

  • Write model (comandos): para alterar o estado do sistema.
  • Read model (consultas): para obter dados otimizados para leitura.

Suponha que temos um comando para registrar uma transação e uma consulta para listar as transações:

// Comando (write)
public class RegisterTransactionCommand
{
    public Guid AccountId { get; set; }
    public decimal Amount { get; set; }
}

// Consulta (read)
public class TransactionDto
{
    public Guid Id { get; set; }
    public Guid AccountId { get; set; }
    public decimal Amount { get; set; }
    public DateTime CreatedAt { get; set; }
}

Separamos a lógica para receber o comando, validar, persistir e publicar eventos, e a lógica para manejar consultas rapidamente via bancos otimizados para leitura.

Integrando CQRS com Event Sourcing e RabbitMQ

Event Sourcing significa que o estado do sistema é derivado de uma série de eventos imutáveis que guardam todas as alterações. RabbitMQ entra para garantir que os eventos sejam difundidos com segurança e que todos os microsserviços possam reagir.

Arquitetura explicada

flowchart TD
    subgraph Command_side
        cmd["Recebe comando (RegisterTransactionCommand)"] --> val["Validação e regras de negócio"]
        val --> ev["Persistência do evento (TransactionRegisteredEvent)"]
        ev --> rmq["Publica evento no RabbitMQ"]
    end

    subgraph Read_side
        rmq --> handle["Handler consume evento TransactionRegisteredEvent"]
        handle --> update["Atualiza modelos de leitura (Read model)"]
    end

    consulta["Consulta (Queries via REST ou GraphQL)"] --> readmodel["Modelos de leitura otimizados"]

Código prático para publicar e consumir eventos

// Evento que representa a transação
public class TransactionRegisteredEvent
{
    public Guid TransactionId { get; set; }
    public Guid AccountId { get; set; }
    public decimal Amount { get; set; }
    public DateTime OccurredAt { get; set; }
}

// Publicador de evento usando RabbitMQ simplificado
public class EventPublisher
{
    private readonly IModel _channel;

    public EventPublisher(IModel channel) {
        _channel = channel;
    }

    public void PublishTransactionRegistered(TransactionRegisteredEvent evt) {
        var body = JsonSerializer.SerializeToUtf8Bytes(evt);
        _channel.BasicPublish(exchange: "transactions-exchange", routingKey: "transaction.registered", body: body);
    }
}

// Consumidor do evento no Read Side
public class TransactionEventHandler
{
    private readonly IReadModelRepository _repository;

    public TransactionEventHandler(IReadModelRepository repository) {
        _repository = repository;
    }

    public void Handle(TransactionRegisteredEvent evt) {
        var dto = new TransactionDto {
            Id = evt.TransactionId,
            AccountId = evt.AccountId,
            Amount = evt.Amount,
            CreatedAt = evt.OccurredAt
        };

        _repository.Save(dto);
    }
}

Armadilhas comuns

  • Ignorar a eventual consistência: os modelos de leitura podem estar defasados em relação aos dados de escrita por alguns instantes; isso deve ser esperado e projetado.
  • Acoplamento forte entre comandos e consultas: mantenha os modelos independentes para facilitar manutenção e evolução.
  • Persistir estado e eventos em armazenamentos diferentes sem cuidado: garanta a atomicidade ou mecanismos de compensação para evitar inconsistências.

Quando usar CQRS e Event Sourcing

Prefiro aplicar essas técnicas em sistemas com requisitos claros de rastreabilidade, auditoria, ou onde a escalabilidade das consultas difere muito da escalabilidade das escritas. Em sistemas simples ou com baixa complexidade, a sobrecarga pode não valer a pena.

Próximos passos

Adotar CQRS e Event Sourcing com RabbitMQ no .NET pode parecer complexo inicialmente, mas traz ganhos estruturais importantes para sistemas distribuídos e escaláveis, facilitando manutenção e evolução contínua do seu backend.

Blog do Felipe Marciano

Decisões de arquitetura, .NET e sistemas distribuídos, escritas por quem vive com as consequências delas.

© 2026 Felipe Marciano. Todos os direitos reservados.