@leivaa/simple-cqrs
TypeScript icon, indicating that this package has built-in type declarations

1.0.9 • Public • Published
SimpleCQRS's logo

A simple lightweight CQRS library inspired in the nestjs/cqrs module


Table of contents

About

simple-cqrs is a package that aims to bring CQRS to its simplest. Its written using Typescript, so it already brings all the d.types.

The package aims to give a smooth programming experience, and its inspired in frameworks like angular and nestjs.

  • Bases

    Command Query Responsability Segregation is a design pattern that aims to keep the infrastructure handler and the service handler decoupled.

Getting Started

All the code for this example is public on the leivaa21/simple-cqrs-express-example repository.

  • Prerequisites

    • Made using:

      • Node v18.3.0
      • NPM v8.5.5
      • Typescript v4.7.3

      so these are the recomended versions.

    • tsconfig.json has to have the property

        "experimentalDecorators": true,

      for being able to use decorators (needed in this package)

    • for installing this package in your npm project, use: npm install @leivaa/simple-cqrs

  • How to use

    • Queries

      Query and Handler structure

      Keep in mind that Queries are meant to return information from our application BUT are not meant to modify the status quo of our application itself.

      getUser.query.ts

      export default class GetUserQuery {
        constructor(public readonly id: string) {}
      }

      getUser.handler.ts

      const config: QueryHandlerConfig = {
        Query: GetUserQuery,
        Dependencies: [InMemoryUserRepository],
      };
      
      @QueryHandler(config)
      export default class GetUserQueryHandler
        implements IQueryHandler<GetUserQuery, GetUserResponse>
      {
        constructor(private readonly repository: UserRepository) {}
        async handle(query: GetUserQuery): Promise<GetUserResponse> {
          //Your code
        }
      }
      • Its recomendend to use abstractions in the constructor, but "Dependencies" in the handler config should always have a implementation class!
      • The implementations given in "Dependencies" should always be marked as "Injectable" (More info down below!)

      Using the query bus

      Here you have an example of controller following the same example as the one given above. This example uses Express, but the framework doesnt matter, and it would work in vanilla node too ie.

      getUser.controller.ts

      export default async function getUserController(
        req: Request,
        res: Response
      ) {
        //Some code here
        const QueryResponse = await QueryBus.dispatch<
          GetUserQuery,
          GetUserResponse
        >(new GetUserQuery(id));
        //Some code more
      }
    • Commands

      Command and Handler structure

      This is pretty much the same as in the query side, only changing Query for Command!

      Keep in mind that Commands are meant to modify the status quo of our application BUT are not meant to return nothing.

      createUser.command.ts

      export default class CreateUserCommand {
        constructor(public readonly id: string, public readonly name: string) {}
      }

      createUser.handler.ts

      const options: CommandHandlerConfig = {
        Command: CreateUserCommand,
        Dependencies: [InMemoryUserRepository, UserFactory],
      };
      
      @CommandHandler(options)
      export default class CreateUserCommandHandler
        implements ICommandHandler<CreateUserCommand>
      {
        constructor(
          private readonly repository: UserRepository,
          private readonly factory: UserFactory
        ) {}
        async handle(command: CreateUserCommand): Promise<void> {
          //Our code here
        }
      }

      Its important to keep in mind that "Dependencies" in the handler config should always keep the same order as in the constructor.

      Using the query bus

      getUser.controller.ts

      export default async function createUserController(
        req: Request,
        res: Response
      ) {
        //Some code here
        await CommandBus.dispatch<CreateUserCommand, void>(
          new CreateUserCommand(id, name)
        );
        //Some more code here
      }

      CommandBus should always use the return type void as handlers are meant to not return nothing.

    • Injectables

      Injecting dependencies brings a lot of decoupling to our apps, as long as we only depends on abstractions and not implementations of our services.

      Keeping with the same example, this would be how we create an Injectable:

      UserFactory.ts

      const config: InjectableConfig = {
        Dependencies: [],
      };
      
      @Injectable(config)
      export default class UserFactory {
        create(id: string, name: string): User {
          //your code
        }
      }

      Dependencies in the config works just like dependencies in hanlders. So its possible to inject dependencies to injectable objects.

    • CQRS Module

      Its important to know that the cqrs module has to be initialized in some way, and all the Handlers & Injectables have to get loaded. So its important to set up the CQRS Module in a space of code that will be loaded at the same time as our application start.

      cqrs.module.ts

      const config: CQRSModuleConfig = {
        QueryHandlers: [...QueryHandlers],
        CommandHandlers: [...CommandHandlers],
        Injectables: [InMemoryUserRepository, UserFactory],
      };
      
      CQRSModule(config);

      Using the spread operator its a way of keeping the module config cleaner, but this will make us made a index.ts with all the handlers loaded into an array, like this:

      application/index.ts

      export const CommandHandlers = [CreateUserCommandHandler];
      export const QueryHandlers = [GetUserQueryHandler, GetAllUsersQueryHandler];

      Some times injectables are loaded by other handlers and it wont fail even if you dont load them into the CQRS Module, BUT it is a good practice to load them just in case it doesnt load all the injectables.

License

This project is under a MIT license, so its open source and open for contributions :). More info about the license here

Package Sidebar

Install

npm i @leivaa/simple-cqrs

Weekly Downloads

1

Version

1.0.9

License

MIT

Unpacked Size

28.7 kB

Total Files

50

Last publish

Collaborators

  • leivaa