0% found this document useful (0 votes)
285 views14 pages

CQRS Implementation in NestJS

Uploaded by

xu5q4v8ne
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
285 views14 pages

CQRS Implementation in NestJS

Uploaded by

xu5q4v8ne
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

To make Medium work, we log user data.

By using Medium, you agree to our Privacy Policy, including cookie policy.

You have 2 free stories left this month. Sign up and get an extra one for free.

CQRS Explained With Nest JS


We will be developing a simple CQRS app in NEST JS

Renjith P Follow
Jan 22 · 8 min read

Reactive programming is getting more popularity regardless of whether we are


developing mobile, web or back end applications. More and more design patterns are
evolving based on the changes that the IT industry adopting to deliver quality products.
CQRS is another design pattern used in
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.

microservices architecture which will have a separate


service, model, and database for insert operations in
the database
CQRS is a design pattern that stands for Command Query Responsibility Segregation.
It's a simple pattern, which enables fantastic possibilities. It simply separates the reading
part of the application from the writing part, with queries and commands.

If you are not that much familiar with CQRS, please go through the link below given:

CQRS: What? Why? How?

As I used to say before we start we have to know the benefits of onboarding CQRS

CQRS allows the read and write workloads to scale independently, and may result in
fewer lock contentions

The read side can use a schema that is optimized for queries, while the write side
uses a schema that is optimized for updates.

It’s easier to ensure that only the right domain entities are performing writes on the
data.

Segregating the read and write sides can result in models that are more
maintainable and flexible. Most of the complex business logic goes into the write
model. The read model can be relatively simple.

By storing a materialized view in the read database, the application can avoid
complex joins when querying.

Okay, it’s time to code


We are taking online shopping as our use case. Let us see how to modularize the order
placing scenario in the CQRS way.

Here is a diagram of the flow in our system:


To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.

application ow

This event-driven system enables the aspect-oriented programming paradigm. This


basically means you can add additional functionality to the software without changing
existing functionalities. In our case, it will mean chaining new commands and
command handlers with events.

We are using Nest JS as our framework that it has built-in support for CQRS.

First, you need to install Nest JS if it is not already done. Run npm i -g @nestjs/cli for
installation.

Now run nest new simple-cqrs-app to create the project. Once it is completed, get into
the project directory and run the project to see whether everything works fine.

cd simple-cqrs-app
npm run start

Then open your browser and navigate to [Link] . You will get a message

“Hello World”. Okay, we are all set with the framework.

Now open src directory and create a folder “order” where we are going to put our order
related stuff. Then create a file [Link] and put the code like below:

export class OrderEvent {


constructor(
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.
public readonly orderTransactionGUID: string,

public readonly orderUser: string,

public readonly orderItem: string,

public readonly orderAmount: number,

) { }

export class OrderEventSuccess {

constructor(

public readonly orderTransactionGUID: string,

public readonly orderItem: string,

public readonly orderAmount: number,

public readonly user: { email: string, id: string },

) { }

export class OrderEventFail {

constructor(

public readonly orderTransactionGUID: string,

public readonly error: object,

) { }

As you know, since CQRS is developed for event-driven programming every module
within the application will be communicating via generating events. So here we have
mentioned 3 possible events that are related to the order module.

Then open [Link] and replace the code with below given:

import { Controller, Get } from '@nestjs/common';


import { EventBus, QueryBus } from '@nestjs/cqrs';
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.
import * as uuid from 'uuid';

import { OrderEvent } from './order/[Link]';

@Controller()

export class AppController {

constructor(private readonly eventBus: EventBus,

private queryBus: QueryBus) { }

@Get()

async bid(): Promise<object> {

const orderTransactionGUID = uuid.v4();

// We are hard-coding values here

// instead of collecting them from a request

[Link](

new OrderEvent(

orderTransactionGUID, 'Daniel Trimson', 'Samsung LED TV',


50000),);

return { status: 'PENDING', };

Here QueryBus and EventBus are Observables. It means application modules can easily
subscribe to the whole stream and enrich communication between modules through
events.

When a user reaches app controller a new event for placing an order will be fired like
below given:

[Link](new OrderEvent(
orderTransactionGUID, 'Daniel Trimson', 'Samsung LED TV',
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.
50000),);

Let us see how this event ends up creating an order in the system.

Go to order directory and create a file [Link] and paste the code given
below:

export class OrderCommand {

constructor(

public readonly orderTransactionGUID: string,

public readonly orderUserGUID: string,

public readonly orderItem: string,

public readonly orderAmount: number,

) { }

In order to make the application easier to understand, each change has to be preceded
by Command. When any command is dispatched, the application has to react to it.
Commands can be dispatched from the services (or directly from the
controllers/gateways) and consumed in corresponding Command Handlers.

Here we have defined a command that has to be dispatched when a customer places an
order. Let us see how it will be dispatched within the system.

For that create a file [Link] and paste the code given below:

import { Injectable } from '@nestjs/common';

import { ICommand, ofType, Saga } from '@nestjs/cqrs';

import { Observable } from 'rxjs';

import { flatMap, map } from 'rxjs/operators';

import { OrderEvent, OrderEventSuccess } from './[Link]';


import { OrderCommand } from './[Link]';
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.
@Injectable()

export class OrderSaga {

@Saga()
createOrder = (events$: Observable<any>): Observable<ICommand> => {

return events$.pipe(

ofType(OrderEvent),

map((event: OrderEvent) => {

return new OrderCommand([Link],


[Link], [Link], [Link]);

}),

);

This type of Event-Driven Architecture improves application reactiveness and


scalability. Now, when we have events, we can simply react to them in various ways. The
Sagas are the last building block from the architectural point of view.

A saga can be thought of as a special event handler that returns commands. Sagas do
this by leveraging RxJS to receive and react to all events published to the event bus.

Followed by the OrderEvent saga will generate a OrderCommand and this will be handled
by a command handler. So we need to create a command handler and let it be
[Link] having code given below:

import { CommandHandler, EventPublisher, ICommandHandler } from


'@nestjs/cqrs';

import { OrderCommand } from './[Link]';

import { ItemRepository } from 'src/item/[Link]';

@CommandHandler(OrderCommand)
export class OrderHandler implements ICommandHandler<OrderCommand> {
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.
constructor(

private readonly itemRepository: ItemRepository,

private readonly publisher: EventPublisher) {}

async execute(command: OrderCommand) {

const { orderTransactionGUID , orderAmount, orderItem,


orderUserGUID } = command;

// tslint:disable-next-line:no-console
[Link](`Make a bid on ${orderItem}, with userID:
${orderUserGUID} amount: ${orderAmount}`);

// to associate model ( Order ) and publisher, we use code bellow

const item = [Link](

await [Link](orderItem),

);

[Link](orderTransactionGUID, orderUserGUID, orderAmount);

[Link]();

Here, the OrderCommand generated will be handled by OrderHandler. In order to succeed

with placing the order, the OrderHandler has to make an interaction with the item
model with which order has no direct relationship as per CQRS terms. So how do we
associate the model and the publisher? We need to use a publisher
mergeObjectContext() method inside our command handler.

So here we are using an instance of ItemRepository which will be having the item we are
looking for as the parameter of mergeObjectContext() to place the order.

Let us create the the item folder having files [Link] , [Link] and
[Link] .

Our [Link] will look like this


To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.
export interface IItemInterface {

id: string;

amount?: number;

And [Link] will be like below:

import { AggregateRoot } from '@nestjs/cqrs';

import { IItemInterface } from './[Link]';

import { OrderEventSuccess, OrderEventFail } from


'src/order/[Link]';

export class ItemModel extends AggregateRoot {

constructor(private readonly item: IItemInterface) {

super();

orderOnItem(orderTransactionGUID: string, userID: string, amount:


number) {

// validation and etc.

try {

// business logic

// upon successful order, dispatch new event

[Link](new OrderEventSuccess(orderTransactionGUID,
[Link], amount, { email: 'fake@[Link]', id: userID }));

} catch (e) {

// dispatch order event fail action

[Link](new OrderEventFail(orderTransactionGUID, e));

}
}
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.

Finally, [Link] will be like below:

import { Injectable } from '@nestjs/common';

import { IItemInterface } from './[Link]';

import { ItemModel } from './[Link]';

@Injectable()

export class ItemRepository {

async getItemById(id: string) {


// fetch it from database for example
const item: IItemInterface = {
id,
amount: 50000,

};

return new ItemModel(item);

async getAll() {

return [];

So ItemRepository will get the item for OrderHandler to complete the order like below:

[Link](orderTransactionGUID, orderUserGUID, orderAmount);

[Link]();

Now everything works as expected. Notice that we need to commit() events since they're
not being dispatched immediately.
To makeSo once work,
Medium we log, user
commit() as we know
data. By using method
Medium, you agree
orderOnItem() to ourwill generate
Privacy any onecookie
Policy, including of thepolicy.
two
events — OrderEventSuccess or OrderEventFail.

So we need to handle the event in order to end the order placing cycle as we expected.

Go to [Link] and add these two sagas there.

@Saga()

createOrderSuccess = (events$: Observable<any>): Observable<ICommand>


=> {

return events$.pipe(

ofType(OrderEventSuccess),

flatMap((event: OrderEventSuccess) => {

// tslint:disable-next-line:no-console

[Link]('Order Placed')

return [];

}),

);

@Saga()

createOrderFail = (events$: Observable<any>): Observable<ICommand> =>


{

return events$.pipe(

ofType(OrderEventFail),

flatMap((event: OrderEventFail) => {

// tslint:disable-next-line:no-console

[Link]('Order Placing Failed')

return [];

}),

);
}
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.

So if the order is a success you will get the “Order Placed” message in the console,
otherwise “Order Placing Failed”.

Finally, go to [Link] file and update like below:

import { Module } from '@nestjs/common';

import { CqrsModule } from '@nestjs/cqrs';

import { AppController } from './[Link]';

import { OrderHandler } from './order/[Link]';

import { OrderSaga } from './order/[Link]';

import { ItemRepository } from './item/[Link]';

@Module({

imports: [CqrsModule],

controllers: [AppController],

providers: [

OrderHandler,

OrderSaga,

ItemRepository],

})

export class AppModule { }

We are all set with our CQRS app. Below given will be your app structure now.
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy, including cookie policy.

From the project root directory run npm run start.

Open your browser and navigate to [Link] and go to the terminal to


see whether you got “Order Placed” message like given below:

As expected we got the “Order Placed” message.

You can find the full source code from here.

Conclusion
Here we have done only the write part with CQRS. We will be adding the read side as
part 2 of this article because lengthy stories may be boring.

Happy Coding :)

Reference

CQRS: What? Why? How?

CQRS: Nest JS
Nestjs work,
To make Medium JavaScript Software
we log user data. ByEngineering
using Medium,Software Development
you agree Programming
to our Privacy Policy, including cookie policy.

About Help Legal

Get the Medium app

Common questions

Powered by AI

A significant challenge when implementing the write model in CQRS is ensuring consistency and maintaining the integrity of complex business rules. This can be addressed by encapsulating business logic within the write model and using transaction mechanisms to handle data consistency. Additionally, employing Sagas can help manage distributed transactions by sequencing events and commands across multiple models, maintaining the correct state of the system in a distributed environment .

In a CQRS pattern, Commands are distinct from traditional command-driven applications because they are explicitly separated from queries and used only to trigger changes in the state of the system. Each Command represents a specific operation with its associated business logic, often handled by a Command Handler. In contrast, traditional applications may intertwine commands and queries, leading to tightly coupled logic that can be harder to maintain and extend. CQRS, however, promotes the separation of concerns, increasing the system's clarity and flexibility .

A typical use case for employing CQRS in an online shopping application involves separating the order processing logic (write side) from the product catalog and order queries (read side). This separation allows for optimization on each side: complex order validation and processing are handled independently of the simpler, often highly-optimized querying logic, enabling better scalability, maintainability, and performance tuning specific to each operation .

The EventBus in a Nest JS application using CQRS serves as the backbone for the communication between different components of the system in an event-driven architecture. It allows modules to subscribe to and publish events, facilitating communication through Observable streams. For example, when an order event is published on the EventBus, various components such as sagas or event handlers can react to it by listening for specific types of events and executing commands accordingly .

Decoupling the read and write models in CQRS is important because it allows each to be optimized for its specific concerns—writes can focus on updating domain logic while reads can focus on aggregation and presentation of data. This separation enhances system flexibility and maintainability as each model can evolve independently and be scaled according to its unique demands. In complex application architectures, this results in more efficient resource utilization and easier management of concurrent workloads .

Sagas in CQRS handle complex transactions by acting as coordinators that manage the control flow of events and commands. They can react to multiple events, execute various business processes, and ensure that all parts of a distributed transaction are completed successfully. In the context of Nest JS, Sagas use RxJS to handle streams of events and transform them into commands. They listen for events such as order creation and generate corresponding commands like OrderCommand, which are then handled by Command Handlers to complete the transaction process .

The main advantage of using the CQRS (Command Query Responsibility Segregation) design pattern is that it allows the read and write workloads to scale independently. This can result in fewer lock contentions, as the read side can use a schema optimized for queries while the write side uses a schema optimized for updates. This segregation leads to models that are more maintainable and flexible, enabling complex business logic in the write model and simplifying the read model .

The mergeObjectContext method in a Nest JS application using CQRS is used to bind a model object to the event publishing mechanism. This method integrates the model and the event lifecycle so that the model can directly generate and commit domain-specific events when state changes occur. It facilitates the publishing of events from within the model, ensuring that any state transitions are tracked and properly handled, as seen in actions such as placing an order or updating inventory .

The event-driven architecture improves the scalability of an application by decoupling event producers from event consumers. This means that an application can easily scale due to its asynchronous nature, allowing parts of the system to be updated and extended without tightly coupling them. Events are processed as they occur, enabling different parts of the system to operate independently and scale according to demand .

The separation of command and query responsibilities in a cloud-based application architecture impacts the development process by streamlining the scalability, flexibility, and maintainability of the application. On the architecture side, it enables optimized resource allocation, as command operations can be processed in isolation and scaled independently from read operations. This is crucial for cloud environments where scaling resources efficiently is both a performance and cost consideration. During development, it allows teams to focus on specific parts of the application's functionality without the complexity of intertwined operations, leading to clearer, more maintainable code .

You might also like