0.3.2 • Published 4 years ago

@functional-cqrs/stores v0.3.2

Weekly downloads
10
License
UNLICENSED
Repository
github
Last release
4 years ago

Functional Cqrs

lerna CI Coverage Status

This library provides functional approach to CQRS (Command Query Responsibility Segregation).

Content list

  1. ✨ Getting Started
  2. 🚀 Usage
  3. Examples

✨ Getting Started

Prerequsites

You will need:

  • nodejs (>=12.13.1, not tested on older versions, but might work)

Installation

npm install @functional-cqrs/core or yarn add @functional-cqrs/core

🚀 Usage

Functional Cqrs splits into three basic components:

Commands

Commands are parts of applications that change it's state (for example by creating new record in database) and dispatch events respective to these actions. Commands can be executed by using CommandsBus.

Typescript

commandsBus.execute<SaveTodo>({
    type: 'SaveTodo',
    payload: todo,
  });

Javascript

commandsBus.execute({
    type: 'SaveTodo',
    payload: todo,
  });

Commands always come with two properties:

  • type definies type of the command (usually named after certain domain action, ex. "SaveTodo"),
  • payload Data attached to command (ex. input of entity that should be validated and saved in database)

Every command should have only one handler

Hint: Commands are usualy named in present tense

Bonus for Typescript users, you can define commands types in following way:

import { Command } from '@functional-cqrs/typings';

export type SaveTodo = Command<'SaveTodo', Todo>;

First generic argument defines command type property, and second defines payload.

Command Handlers

Handlers are always bound to single command, and they can return value. They can also execute queries and dispatch events

Typescript

import { CommandHandler } from '@functional-cqrs/typings';
import { commandHandler } from '@functional-cqrs/stores';

export interface Context {
  connection: Connection;
}

const saveTodoHandler: CommandHandler<SaveTodo, Context> = ({
  eventsBus,
  connection,
}) => async ({ payload }) => {
  if (!payload.title) {
    throw new Error('Title is required.');
  }

  const todo = connection.save(payload);

  await eventsBus.dispatch<TodoSaved>({
    event: 'TodoSaved',
    payload: todo,
  });

  return todo;
};

export default commandHandler<SaveTodo, Context>('SaveTodo', saveTodoHandler);

Javascript

import { commandHandler } from '@functional-cqrs/stores';

const saveTodoHandler= ({
  eventsBus,
  connection,
}) => async ({ payload }) => {
  if (!payload.title) {
    throw new Error('Title is required.');
  }

  const todo = connection.save(payload);

  await eventsBus.dispatch({
    event: 'TodoSaved',
    payload: todo,
  });

  return todo;
};

module.exports = commandHandler('SaveTodo', saveTodoHandler);

Handler is a function that takes one parameter called Context which basically contains all dependencies from your application, such as database connection and also buses related to it's type. Command handlers get's injected with two buses - Queries Bus and Events Bus in addition to the context. This function returns another function that takes in this case command as a parameter that then get's resolved.

Hint: You always need to export the handler by "decorating" it with commandHandler decorator that comes from @functional-cqrs/stores package.

Exporting handler

The decorated handler can be exported in following ways:

// Export as node module
module.exports = commandHandler('SaveTodo', saveTodoHandler);

// Export as node modules as object
module.exports = {
  handler: commandHandler('SaveTodo', saveTodoHandler)
};

// Default ES6 export
export default commandHandler<SaveTodo, Context>('SaveTodo', saveTodoHandler);

// Export as "handler" variable
export const handler = commandHandler<SaveTodo, Context>('SaveTodo', saveTodoHandler);