Se você já trabalha com desenvolvimento backend ou está começando agora a se aprofundar em arquitetura de software, provavelmente já ouviu falar em CQRS (Command Query Responsibility Segregation). O nome pode assustar à primeira vista, mas a ideia por trás desse padrão é mais simples do que parece — e faz muito sentido quando lidamos com aplicações que crescem e ficam mais complexas com o tempo.
De forma bem direta, o CQRS propõe separar operações de escrita (commands) das operações de leitura (queries). Em vez de usar o mesmo modelo, a mesma lógica e, muitas vezes, a mesma estrutura para tudo, você passa a tratar cada responsabilidade de forma isolada. Isso ajuda a deixar o código mais organizado, mais fácil de manter e, em muitos casos, mais performático.
Em aplicações tradicionais, é comum usar o mesmo serviço e as mesmas entidades tanto para salvar dados quanto para buscá-los. Funciona bem no começo, mas conforme o sistema evolui, surgem regras complexas, validações específicas, otimizações de leitura e integrações externas. É aí que o CQRS começa a brilhar.
No ecossistema Node.js, especialmente com NestJS, o CQRS se encaixa muito bem. O próprio framework oferece um módulo oficial (@nestjs/cqrs) que facilita a implementação do padrão, seguindo boas práticas e conceitos bem alinhados com DDD (Domain-Driven Design). Mesmo que você não use DDD formalmente, dá para aplicar CQRS de forma gradual e pragmática.
Neste artigo, vamos conversar com calma sobre o que é CQRS, quando faz sentido usar, quando evitar, e como aplicá-lo na prática com exemplos em NestJS. A ideia aqui não é complicar, mas clarear. Se você já sentiu que seu código backend está ficando confuso demais, esse padrão pode ser exatamente o que você estava procurando.
O que é CQRS na prática?
Agora que já entendemos a ideia geral, vamos descer um pouco mais para o nível prático. CQRS (Command Query Responsibility Segregation) nada mais é do que um padrão arquitetural que separa claramente duas responsabilidades dentro da aplicação:
- Commands: tudo o que altera o estado do sistema
- Queries: tudo o que consulta dados, sem modificar nada
Essa separação é conceitual antes de ser técnica. Ou seja, você não começa criando dezenas de pastas ou camadas só porque ouviu falar de CQRS. Primeiro, você muda a forma de pensar: “isso aqui grava dados ou apenas lê?”.
Um command representa uma intenção de mudança. Exemplos simples:
- Criar um usuário
- Atualizar um perfil
- Cancelar um pedido
Já uma query representa apenas uma leitura:
- Buscar um usuário pelo ID
- Listar pedidos
- Exibir um dashboard
No modelo tradicional (CRUD), tudo isso costuma passar pelo mesmo serviço, usando as mesmas entidades e, muitas vezes, o mesmo modelo de dados. O CQRS quebra esse acoplamento. Commands e queries podem ter modelos diferentes, validações diferentes e até bancos de dados diferentes, se fizer sentido.
É importante deixar claro: CQRS não significa obrigatoriamente dois bancos de dados. Esse é um erro comum. Você pode aplicar CQRS usando o mesmo banco, a mesma tabela e até o mesmo ORM. A separação começa no código, na responsabilidade e na clareza.
Para quem está começando, pense assim:
- Commands focam em regras de negócio
- Queries focam em performance e simplicidade de leitura
Quando usar (e quando não usar) CQRS
Antes de sair aplicando CQRS em todo projeto, vale um alerta bem honesto: CQRS não é bala de prata. Ele resolve problemas reais, mas também adiciona complexidade. Saber quando usar é tão importante quanto saber como usar.
Quando CQRS faz sentido
O CQRS começa a fazer muito sentido quando sua aplicação apresenta alguns destes sinais:
1. Regras de negócio complexas
Se suas operações de escrita têm muitas validações, fluxos, efeitos colaterais ou dependem de outros contextos, separar commands ajuda bastante. Cada command representa uma ação clara do negócio, o que deixa o código mais legível e testável.
2. Leitura e escrita têm necessidades diferentes
Em muitos sistemas, as leituras são muito mais frequentes que as escritas. Com CQRS, você pode otimizar queries sem se preocupar em quebrar regras de negócio. Às vezes, uma query simples com JOIN ou até uma view materializada resolve melhor do que reaproveitar entidades cheias de lógica.
3. Código difícil de manter
Quando um service começa a ter métodos demais, com responsabilidades misturadas, é um forte sinal de que separar commands e queries pode ajudar a organizar a casa.
4. Aplicações que crescem com o tempo
Projetos que começam simples, mas têm previsão de crescimento, costumam se beneficiar do CQRS, principalmente se você já pensa em arquitetura desde cedo.
Quando evitar CQRS
Agora a parte que quase ninguém fala, mas é essencial:
1. CRUD simples
Se seu sistema é basicamente um CRUD sem regras complexas, CQRS pode ser exagero. Um service bem organizado resolve melhor.
2. Times pequenos ou iniciantes
CQRS exige disciplina e entendimento do padrão. Se o time ainda está aprendendo conceitos básicos de backend, pode virar confusão.
3. Overengineering
Aplicar CQRS só porque “é padrão famoso” geralmente gera mais problemas do que soluções.
Resumo honesto: use CQRS quando a complexidade justificar.
CQRS no NestJS: como o framework facilita tudo
Um dos motivos pelos quais o CQRS se tornou tão popular no ecossistema Node.js é o NestJS. O framework já nasce com uma mentalidade fortemente inspirada em boas práticas de arquitetura, e isso inclui suporte oficial ao CQRS por meio do pacote @nestjs/cqrs.
Isso é importante porque o NestJS não força você a “inventar moda”. Ele oferece abstrações prontas e bem definidas para trabalhar com commands, queries, handlers e até events, mantendo o código organizado e previsível.
O módulo @nestjs/cqrs
Para começar a usar CQRS no NestJS, o primeiro passo é instalar o módulo:
npm install @nestjs/cqrs
Depois, você importa o CqrsModule no seu módulo principal ou no módulo de domínio:
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
@Module({
imports: [CqrsModule],
})
export class UsersModule {}
A partir daqui, o NestJS passa a entender conceitos como:
- CommandBus
- QueryBus
- CommandHandlers
- QueryHandlers
Esses componentes são responsáveis por despachar e executar as ações corretas, sem que você precise acoplar tudo manualmente.
Separando responsabilidades na prática
No CQRS com NestJS, você normalmente terá:
- Um Controller, que recebe a requisição HTTP
- Um Command ou Query, que representa a intenção
- Um Handler, que executa a lógica
O controller não sabe como a lógica funciona. Ele apenas envia um command ou uma query para o bus. Isso deixa sua camada de entrada (HTTP, GraphQL, fila, etc.) completamente desacoplada da regra de negócio.
Essa abordagem melhora muito a testabilidade, já que cada handler pode ser testado de forma isolada, sem depender do framework inteiro.
Exemplo prático de CQRS com NestJS
Agora vamos colocar a mão na massa com um exemplo simples e realista. A ideia aqui não é criar uma arquitetura perfeita, mas mostrar como o CQRS funciona na prática, de forma que você consiga adaptar para o seu projeto.
Imagine um caso bem comum: criar e buscar usuários.
Criando um Command
Um command representa uma intenção de mudança no sistema. No nosso caso: criar um usuário.
export class CreateUserCommand {
constructor(
public readonly name: string,
public readonly email: string,
) {}
}
Esse arquivo não tem lógica nenhuma. Ele apenas carrega os dados necessários para executar a ação. Isso é proposital.
Criando o CommandHandler
Agora criamos o handler responsável por executar esse command:
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreateUserCommand } from './create-user.command';
@CommandHandler(CreateUserCommand)
export class CreateUserHandler
implements ICommandHandler<CreateUserCommand> {
async execute(command: CreateUserCommand) {
const { name, email } = command;
// Aqui entrariam validações e regras de negócio
// Ex: verificar se o email já existe
return {
id: 1,
name,
email,
};
}
}
Perceba como a regra de negócio fica concentrada no handler. Ele não sabe nada sobre HTTP, controllers ou rotas. Isso deixa tudo mais limpo.
Criando uma Query
Agora vamos criar uma query para buscar um usuário:
export class GetUserByIdQuery {
constructor(public readonly id: number) {}
}
Queries seguem o mesmo conceito: simples, diretas e sem lógica interna.
Criando o QueryHandler
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { GetUserByIdQuery } from './get-user-by-id.query';
@QueryHandler(GetUserByIdQuery)
export class GetUserByIdHandler
implements IQueryHandler<GetUserByIdQuery> {
async execute(query: GetUserByIdQuery) {
const { id } = query;
// Simulando uma busca no banco
return {
id,
name: 'Usuário Exemplo',
email: '[email protected]',
};
}
}
Aqui fica bem claro: queries não alteram estado. Elas apenas retornam dados.
Conectando o Controller ao CQRS
Até agora, criamos commands, queries e seus respectivos handlers. Falta entender como tudo isso se conecta ao mundo real, ou seja, às requisições HTTP. É aqui que o Controller entra, e o CQRS mostra mais um dos seus pontos fortes: o desacoplamento.
No NestJS, o controller não precisa conhecer a regra de negócio. Ele apenas recebe a requisição e encaminha a intenção correta para o CommandBus ou QueryBus.
Exemplo de Controller usando CQRS
import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { CreateUserCommand } from './commands/create-user.command';
import { GetUserByIdQuery } from './queries/get-user-by-id.query';
@Controller('users')
export class UsersController {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus,
) {}
@Post()
async create(@Body() body: { name: string; email: string }) {
return this.commandBus.execute(
new CreateUserCommand(body.name, body.email),
);
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.queryBus.execute(
new GetUserByIdQuery(Number(id)),
);
}
}
Repare como o controller ficou simples:
- Não tem validação pesada
- Não tem regra de negócio
- Não acessa diretamente o banco
Ele apenas traduz a requisição HTTP em uma intenção clara: criar ou buscar um usuário.
Organização de pastas
Um exemplo comum de estrutura usando CQRS no NestJS seria:
users
├── commands
│ ├── create-user.command.ts
│ └── create-user.handler.ts
├── queries
│ ├── get-user-by-id.query.ts
│ └── get-user-by-id.handler.ts
├── users.controller.ts
├── users.module.ts
Essa organização deixa muito claro o papel de cada arquivo, o que ajuda bastante quando o projeto cresce ou quando outra pessoa entra no time.
Vantagens e desvantagens do CQRS
Até aqui, o CQRS parece resolver muitos problemas, mas como qualquer padrão arquitetural, ele tem prós e contras. Entender esse equilíbrio é essencial para tomar boas decisões técnicas e evitar frustrações no futuro.
Vantagens do CQRS
1. Código mais organizado e legível
Separar leitura e escrita deixa as responsabilidades muito claras. Você sabe exatamente onde procurar quando algo envolve regra de negócio (commands) ou apenas leitura de dados (queries).
2. Facilidade para evoluir o sistema
Com CQRS, fica mais simples adicionar novas regras, novos comandos ou novas consultas sem quebrar o que já existe. Isso ajuda muito em sistemas que crescem com o tempo.
3. Melhor testabilidade
Handlers são fáceis de testar de forma isolada. Você consegue validar a lógica de negócio sem precisar subir servidor, controller ou infraestrutura completa.
4. Flexibilidade nos modelos de dados
Você não fica preso a um único modelo para tudo. Queries podem retornar DTOs específicos, otimizados para a tela ou para a API, sem poluir entidades de domínio.
5. Integração natural com DDD
Para quem usa ou pretende usar Domain-Driven Design, CQRS encaixa perfeitamente nos conceitos de domínio, casos de uso e linguagem ubíqua.
Desvantagens do CQRS
1. Mais arquivos e mais camadas
Para sistemas pequenos, o CQRS pode parecer verboso. O número de arquivos aumenta, e isso pode assustar quem está começando.
2. Curva de aprendizado
É preciso entender bem o padrão para usá-lo corretamente. Sem disciplina, o projeto pode virar apenas um “CRUD disfarçado”.
3. Complexidade desnecessária em projetos simples
Aplicar CQRS em um sistema simples pode ser overengineering. Às vezes, um service bem estruturado resolve melhor.
A chave aqui é o equilíbrio. CQRS é uma ferramenta, não uma regra.
Conclusão: CQRS é sobre clareza, não sobre complexidade
Depois de entender o conceito, os exemplos e os prós e contras, fica mais fácil enxergar o CQRS pelo que ele realmente é: um padrão para trazer clareza e organização quando a complexidade começa a aparecer. Ele não existe para deixar o sistema mais “arquiteturalmente bonito”, mas para resolver problemas reais de manutenção, crescimento e entendimento do código.
Se você está começando agora, não encare o CQRS como uma obrigação. Muitos sistemas funcionam muito bem sem ele. Mas, à medida que o projeto cresce, as regras de negócio se multiplicam e os services começam a ficar confusos, separar commands e queries pode ser um divisor de águas.
O NestJS ajuda bastante nesse processo. O suporte oficial ao CQRS reduz o atrito e deixa a implementação mais natural, sem exigir soluções caseiras ou complexas demais. Dá para começar pequeno, aplicando CQRS em um único módulo, e evoluir aos poucos conforme o time e o sistema amadurecem.
A recomendação mais honesta é: observe seu código. Se você sente dificuldade para entender onde as regras vivem, se cada mudança quebra algo inesperado ou se a leitura e a escrita têm necessidades muito diferentes, talvez o CQRS seja exatamente o próximo passo.
Agora quero saber de você:
Você já usou CQRS em algum projeto real? Funcionou bem ou trouxe mais complexidade do que valor? Deixa seu comentário aqui no CulturaDev e vamos trocar experiências.