clev-cqrs
clev-cqrs is a nodejs library that allows you to incorporate CQRS and Event sourcing into your nodejs project without any stress. It is designed to be compatible with any database and work in an asynchronous environment. clev-cqrs supports callbacks. Just hearing CQRS or Event Sourcing for the first time? check out this article
Documentation
The official documentation website is at our repository.
Supported Database
clev-cqrs supports any database. All relational(sql)
and non-relational(no-sql)
database are well supported
Contributors
Pull requests are always welcome! Please base pull requests against the master
branch and follow the contributing guide.
Please make your changes and contribution in the src
folder.
Installation
First install Node.js. Then:
$ npm install clev-cqrs
Importing
// Using Node.js `require()`const clevCqrs = ; // Using ES6 imports;
Overview
Create a Schema
First, we need to create a schema using the Schema constructor or simply run sh node ./node_modules/clev-cqrs/src/createTemplate
from clev-cqrs, the schema should look something like this
let clevCqrs = ;const ExampleView2 = ;const ExampleView = ;const eventStore = ;let SchemaName = schemaName: "schemaName e.g Product" fields: //fields here // e.g name:{ // type:'string', // required:true // }, commandHandlers: //handleCreate command handler is required { currentData = changesObjectid; //required // add other entity details here using example of this format // currentData.description= changesObject.description // currentData.name = changesObject.name } // other command handlers here e.g // handleChangeName: function(changesObject, currentData){ // currentData.name= changesObject.name // }, eventStore: methods: get: eventStoreget delete: eventStoredelete getAll: eventStoregetAll save: eventStoresave eventHandlers: views: ExampleView ExampleView2 ; clevCqrs;
Your schema should contain the following
string
schemaName This should be the name of your business entity e.g Product, User e.t.c
object
fields This should contain all the property of the entity, each property should have its own type
and required
field e.g
fields: name: type:'string'// all types would be discussed later on this page required:true // can also be false
object
commandHandlers commandHandlers are mearnt to handle any change that happens to your entity e.g(change of name, price, age and so on), each handler points to a function that takes in two parameters, 1. changesObject
An object with the changes you made e.g {name:'Roberto Callus'} you can then set the changes to your currentData as shown in the code below and 2. currentData
which is the current state of your entity.
Note that the handleCreate
command handler is required as this would be used to create your entity and don't forget to set the id property(id would be autogenerated you don't need to border about it just set it that's all). Also every command handler must start with the word handle
e.g handleDebitWallet
, handleShipOrder
and every handler is mearnt to handle a change in each property i.e handleChangeName should only handle change of name, handleUpdateAge should only handle change of age e.t.c
commandHandlers: //handleCreate command handler is required //every command handler must start with the word handle { currentDataid = changesObjectid //required // add other entity details here using example of this format // currentData.description= changesObject.description // currentData.name = changesObject.name } // examples of command handlers { currentDataname= changesObjectname } { currentDatawallet = currentDatawallet - changesObjectwallet } //....
object
eventStore eventStore is an object that points to methods. There are four methods that must be provided
get
, getAll
, delete
and save
. The get
property should be set to an asynchronous function that takes in an id, search for an object from your database with the id and return it. The getAll
property should be set to an asynchronous function that returns all the documents in your database as an array. The delete
property should be set to an asynchronous function that takes in an id and delete a document based on the id that was passed. The save
property should be set to a function that saves a object to your database. e.g
//if you are using a mongo db eventStore: methods: { let event = await db return event } { await db } { let events = await db let eventsToArray = await events return eventsToArray } { await db }
object
eventHandlers event handlers is an object that points to different views that you want in your service.
eventHandlers: views: ExampleView ExampleView2;
object
views views are various ways you want see your data represented, for example if you are working on an e-commerce service, you can have a view that displays all the order that has been placed so far (ordersView
) and another view that displays the customer that has placed the highest number of orders(cutomerPeformanceView
). Each view respond to events that concerns it, lets use the example above, each order would have properties like
orderPlacedBy
, items
,totalPrice
, shippingAddress
, totalQuantity
. When the shippingAddress
is updated, only the ordersView
would be affected but if the orderPlacedBy
was updated both the ordersView
and the cutomerPeformanceView
would be updated.
To create a view, create an instance of the clevCqrs.View then execute the setHandlers method. the setHandlers method takes in an object parameter, this object would have the handlers for any change that happens, each handler is a function that takes in two parameters, 1
an object representing the change that was made e.g ``{age : 20}.
2callback` this should always be called after an event is handled to show that no error occured, you can pass an error to it, it would be thrown if it occurs.
Note that eventHandlers in a view must use thesame name that was used for the commandHandler e.g handleChangeName
, handleChangeAddress
and so on. Each view must have a handleCreate event handler as this would be used to create the entity don't forget to set the id property on the entity while creating. An example of a view is shown below
//Assuming you are using a mongo databaseconst clevCqrs = ;let ProductDetailsView = ;//set handler for any event any event that is related to this view, this should relate to changes you want to make to the read database// for example if this a ProductDetails view you might want to check for events that affect all attribute of the product and if it's a ProductPrice view you might want to check for event that affect only name and price attributeProductDetailsView; moduleexports = ExampleView2;
setSchema method
once you are done creating the schema which include all the proprties explained above jist call the setSchema function passing the schema that you created
clevCqrs;
sending a command
After you have created a schema and set it, the steps remaining are very few. Call the module where you created your schema in your root module e.g (app.js or index.js) by using the require function. You can send commands by using the clevCQRS.handler object. The format is clevCQRS.handler. command handlers name e.g handleCreate or handleChangeName
. The commandHandler that you provided in your schema would be used here. Note that every command handler apart from the handleCreate takes in an id string
, command object
and callback function,the id is the id of document that you want to change , command would take in the name of the attribute that you want to change and the new value you want it to be, and the callback would be a function the returns the updated entity and all the previous events that has ever occured to that entity. e.g
let express = const app = // file you setup your schemaconst clevCqrs = // this module was auto installed when you installed clev-cqrsconst clevCqrsUi = app app app
eventstore page
clev-cqrs provides a nice user interface to view all the events that has occured to your data. This include the event id, the event type e.g(changeName), the event time and the event data. to generatge this page just add the clevCQRS.generatePage as a middleware. e.g
// this module was auto instaled when you installed clev-cqrsconst clevCqrsUi = app
You can enter this in the browser to see your nice UI localhost:4000/cqrs
It looks like this
Contributions are opened to make this UI better
Types in clev-cqrs
clev-Cqrs support various types which are
- string
- number
- buffer
- date
- mix
- object
- array
The types are used when contructing your schema. An example of how to use the types is in the code below
let clevCqrs = ;const ExampleView2 = ;const ExampleView = ;const eventStore = ;let SchemaName = schemaName: "User" fields: name: type: 'string' required: true age: max: 100 min: 1 type: 'number' nameToBuffer: type: 'buffer' dob: type: 'date' required: false mixed: type: 'mix' address: type: 'object' roles: type: 'array' //.........; clevCqrs;
You can set the min and max value if the data type is a number as shown in the code above and you can also make a field required or not.
Here is an example of a command with all the clev-cqrs data types
let command = name: 1//number type address: //object type town: 'ado' roles: 1 2//array type mixed: '1234'// mixed sample dob: 123 // date type nameToBuffer: 1 2// buffer type app
You may be also interested in:
- clev-cqrs-ui:Adds middleware to your express app to autogenerate a page that shows all the events that has ever happend to your entity using the clev-cqrs-ui bound to the clev-cqrs framework.
License
Copyright (c) 2020 Adewumi Sunkanmi;
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.