ArquiteturaAvançado9 min de leitura8 seçõesc. 2010, As fontes divergem sobre a data

CQRS (Command Query Responsibility Segregation)

Separa o modelo de escrita (commands) do modelo de leitura (queries), podendo usar stores e serviços distintos otimizados para cada lado.

Em 10 segundos

Escrever e ler são trabalhos diferentes: comandos passam por regras e vão para o modelo de escrita; consultas leem read models desnormalizados, atualizados por projeções — geralmente com consistência eventual.

Antes de ter nome

É a separação de CQS (Meyer, 1988) levada da assinatura do método para a arquitetura inteira — dois modelos, não só dois tipos de método.

  • #cqs
  • #event-sourcing
  • #escala
  • #read-model
Analogia

A cozinha e o salão do restaurante

O garçom anota o pedido e o entrega à cozinha (command side): lá existem regras, ordem e controle. O salão consulta o painel de 'pedidos prontos' (read model): uma visão simples, feita para ser lida de relance. Ninguém entra na cozinha para saber se o prato saiu — e o painel atualiza alguns segundos depois do fogão (consistência eventual).

01Conceito

O problema

Um único modelo servindo leitura e escrita é um mau compromisso para os dois lados: as regras de escrita pedem normalização, invariantes e consistência forte; as telas de leitura pedem dados agregados, desnormalizados e rápidos.

E os dois lados escalam diferente — quase sempre há muito mais leituras. Presos ao mesmo modelo e à mesma base, otimizar um tende a piorar o outro.

Os sintomas aparecem cedo: joins caros para montar telas, caches invalidados a cada escrita, índices de leitura que deixam o INSERT lento, e um ORM tentando servir dois senhores.

Não confunda com CQS. CQS (Bertrand Meyer) é um princípio de nível de método: um método ou é comando (muda estado) ou é query (retorna dado) — nunca ambos. CQRS eleva a mesma ideia à arquitetura: modelos, serviços e possivelmente bancos inteiros separados por responsabilidade. CQS é sempre saudável e de graça; CQRS é uma decisão arquitetural com custo real.

O preço principal do CQRS é a consistência eventual: entre a escrita e a projeção existe uma janela em que a leitura está desatualizada. Todo o design de UX e de contratos precisa assumir essa janela — de 'seu pedido está sendo processado' a versões/etags.

Como se encaixa
  1. Comando
  2. valida regrasWrite model
  3. publicaEvento / Projeção
  4. atualizaRead model
  5. serveConsulta
O caminho da escrita e o da leitura só se encontram na projeção — cada lado otimizado para o próprio trabalho.
02Interativo

Veja a consistência eventual acontecendo

Mexa nos controles: a teoria acima vira comportamento observável.

Command side (escrita)

Write store: 0 pedidos

Query side (leitura)

Read model: 0 pedidos

O comando muda o write store na hora; o read model é atualizado pela projeção. Por isso a leitura pode ficar um instante atrás (consistência eventual).

03Anatomia

Navegue pelos lados

Camada 1/3Command side (escrita)

Camada 1

Command side (escrita)

intenções passam por regras

Recebe comandos ('criar pedido', 'alterar endereço'), valida invariantes de negócio e persiste no write store — normalizado e consistente. É o único lugar do sistema autorizado a mudar estado.

No código

commandHandler.handle(new CriarPedido(itens));

Se violar: se a tela escrever direto no banco, as invariantes deixam de valer e o read model diverge sem ninguém perceber.

04Estrutura

Fluxo completo (sequência)

Diagrama de classes
05Implementação

Código

// Command side: recebe intenção, valida e persiste
interface Command { tipo: string; }
class CriarPedido implements Command {
  readonly tipo = "CriarPedido";
  constructor(public itens: string[]) {}
}

class CommandHandler {
  constructor(private writeStore: WriteStore, private bus: EventBus) {}
  async handle(cmd: CriarPedido): Promise<void> {
    const pedido = Pedido.novo(cmd.itens);      // regras de negócio
    await this.writeStore.salvar(pedido);
    this.bus.publicar({ tipo: "PedidoCriado", pedido });
  }
}

// Query side: lê de um read model desnormalizado
class ConsultaPedidos {
  constructor(private readStore: ReadStore) {}
  porCliente(clienteId: string) {
    return this.readStore.listar(clienteId);    // sem regras, só leitura
  }
}
06No mundo real

Casos de uso

Dashboard pesado sobre operação transacional

Cenário

Um marketplace tem checkout transacional e um painel do vendedor com agregações (vendas por dia, top produtos, funil).

Como o padrão entra

As escritas seguem no modelo normalizado; projeções alimentam tabelas agregadas por painel. O dashboard lê pré-calculado em milissegundos, sem competir com o checkout.

Trade-off: Os números do painel podem atrasar alguns segundos — aceitável para analytics, inaceitável para saldo; escolha por visão.

Busca com tecnologia dedicada

Cenário

O catálogo vive em SQL, mas a busca precisa de relevância, facetas e typo-tolerance.

Como o padrão entra

Eventos de produto projetam para um índice (ex.: OpenSearch/Meilisearch). O command side não sabe que a busca existe; o read model é um índice inteiro em outra tecnologia.

Trade-off: Mais uma peça para operar e reindexar; o replay de eventos vira a ferramenta de reconstrução.

Auditoria e Event Sourcing

Cenário

Um sistema financeiro precisa provar como cada saldo chegou ao valor atual.

Como o padrão entra

Com CQRS o passo para Event Sourcing é curto: o write store passa a ser o log de eventos, e todos os read models (saldo, extrato, relatórios) são projeções reconstruíveis do log.

Trade-off: Event Sourcing amplifica custos (versionamento de eventos, replay, GDPR) — só vale onde o histórico é requisito, não luxo.

07Cuidado

Armadilhas

  1. CQRS em CRUD simples

    Duplicar modelos, handlers e projeções para um cadastro básico só multiplica arquivos. Se um modelo único serve bem os dois lados, aplique apenas CQS nos métodos e siga em frente.

  2. Fingir que a leitura é imediata

    UI que lê logo após escrever e espera ver o dado novo quebra com projeção assíncrona. Desenhe para a janela: confirme com o resultado do comando, use 'processando…', ou leia a própria resposta da escrita.

  3. Read model como cópia 1:1

    Projetar o read model idêntico ao write model desperdiça o padrão — o ganho vem de moldar cada visão à tela que a consome. Cópia espelhada é só replicação com passos extras.

08Decisão

Quando usar × quando evitar

Use quando

3
  • Leitura e escrita têm requisitos de modelo, carga ou escala muito diferentes.
  • Há muitas visões de leitura distintas sobre os mesmos dados de escrita.
  • Combina com Event Sourcing, integração por eventos ou domínios complexos (DDD).

Evite quando

2
  • CRUDs e domínios simples — a duplicação de modelos vira complexidade sem retorno.
  • Quando toda leitura precisa refletir a escrita imediatamente (consistência forte ponta a ponta).

Testar o que ficou

3 armadilhas de CQRS (Command Query Responsibility Segregation) viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • réplica de leituraEscritas vão para o primário e as consultas para réplicas: dois caminhos com modelos e escala próprios para ler e para gravar.
  • Elasticsearch ao lado do bancoO banco transacional guarda a verdade das escritas e um índice de busca serve as leituras — modelos de leitura e escrita separados de fato.
  • materialized viewsA view materializada é um modelo de leitura pré-computado, alimentado a partir das escritas e otimizado só para consultar rápido.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Modelo de escrita ≠ modelo de leitura.
await comandos.criarPedido(cmd);
const view = await leituras.pedidoDetalhe(id);

O que ele cobra

2 saltos a mais entre a chamada e o efeito.

  • Dois modelos a manter, de escrita e de leitura, que podem divergir enquanto sincronizam
  • A leitura passa a ser eventualmente consistente em relação à escrita

Não vale se: leitura e escrita cabem no mesmo modelo sem se atrapalhar — separar aí dobra o trabalho sem resolver gargalo nenhum.

Costuma ser confundido com

Onde isto aparece

Continue por aqui