Using Command Bus in NodeJs
The Command Pattern was one of the behavioral patterns described by The Gang of Four as a way for two objects to communicate.
Lets get familiar with terminology
A command is an object that signals an intent, like RegisterUser
. Commands are passed to the command bus. Command bus will pass the command to a handler. The handler will perform the required actions, like registering users.
Now, let's deep dive into the specifics.
What is command?
Commands should contain all necessary information to be executed. They are basically Data Transfer Objects(DTO's). Because commands encapsulate all the information needed to execute, they can be handled synchronously or asynchronously.
Implementation for NodeJS
First you need to install via;
NPM
npm install simple-command-bus
YARN
yarn add simple-command-bus
Commands
Now, we are going to create the Command first. It needs to contain all the input parameters.
let’s look at a sample command that encapsulates our input:
const { Command } = require("simple-command-bus");
class CreateUserCommand extends Command {
constructor(userName, email, password) {
super();
this.userName = userName;
this.email = email;
this.password = password;
}
}
module.exports = CreateUserCommand;
So, how are these commands executed?
Command Handlers
Commands are handled by command handlers. A command must be handled by exactly one handler. In this sense, it differs from event, where we don’t care how many handlers (listeners) are involved.
An example:
const userService = require("../Domain/Services/UserService")
class CreateUserHandler {
async handle(command) {
return await userService.newUser(command)
}
}
module.exports = CreateUserHandler
Command handler dedicated to a single command, does the actual logic.
Handlers interpret the intent of a specific Command and perform the expected behavior. They have a 1:1 relationship with Commands – meaning that for each Command, there is only ever one Handler.
All the Command handler should be gathered to the Handlers folder in index.js file
Take a look of an example:
const {
CommandHandlerMiddleware,
ClassNameExtractor,
InMemoryLocator,
HandleInflector
} = require("simple-command-bus")
const CreateUserHandler = require("./UserHandler/CreateUserHandler")
const commandHandlerMiddleware = new CommandHandlerMiddleware(
new ClassNameExtractor(),
new InMemoryLocator({
CreateUserHandler: new CreateUserHandler()
}),
new HandleInflector()
)
module.exports = commandHandlerMiddleware
The command bus matches commands to handlers. This matching can be done automatically by some kind of naming convention.
When a command is dispatched, the bus locates the handler and calls the handle method.
The interface of the Command Bus might look like this:
const { CommandBus, LoggerMiddleware } = require("simple-command-bus");
const commandHandlerMiddleware = require("../Command/Handlers");
const CreateUserCommand = require("../Command/UserCommand/CreateUserCommand");
const commandBus = new CommandBus([
new LoggerMiddleware(console),
commandHandlerMiddleware
]);
const registerUser = async (req, res) => {
const { userName, email, password } = req.body;
try {
const createUserCommand = new CreateUserCommand(userName,email,password);
const user = await commandBus.handle(createUserCommand);
return res.redirect("/profile");
};
Conclusion
With the Command bus pattern, we have made our controllers quite lean and now they have following responsibilities:
- Forward the data to command handlers by extracting input parameters from the request and creating a command
- Receiving response from the command handler (via the Command Bus)
While using command bus in carbonteq we have realized they have made our code more testable now we don't need to spin a HTTP server to test a particular endpoint, furthermore we have noticed they have made our code-base more readable and now its much easier to onboard new team members.