Download as pdf or txt
Download as pdf or txt
You are on page 1of 14

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 http://localhost:3000/ . 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 order.events.ts 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 app.controller.ts 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/order.events';

@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

this.eventBus.publish(

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:

this.eventBus.publish(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 order.command.ts 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 order.saga.ts 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 './order.events';


import { OrderCommand } from './order.command';
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(event.orderTransactionGUID,


event.orderUser, event.orderItem, event.orderAmount);

}),

);

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
order.handler.ts having code given below:

import { CommandHandler, EventPublisher, ICommandHandler } from


'@nestjs/cqrs';

import { OrderCommand } from './order.command';

import { ItemRepository } from 'src/item/item.repository';

@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
console.log(`Make a bid on ${orderItem}, with userID:
${orderUserGUID} amount: ${orderAmount}`);

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

const item = this.publisher.mergeObjectContext(

await this.itemRepository.getItemById(orderItem),

);

item.orderOnItem(orderTransactionGUID, orderUserGUID, orderAmount);

item.commit();

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 item.interface.ts , item.model.ts and
item.repository.ts .

Our item.interface.ts 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 item.model.ts will be like below:

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

import { IItemInterface } from './item.interface';

import { OrderEventSuccess, OrderEventFail } from


'src/order/order.events';

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

this.apply(new OrderEventSuccess(orderTransactionGUID,
this.item.id, amount, { email: 'fake@email.com', id: userID }));

} catch (e) {

// dispatch order event fail action

this.apply(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, item.repository.ts will be like below:

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

import { IItemInterface } from './item.interface';

import { ItemModel } from './item.model';

@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:

item.orderOnItem(orderTransactionGUID, orderUserGUID, orderAmount);

item.commit();

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 order.saga.ts 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

console.log('Order Placed')

return [];

}),

);

@Saga()

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


{

return events$.pipe(

ofType(OrderEventFail),

flatMap((event: OrderEventFail) => {

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

console.log('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 app.module.ts file and update like below:

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

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

import { AppController } from './app.controller';

import { OrderHandler } from './order/order.handler';

import { OrderSaga } from './order/order.saga';

import { ItemRepository } from './item/item.repository';

@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 http://localhost:3000/ 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

You might also like