CQRS no Node.js separa operações que alteram estado das operações que consultam dados. Comandos passam por regras do domínio e modificam aggregates. Queries usam modelos de leitura otimizados para a necessidade da interface.
A separação pode ser apenas lógica, dentro do mesmo processo e banco, ou física, com serviços e bancos distintos. Começar simples evita introduzir consistência eventual antes que ela seja necessária.
Commands e Queries
Um command expressa intenção e pode falhar por regra de negócio.
export async function confirmarPedido(command, deps) {
const pedido = await deps.pedidos.obter(command.pedidoId);
pedido.confirmar({ confirmadoEm: deps.clock.now() });
await deps.pedidos.salvar(pedido);
return { id: pedido.id, status: pedido.status };
}Uma query apenas retorna dados e não deve alterar estado.
export async function obterResumoPedido(id, db) {
const result = await db.query(
'SELECT id, status, total_cents FROM pedidos_resumo WHERE id = $1',
[id]
);
return result.rows[0] ?? null;
}Por que separar?
- modelos de escrita protegem invariantes;
- modelos de leitura podem ser desnormalizados;
- queries não precisam hidratar aggregates;
- cada lado escala de forma diferente;
- permissões e observabilidade ficam mais claras.
CQRS no mesmo banco
Uma primeira implementação pode usar tabelas normalizadas para escrita e views SQL para leitura. Não é obrigatório adotar mensageria.
Modelo de leitura separado
Quando leituras exigem joins caros ou alta escala, mantenha uma projeção.
CREATE TABLE pedidos_resumo (
id text PRIMARY KEY,
cliente_nome text NOT NULL,
status text NOT NULL,
total_cents integer NOT NULL,
atualizado_em timestamptz NOT NULL
);Um consumidor atualiza a projeção a partir de eventos.
Consistência eventual
Com projeções assíncronas, a resposta de um command pode chegar antes da atualização da query. A interface deve tolerar estado intermediário.
Uma opção é retornar dados mínimos do command e atualizar a tela quando a projeção estiver pronta.
Command Bus
export class CommandBus {
#handlers = new Map();
register(type, handler) {
this.#handlers.set(type, handler);
}
execute(command) {
const handler = this.#handlers.get(command.type);
if (!handler) throw new Error(`Handler ausente: ${command.type}`);
return handler(command);
}
}Um bus pode padronizar logs, autorização e métricas, mas não deve esconder todo fluxo.
Query Bus
O mesmo princípio pode organizar queries, embora chamadas diretas frequentemente sejam suficientes.
Validação
Valide formato na borda e regras no domínio. Um command deve ser imutável e conter apenas dados necessários.
Idempotência
Commands expostos por APIs podem receber uma idempotency key. Projeções precisam registrar eventos já processados.
Autorização
Commands e queries podem exigir permissões diferentes. Um usuário talvez possa consultar um pedido, mas não cancelá-lo.
CQRS e Event Sourcing
CQRS não exige Event Sourcing. É possível salvar estado atual e manter leitura separada. Event Sourcing também pode usar CQRS porque projeções são naturais, mas os padrões são independentes.
Leia Event Sourcing no Node.js.
CQRS e DDD
O lado de escrita combina bem com aggregates e repositories. O lado de leitura pode retornar DTOs diretamente.
Consulte Aggregates no Node.js.
Projeções reconstruíveis
Uma projeção deve ter checkpoint e processo de rebuild. Durante reconstrução, use tabela paralela ou versão para evitar indisponibilidade.
Observabilidade
Monitore latência de commands, taxa de erros de domínio, lag das projeções, tempo de rebuild, queries lentas e divergência entre modelos.
Testes
- commands com regras e concorrência;
- queries com filtros e paginação;
- projeções idempotentes;
- eventos fora de ordem;
- consistência após processamento;
- permissões separadas.
Quando usar
CQRS é útil quando leitura e escrita têm necessidades muito diferentes, o domínio de escrita é complexo ou projeções específicas simplificam consultas.
Quando evitar
Para CRUD simples, separar modelos pode duplicar código e operação sem benefício. Comece com módulos distintos e evolua quando métricas justificarem.
Erros comuns
- adotar bancos separados desde o início;
- chamar qualquer endpoint de command;
- alterar estado em query;
- hidratar aggregate para relatório;
- não comunicar consistência eventual;
- projeções sem idempotência;
- sem processo de rebuild;
- retornar modelos internos diretamente.
Conclusão
CQRS no Node.js permite modelar escrita para consistência e leitura para eficiência. A separação deve crescer conforme a necessidade: primeiro no código, depois em tabelas e somente então em infraestrutura independente.
Combine commands com aggregates, queries com DTOs e projeções com idempotência. Consulte a referência de CQRS de Martin Fowler e Event-Driven Architecture no Node.js.




