Diagrama de CQRS e event sourcing: comandos, eventos e modelos de leitura

CQRS e event sourcing explicados com um diagrama: lado de comandos, event store só de inserção, projetores, modelos de leitura, replays e versionamento.

· Fundador e desenvolvedor

Publicado em

Diagrama de CQRS e event sourcing: comandos, eventos e modelos de leitura

A maioria das aplicações lê e grava as mesmas tabelas pelo mesmo modelo. Isso funciona até os formatos divergirem: as gravações precisam de regras de negócio estritas em um pedido por vez, enquanto as leituras querem listas, buscas e totais sobre milhares de pedidos. O CQRS separa as duas coisas, e o event sourcing muda o que você guarda desde o início.

Os dois costumam ser apresentados como um par, mas são decisões separadas. Este artigo explica cada um, como se encaixam, como fica o diagrama de um sistema que combina os dois e os custos que fazem muitos times se arrependerem de adotá-los cedo demais.

CQRS em um parágrafo

Command Query Responsibility Segregation significa usar um modelo para alterar dados e outro para lê-los. Comandos (PlaceOrder, CancelOrder) vão para um lado de escrita que aplica as regras. Consultas vão para um lado de leitura moldado para as telas que precisam delas. O artigo de Martin Fowler sobre CQRS, que atribui o padrão a Greg Young, traz também o alerta que vale repetir: para a maioria dos sistemas, o CQRS adiciona uma complexidade arriscada. Use-o onde leituras e escritas realmente divergem, não na aplicação inteira.

CQRS não exige dois bancos de dados. Um modelo de escrita e um conjunto de views SQL sobre as mesmas tabelas já é CQRS. Armazenamentos separados vêm depois, quando a carga ou o formato das leituras exigem.

Event sourcing em um parágrafo

O event sourcing guarda as mudanças em vez do estado atual. Um pedido não é uma linha com uma coluna de status; é um fluxo de eventos: OrderPlaced, PaymentAuthorized, OrderShipped. O estado atual é o que você obtém ao reproduzi-los. O artigo de Fowler sobre event sourcing descreve a ideia em detalhes. Você ganha de graça um histórico completo de auditoria e pode criar novas visões do passado a qualquer momento, porque nada foi sobrescrito.

Como os dois se encaixam

O event sourcing quase obriga ao CQRS. Reproduzir eventos é um bom jeito de carregar um pedido antes de alterá-lo, mas um péssimo jeito de responder "mostre todos os pedidos abertos acima de 100 dólares". Por isso o lado de escrita trabalha com fluxos de eventos, e o lado de leitura mantém modelos de leitura separados, atualizados a partir desses eventos.

O fluxo:

  1. Um comando chega ao lado de comandos.
  2. O handler carrega o agregado (o pedido) reproduzindo seu fluxo, verifica as regras de negócio e decide quais eventos novos emitir.
  3. Ele acrescenta os eventos ao event store com a versão esperada do fluxo. Se outro comando acrescentou eventos nesse meio-tempo, a operação falha e o comando é repetido. Isso é concorrência otimista, e substitui os locks de linha.
  4. Projetores assinam o fluxo de eventos e atualizam os modelos de leitura: uma tabela para a lista de pedidos, um índice de busca, contadores para um painel.
  5. As consultas leem apenas dos modelos de leitura.

O event store

Um event store precisa de poucos recursos, mas precisa deles exatamente:

  • Fluxos só de inserção, um por agregado, com um número de versão por evento.
  • Inserção condicional pela versão esperada, de forma atômica.
  • Leitura de um fluxo desde o início ou a partir de uma versão.
  • Assinaturas de todos os eventos novos, em ordem, com uma posição que um projetor pode guardar para retomar dali.

Event stores dedicados oferecem isso, e uma tabela relacional com uma restrição de unicidade no ID do fluxo e na versão também, junto com um outbox ou um change feed para as assinaturas.

Modelos de leitura e consistência eventual

As projeções rodam depois que o evento é gravado, então os modelos de leitura ficam atrasados em milissegundos ou, durante um acúmulo, bem mais. Projete para isso:

  • Depois de um comando, devolva a nova versão e deixe o cliente esperar até o modelo de leitura alcançá-la, ou mostre o resultado a partir da resposta do comando.
  • Dê a cada projetor sua própria posição, para que um índice de busca lento não atrase a lista de pedidos.
  • Torne as projeções idempotentes. Projetores reiniciam e releem eventos; aplicar OrderShipped duas vezes não pode contar dois envios.

A recompensa é que os modelos de leitura são descartáveis. Uma tela nova precisa de outro formato? Escreva um projetor e reproduza o histórico nele. Um bug numa projeção? Corrija, descarte o modelo de leitura e reconstrua.

Um exemplo prático: um relatório novo

Um mês depois do lançamento, o financeiro pede a receita por categoria de produto por dia, incluindo o histórico. Num sistema CRUD, você escreveria uma migration e um script de backfill, e o histórico anterior à mudança talvez já tivesse se perdido. Com event sourcing, você escreve um projetor que escuta OrderPlaced e OrderRefunded, aponta-o para o início do log de eventos e deixa reproduzir. Quando ele alcança o presente, o relatório cobre todos os pedidos desde o lançamento e passa a se manter atualizado sozinho.

O diagrama de arquitetura

Um usuário envia um comando PlaceOrder ao lado de comandos, que acrescenta eventos a um event store. Por um fluxo, eles chegam a projetores que atualizam três modelos de leitura: lista de pedidos, índice de busca e totais diários. O lado de consultas só lê esses modelos.

10 componentes · 11 conexões

Abrir no Archboard
Conteúdo do diagrama
  • User
  • Command side
  • Event store
  • Event stream
  • Projectors
  • Read model: order list
  • Read model: search index
  • Read model: daily totals
  • Query side
  • PlaceOrder
  • append
  • subscription
  • GET /orders
CQRS com event sourcing. Os comandos são validados contra um agregado e gravados como eventos em um event store só de inserção; projetores transformam o fluxo de eventos em vários modelos de leitura, e as consultas só leem esses modelos.

Como ler:

  • Linha de cima, o caminho de escrita: o comando PlaceOrder do usuário vai para o lado de comandos, que acrescenta eventos ao event store. Uma assinatura alimenta o fluxo de eventos.
  • Direita: os projetores consomem o fluxo e mantêm três modelos de leitura atualizados, cada um no armazenamento que combina com suas consultas.
  • Embaixo à esquerda, o caminho de leitura: as consultas vão para o lado de consultas, que só lê modelos de leitura e não contém regras de negócio.
  • O bloco Markdown mostra um fluxo com suas versões, o que facilita explicar a verificação da versão esperada.

Os custos

  • A evolução do schema nunca termina. Eventos ficam guardados para sempre, então versões antigas precisam continuar legíveis. Planeje o upcasting (converter eventos antigos para o formato atual na leitura) desde o primeiro dia.
  • Apagar dados é difícil. Regulações que exigem exclusão conflitam com logs imutáveis; mantenha dados pessoais fora dos eventos ou criptografe-os por pessoa e apague a chave.
  • Fluxos longos ficam lentos para carregar. Snapshots do estado do agregado a cada algumas centenas de eventos resolvem isso.
  • A depuração muda de lugar. Bugs aparecem como modelos de leitura errados, e a correção é um replay, que exige ferramentas.
  • Habilidades do time. Modelar com eventos é outro jeito de pensar. Um único domínio central com event sourcing já basta; o resto do sistema pode continuar CRUD.

O que colocar no diagrama

  • Dois caminhos claramente separados para comandos e consultas.
  • O event store como fonte da verdade, com os modelos de leitura desenhados como derivados.
  • Cada projetor e seu modelo de leitura, para que quem lê veja quais visões podem atrasar.
  • Um fluxo de exemplo, com versões, para ancorar a conversa.

A mecânica de eventos e consumidores está no artigo sobre arquitetura orientada a eventos, e o padrão transactional outbox explica como publicar eventos de forma confiável quando você não usa um event store dedicado. O diagrama de arquitetura do Kafka cobre o fluxo em si.

Monte no Archboard

  1. Entre com Google ou GitHub e crie um diagrama.
  2. Adicione uma Pessoa e nós Contêiner do pack Modelo C4 para o lado de comandos, os projetores e o lado de consultas.
  3. Adicione um Banco de dados para o event store e nós Banco de dados para os modelos de leitura, além de Kafka e Redis do pack Dados para o fluxo de eventos e os contadores.
  4. Desenhe o caminho de escrita em cima e o de leitura embaixo, com os projetores alimentando os modelos de leitura pela direita.
  5. Adicione um bloco Markdown do pack Básico com um fluxo de eventos de exemplo.

O exemplo usa 10 nós de diagrama e fica dentro do plano Free.

Perguntas frequentes

CQRS e event sourcing precisam ser usados juntos? Não. O CQRS funciona com um banco de dados comum, e o event sourcing pode alimentar um único modelo de leitura. Event sourcing sem nenhuma forma de CQRS é raro, porque consultas sobre fluxos reproduzidos são lentas.

Quando não devo usar CQRS? Quando leituras e escritas usam o mesmo formato de dados, o que vale para a maioria das telas CRUD. Aplique-o nas partes do sistema em que elas divergem.

Como os modelos de leitura ficam consistentes com o event store? Eventualmente. Os projetores processam eventos em ordem e registram sua posição; depois de uma falha, retomam dali. Os clientes precisam tolerar um pequeno atraso.

Como mudo a estrutura de um evento depois? Versione os eventos e converta as versões antigas na leitura (upcasting). Nunca edite eventos gravados no lugar.

O que é um snapshot em event sourcing? Uma cópia salva do estado de um agregado em uma versão, para que carregá-lo signifique ler o snapshot mais os eventos seguintes em vez do fluxo inteiro.

Comece grátis — um assento, sem cartão de crédito.

Artigos relacionados

← Voltar ao blog

Abra um canvas em um minuto

Entre com Google ou GitHub. Grátis para um assento. Faça upgrade quando a equipe entrar.