Blog

Event Sourcing e CQRS: eventos como trilha auditável

Quando o histórico importa mais que o estado atual, Event Sourcing e CQRS oferecem rastreabilidade total com modelos separados de leitura e escrita.

Por Alexandre Martins · · 3 min de leitura

Event Sourcing e CQRS: eventos como trilha auditável. Quando o histórico importa mais que o estado atual.

Sistemas complexos frequentemente carregam uma exigência que os modelos tradicionais de persistência falham em atender: entender não apenas o que é o estado atual de uma entidade, mas como ela chegou até ali.

Quando o histórico se torna mais valioso do que o estado final, é hora de considerar arquiteturas que tratem o dado como narrativa — e não apenas como fotografia.

Por que Event Sourcing?

Event Sourcing propõe uma mudança radical: em vez de gravar o estado atual de uma entidade, gravamos todos os eventos que a levaram até ele.

Imagine um pedido entregue. Em uma arquitetura tradicional, você veria algo como:

status: ENTREGUE

No Event Sourcing, o que temos é:

  • PedidoCriadoEvent
  • PagamentoConfirmadoEvent
  • PedidoEnviadoEvent
  • EntregaConfirmadaEvent

Essa sequência de eventos constitui uma linha do tempo imutável. O estado atual é derivado da reprodução (ou “replay”) desses eventos. Isso traz não só auditabilidade total, mas também a possibilidade de simular, depurar e evoluir o sistema com mais segurança.

CQRS: leitura e escrita desacopladas

O padrão Command Query Responsibility Segregation (CQRS) complementa bem o Event Sourcing ao propor a separação entre:

  • Comandos (Commands): ações que alteram o estado do sistema.
  • Consultas (Queries): operações que apenas leem os dados.

Essa divisão permite otimizar cada lado com foco distinto. Comandos podem aplicar regras de negócio rigorosas. Leitura pode ser estruturada para atender interfaces, relatórios ou APIs com desempenho e formato adequados.

Do comando à consulta: escrita por eventos, leitura por projeção. Fluxograma do lado de escrita e do lado de leitura: o comando passa pelo Aggregate, que valida as regras e emite o evento; o evento vai para o Event Store, um log imutável; a projeção consome o evento e atualiza o modelo de leitura que as consultas usam. O replay dos eventos reconstrói o estado e as projeções.
Do comando à consulta: escrita por eventos, leitura por projeção. Fluxograma do lado de escrita e do lado de leitura: o comando passa pelo Aggregate, que valida as regras e emite o evento; o evento vai para o Event Store, um log imutável; a projeção consome o evento e atualiza o modelo de leitura que as consultas usam. O replay dos eventos reconstrói o estado e as projeções.
Ver o fluxograma em texto (Mermaid)
flowchart TD
  cmd["Comando CriarPedido"] --> agg{"O Aggregate aceita\nas regras de negócio?"}
  agg -- "não" --> rej["Comando rejeitado"]
  agg -- "sim" --> evt["Evento PedidoCriado"]
  evt --> store[("Event Store\nlog imutável")]
  store --> proj["Projeção consome o evento"]
  proj --> view[("Modelo de leitura\nPedidoView")]
  consulta["Consulta"] --> view
  store -. "replay" .-> rebuild["Reconstrói o estado\ne as projeções"]

Exemplo prático com Java e Axon Framework

Vamos a um exemplo de modelagem de pedidos. Começamos com um comando:

public class CriarPedidoCommand {
    @TargetAggregateIdentifier
    private String pedidoId;
    private List<Item> itens;
    private BigDecimal valorTotal;
}

E o evento correspondente:

public class PedidoCriadoEvent {
    private String pedidoId;
    private List<Item> itens;
    private BigDecimal valorTotal;
}

A Aggregate é a responsável por aplicar comandos e emitir eventos:

@Aggregate
public class PedidoAggregate {

    @AggregateIdentifier
    private String pedidoId;

    public PedidoAggregate() {}

    @CommandHandler
    public PedidoAggregate(CriarPedidoCommand cmd) {
        if (cmd.getItens().isEmpty()) {
            throw new IllegalArgumentException("Pedido sem itens");
        }
        AggregateLifecycle.apply(new PedidoCriadoEvent(cmd.getPedidoId(), cmd.getItens(), cmd.getValorTotal()));
    }

    @EventSourcingHandler
    public void on(PedidoCriadoEvent event) {
        this.pedidoId = event.getPedidoId();
    }
}

Para leitura, criamos uma projeção desacoplada:

@Component
public class PedidoProjection {

    @Autowired
    private PedidoRepository repository;

    @EventHandler
    public void on(PedidoCriadoEvent event) {
        PedidoView view = new PedidoView(event.getPedidoId(), "CRIADO", event.getItens());
        repository.save(view);
    }
}

Desafios reais que surgem com essa abordagem

Apesar das vantagens claras, Event Sourcing e CQRS introduzem complexidades operacionais e conceituais que exigem maturidade técnica da equipe. Os principais desafios incluem:

Versionamento de eventos

Com o tempo, eventos evoluem. Campos mudam, regras mudam. A melhor prática é tratar eventos como imutáveis, criando novas versões (ex: PedidoCriadoV2Event) e mantendo o suporte a eventos antigos.

Rebuild de projeções

Se o modelo de leitura mudar, será necessário reprocessar todos os eventos históricos. Isso demanda infraestrutura que permita replays ordenados e confiáveis — como Kafka, EventStoreDB ou Axon Server.

Consistência eventual

A leitura não é sincronizada com a escrita em tempo real. Isso significa que usuários podem ver dados “defasados” por alguns instantes. É necessário explicitar isso nas interfaces e ajustar expectativas no domínio.

Diagnóstico e debug

Com Event Sourcing, você não investiga o banco — você reproduz eventos. Para isso, ferramentas como Axon Dashboard, Kibana, observabilidade com tracing e correlação de eventos são essenciais.

Ferramentas que ajudam a implementar

  • Axon Framework (Java): suporte completo a Event Sourcing, CQRS e DDD.
  • EventStoreDB: banco de dados de eventos com log append-only nativo.
  • Kafka + Spring Boot: estrutura escalável baseada em consumidores assíncronos.
  • Temporal.io: engine de orquestração de workflows com histórico imutável (não é Event Sourcing puro, mas compartilha muitas ideias).

Boas práticas

  1. Trate eventos como contratos públicos: imutáveis, versionáveis e bem documentados.
  2. Não acople o modelo de leitura ao domínio: evite usar entidades de negócio diretamente na projeção.
  3. Use snapshots com moderação: úteis para desempenho, mas não substituem o log completo.
  4. Respeite os Bounded Contexts: nem todo o sistema precisa seguir CQRS e Event Sourcing.
  5. Automatize replays de eventos: tenha pipelines versionadas e seguras para reconstrução de projeções.

Conclusão

Event Sourcing e CQRS não são soluções universais — e não devem ser aplicadas por modismo. Mas quando o domínio exige rastreabilidade, múltiplas visões sobre os dados e controle total sobre o histórico de mudanças, essas abordagens brilham.

A adoção inicial é exigente. Envolve novas mentalidades, novas ferramentas e novas formas de pensar persistência. Mas os ganhos em termos de resiliência, auditabilidade, flexibilidade e desacoplamento são evidentes quando bem aplicados.

Comece pelo domínio. Se ele for complexo o suficiente, Event Sourcing e CQRS surgirão naturalmente como aliados valiosos.

Assuntos: Event Sourcing, CQRS, Arquitetura de Software, Java, Axon Framework

Continue lendo

Quer aplicar isto no seu sistema? Fale com a MFactor: contato ou WhatsApp +55 (19) 99778-9831.