Introduction
CQRS (Command Query Responsibility Segregation) is a software design pattern which separates the responsibility of reading and writing data in system. In traditional design pattern often, same set of components are used for reading and writing of data. CQRS proposes the segregation of these responsibilities by introducing two separate models
- Command Model: The command model is responsible for processing and validating commands, updating the data store, and triggering events.
- Query Model: The Query Model handles operations to retrieve data from the system.
CQRS Have mainly four key concepts as below
- Command: Event or request to change the state of system
- Query: Event or request to retrieve data from system
- Command Handler: A component responsible for handling commands and updating the state of the system.
- Query Handler: A component responsible for handling queries and retrieving data from the system.
CQRS Provides Benefits such as
- Scalability: Application can be scalable as it supports different model for read and write data.
- Flexibility: It allows utilization of separate data stores optimized for reading and writing operations.
- Performance: Application performance can be improved.
Nest JS allows to use this CQRS Model, we will learn how to implement that in Nest JS in this article.
We will take example of creating and fetching TODO application with the help of CQRS pattern.
TODO application with CQRS pattern
If nest cli is not installed globally in your system you need to install it using command
npm i -g @nestjs/cli
Create new Nest Js project if not created already using below command
nest new todo-application
Dependency Installation
Nest JS provides a package to implement CQRS, we need to install the package first using below command
npm install --save @nestjs/cqrs
Module Creation
Create new Module for our TODO application using command
nest generate module todo
Commands
Commands are used to change the application state, when a command is triggered, it is handled by corresponding command handler. Then handler will be responsible to process the operation. Every command will have command handler in order to process the command
Create new file inside todo
module with name create-todo-command.ts
and implement the below code
import { ICommand } from '@nestjs/cqrs';
export class CreateToDoCommand implements ICommand {
constructor(
public readonly title: string,
public readonly description: string,
) {}
}
Command Handler
Create new file inside todo
module with name create-todo-command-handler.ts
and implement the below code
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreateToDoCommand } from './create-todo-command';
@CommandHandler(CreateToDoCommand)
export class CreateToDoHandler implements ICommandHandler<CreateToDoCommand> {
async execute(command: CreateToDoCommand): Promise<void> {
// Add Logic to do validation and business rule
const { title, description } = command;
// Use Repository to save directly or Create Factory to add business logic and save
}
}
Query
Queries are used to retrieve the data from the application, when a query is requested, Query handler handles the requests and retrieves the data. Every query will have query handler
Create new file inside todo
module with name get-todo-query.ts
and implement the below code
import { IQuery } from '@nestjs/cqrs';
export class GetToDoQuery implements IQuery {
constructor() {}
}
Query Handler
Create new file inside todo
module with name get-todo-query-handler.ts
and implement the below code
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { GetToDoQuery } from './get-todo-query';
@QueryHandler(GetToDoQuery)
export class GetToDoQueryHandler implements IQueryHandler<GetToDoQuery> {
async execute(query: GetToDoQuery): Promise<any> {
// Fetch data using repository or factory and return it
// Sample Response
return [
{ id: 1, title: 'Test', description: 'Reminder to complete daily activity' },
{ id: 2, title: 'Test 2', description: 'Reminder to complete daily activity2' },
];
}
}
Module
In the todo.module.ts
file, import the CQRS module to use the command handlers and query handlers
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { ToDoController } from './todo.controller';
import { CreateToDoHandler } from './create-todo-command-handler';
import { GetToDoQueryHandler } from './get-todo-query-handler';
@Module({
imports: [CqrsModule],
controllers: [ToDoController],
providers: [CreateToDoHandler, GetToDoQueryHandler],
})
export class ToDoModule {}
Controller
Create todo.controller.ts
file to handle the API request and use command and query bus
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { CreateToDoCommand } from './create-todo-command';
import { GetToDoQuery } from './get-todo-query';
@Controller('ToDo')
export class ToDoController {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus,
) {}
@Post()
async createToDo(@Body() body: { title: string, description: string }): Promise<void> {
const { title, description } = body;
await this.commandBus.execute(new CreateToDoCommand(title, description));
}
@Get()
async getToDo(): Promise<any[]> {
return this.queryBus.execute(new GetToDoQuery());
}
}
Pro Tips
You can create a separate folder for command and query to segregate the code and make it more readable. Also, here I just gave the basic high level demo, but in your application, you can create dto for command and query, and you can directly use that dto
Conclusion
In conclusion, while CQRS can provide advantages in terms of scalability and flexibility, it comes with increased complexity and potential challenges in terms of consistency and development overhead. It is important to carefully consider whether the benefits align with the specific requirements and goals of the application being developed.