/simple-cqrs

A simple CQRS implementation for node.

Primary LanguageJavaScriptMIT LicenseMIT

Simple, small and flexible CQRS for node.

NPM Version Downloads Build Status Coverage Status Known Vulnerabilities

Installation

$ npm install simple-cqrs

Quick Start

const cqrs = require('simple-cqrs')
const app = cqrs.createApp()

app.useCommandHandler('showMessageCommand', (command) => {
	app.publish({ type: 'messageDisplayedEvent', message: command.message })
})

app.useEventHandler((event) => {
	console.log(`Message ${event.message} displayed.`)
}, 'messageDisplayedEvent')


app.send({ type: 'showMessageCommand', message: 'Hello World!' })

Download the example at examples/simple-app

Last Update

The last update 2.1.0 includes the possibility to register one handler to multiple events. Example: LogginEventHandler should handle all events of stream

Introduction

There are two ways to use Simple-CQRS library. The easier, showed in Quick Start section above and the flexible that will be show in the Docs section. Both use a set of classes included in core module, but the easier uses a mediator cqrs.createApp() to coordinate the access of those core classes.

Contents

Docs

Commands

You can use a json-based style command or a type-based command, the only requirement is to set the type of command:

//json-based
const command = {type:'showMessageCommand', message:'Hello World!'};

When you use type-based command, you don't have to set the type manually. It's automatically set by Command class. The type will be the name of the subclass.

//type-based using Ecma6
const { Command } = require('simple-cqrs')

class ShowMessageCommand extends Command{
  get message(){return this._message;}
  set message(value){this._message=value;}
}

//the type of above class is "ShowMessageCommand"

CommandDispatcher as a default CommandBus

A command bus is responsible for routing the command to the handler that will execute it. The CommandBus class is just an abstraction of a command bus and mustn't be instantiated.

You can implement a command bus by extending it. e.g: You can send a command through an Azure Service Bus Queue following this documentation: How to use Service Bus queues

If you just want to handle/execute a command in the same proccess, you can use the CommandDispatcher class included in the core:

const { CommandDispatcher } = require('simple-cqrs')
const dispatcher = new CommandDispatcher();

The first thing you must do is register a handler for a command. You can register a function to handle the command, or a command handler type-based:

//Registering a function as a handler of the ShowMessageCommand. 
dispatcher.register('ShowMessageCommand', (command)=>{
  console.log(command);
});

//Registering a command handler as a handler of the ShowMessageCommand. 
dispatcher.register('ShowMessageCommand', {factory:()=> {
  return new ShowMessageCommandHandler();
	}
});

You can't register a command twice. But you can register multiple commands for a handler:

//Registering multiple commands for a function.
dispatcher.register(['ShowMessageCommand','ClearConsoleCommand'], (command)=>{
  switch(command.type){
    case 'ShowMessageCommand':
      console.log(command.message);
      break;
    case 'ClearConsoleCommand':
      console.clear();
      break;
    default:
      throw Error(`Command ${command.type} can\'t be handled.`)
  }
});

Events

Just as commands, events also can use a json-based style or a type-based, the only requirement is to set the type of event:

//json-based
const event = {type:'messageDisplayedEvent', message:'Hello World!'};

When you use type-based, you don't have to set the type manually. It's automatically set by Event class. The type will be the name of the subclass.

//type-based using Ecma6
const { Event } = require('simple-cqrs')

class MessageDisplayedEvent extends Event {
  get message(){return this._message;}
  set message(value){this._message=value;}
}

//the type of above class is "MessageDisplayedEvent"

EventDispatcher as a default EventBus

Event bus is responsible to distribute the events to their handlers/listeners. It's a publish/subscribe communication pattern. The EventBus class is just an abstraction of an event bus and mustn't be instantiated.

You can implement your own event bus by extending the EventBus class. Just like CommanbBus, you can listen an infrastructure messaging component as an Azure Service Bus or RabbitMQ.

If you just want to handle/listen events in the same proccess, you can use the EventDispatcher class included in the core.

const { EventDispatcher } = require('simple-cqrs')
const dispatcher = new EventDispatcher();

The first thing you must do is register a handler for an event. You can register a function to handles the events, or a event handler type-based:

//Registering a function as a handler of the MessageDisplayedEvent. 
dispatcher.register((event)=>{
  console.log(event);
}, 'MessageDisplayedEvent');

//Registering a event handler as a handler of the MessageDisplayedEvent. 
const MessageDisplayedEventHandler = class MessageDisplayedEventHandler extends EventHandler {
	constructor() {
		super()
		this.register('MessageDisplayedEvent', handler)
	}
	handle(event) {
		console.log(`Event ${event.type} handled`)
		}
}

const eventHandler = new MessageDisplayedEventHandler()

dispatcher.register(eventHandler, 'MessageDisplayedEvent');

Tests & Coverage

To run the test suite against simple-cqrs or check coverage, first install the dependencies, then run npm test:

$ npm install
$ npm test
$ npm run cover

We're using mocha, chai and sinon for testing and istanbul for coverage

Contributing

Feel free to make changes!

License

MIT License

Copyright (c) 2017 rmelo

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.