Skip to content

CQRS e Event Sourcing

Dois padrões que costumam aparecer juntos em sistemas orientados a eventos (Event-Driven Architecture) e modelados com DDD: CQRS separa leitura de escrita; Event Sourcing guarda os eventos em vez do estado atual.

CQRS

Definição: CQRS (Command Query Responsibility Segregation)

Separa as operações que alteram o estado (commands) das que apenas consultam (queries), com modelos distintos para cada lado: o de escrita protege regras e consistência; o de leitura é otimizado para consultar.

Command (escrita) Query (leitura)
Intenção Mudar o estado: criar, atualizar, cancelar Consultar dados
Retorno Não devolve dados de negócio (no máximo um identificador) Devolve DTOs / view models
Foco Validar regras e invariantes Desempenho e formato da resposta
Armazenamento Banco transacional Pode ser outro banco (réplica, NoSQL, índice de busca)
public record CriarPedidoCommand(Long clienteId, List<ItemDTO> itens) {}
public record BuscarPedidosQuery(Long clienteId) {}

public class CriarPedidoHandler {              // um handler por comando
    public void handle(CriarPedidoCommand cmd) {
        // valida, persiste e publica o evento PedidoCriado
    }
}
flowchart LR
    C["Cliente"] -->|Command| H["Handler de escrita"]
    H --> W[("Banco de escrita")]
    H -->|evento| P["Projetor"]
    P --> R[("Banco de leitura")]
    C -->|Query| Q["Handler de leitura"]
    Q --> R
  • Modelos separados: o de escrita pode ter regras ricas; o de leitura usa estruturas desnormalizadas e DTOs prontos para a tela.
  • Consistência eventual: depois de um command, a leitura pode levar um instante para refletir a mudança — é o comportamento esperado. Comunique isso ao usuário e evite depender de leitura imediata.
  • Escalabilidade: escrita e leitura escalam de forma independente (várias réplicas de leitura, caches, índices).
  • Projeções: modelos de leitura montados a partir de eventos (PedidoCriadoEvent), atualizados de forma assíncrona; podem existir várias, uma por necessidade de consulta.
  • Eventos de domínio: algo que aconteceu no negócio, publicado após o command para atualizar projeções e notificar outros serviços (Kafka, RabbitMQ).

Quando NÃO usar

CQRS adiciona complexidade (dois modelos, projeções, consistência eventual). Se o CRUD simples resolve, não use. Comece pequeno, avalie o custo, monitore o atraso das projeções e documente o fluxo. É útil quando leitura e escrita têm necessidades muito diferentes (carga, forma dos dados, escalabilidade).

Event Sourcing

Definição: Event Sourcing

Em vez de gravar só o estado atual, grava-se a sequência de eventos que levou até ele. O estado é obtido reaplicando os eventos em ordem (replay). O histórico é a fonte da verdade.

public record PedidoCriadoEvent(String pedidoId, String clienteId,
                                Instant ocorridoEm, int versaoSchema) {}   // evento imutável

eventStore.append("pedido-123", new PedidoCriadoEvent(...));

PedidoAggregate p = new PedidoAggregate();
for (Evento e : eventStore.carregar("pedido-123")) {
    p.aplicar(e);                          // reconstrói o estado
}
Conceito Detalhe
Event store Repositório append-only (só acrescenta), com ordem e durabilidade; consulta por agregado e por período
Eventos imutáveis Fatos no passado ("PedidoConfirmado"), nunca alterados; carregam dados e timestamp; têm versão de schema
Replay Reconstrói o estado atual (ou de um instante passado) reaplicando eventos; permite simulações e correções
Agregado Valida regras e gera os eventos; o estado é derivado deles (ver DDD)
Snapshots "Fotos" do estado em um ponto, para não reaplicar milhares de eventos: carrega-se o snapshot e só os eventos posteriores
Versionamento de eventos Eventos antigos continuam existindo: use upcasters (conversão entre versões) e múltiplas versões na leitura
Auditoria Registro completo de quem, o quê e quando, útil para conformidade (ex.: LGPD)

Consistência: o Event Sourcing costuma ser combinado com CQRS: o lado de escrita grava eventos; projeções assíncronas montam os modelos de leitura (consistência eventual). Exemplo de fluxo: pedido criado → confirmado → cancelado; o estado atual é a soma desses eventos.

Cuidados: exige disciplina com o esquema dos eventos, ferramentas para replay e projeções e uma equipe que domine o modelo; para dados com pouco valor histórico, o custo não compensa.

Multi-tenancy (arquitetura de dados)

Um tenant é um cliente que usa a mesma aplicação, compartilhando a infraestrutura mas com dados e configuração isolados.

Modelo Isolamento Custo Quando
Banco por cliente Máximo (físico) Alto Clientes grandes, exigências regulatórias
Schema por cliente Bom (lógico, no mesmo banco) Médio Clientes médios
Coluna tenant_id Menor (lógico por linha) Baixo Muitos clientes pequenos
@Entity
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantId", type = String.class))
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public class Pedido { @Column(name = "tenant_id") private String tenantId; /* ... */ }
  • Resolução do tenant na requisição: subdomínio (cliente1.app.com), cabeçalho (X-Tenant-ID), claim do JWT ou parâmetro; guarde em um contexto (ThreadLocal) e limpe-o ao final.
  • Filtros de dados: aplique-os automaticamente (filtro do Hibernate, @Where) para nunca esquecer o tenant_id — um vazamento entre clientes (data leak) é grave.
  • Segurança: valide o tenant no serviço, use RBAC e faça auditoria.
  • Escala: comece com um modelo simples e permita migrar para um mais isolado quando um cliente crescer.