Command Bus
The classes and interfaces from this package can be used to set up a command bus. The characteristics of a command bus are:
It handles commands, i.e. imperative messages.
Commands are handled by exactly one command handler.
The behavior of the command bus is extensible: middlewares are allowed to do things before or after handling a command.
Setting up a Command Bus
At least we need an instance of MessageHandlerMiddleware:
Defining a message mapper for the middleware
The message handler middleware needs an instance of a message mapper that allows to retrieve a handler for a given message (in this case commands).
To create it we need some other objects that are dependencies of the mapper.
Creating the collection to defined the command handler map
We want commands to be handled by exactly one command handler (which can be functions or classes). We first need to define the collection of handlers and commands pair that are available in the application. We should make this command handler map lazy-loading, or every command handler will be fully loaded, even though it is not going to be used:
Each of the provided handler can be one of the following things:
A function.
An array of functions.
A service id (string or class) which the resolver (see below)
can resolve to a valid object that has a method named
handle()
.An array where the first item is a class to resolve against the handler
and the second item is a custom method to call.
Creating the resolver to instantiate handlers
If you don't use functions as handlers, you will need to create a resolver. It should provide an instance of the handler when the bus needs it to handle a command.
@layerr/bus doesn't provide a default resolver since it strictly depends on your application setup. For example, if you use Angular, your resolver will be the Injector which is the service container used by the Angular DI to create the services. If your framework doesn't support a DI or you simply don't use it you can use the resolver to import the needed class.
If you use a framework dependency manager the resolve will be something similar to:
If you don't use the DI maybe you will use strings instead of classes:
Resolving the command handler for a command
First we need a way to resolve the name of a command. You can use the constructor function of a command object as its name:
If you use this extract you have to define class as commands in the collection.
Alternatively, you can define a public name
property in the command and use a specific extractor:
with this one, you have to use strings as commands in the collection.
If you don't use classes, but strings, you have to use another extractor:
and make sure to you strings as commands as well.
Allow the handler to map and resolve the command and command handler
Finally, you have to create a message mapper by combining the previous components. It is used by the handler middleware to resolve and get the specific handler for a message (i.e. command).
The MessageMapper
is the default one. It works with any extractor, but it needs a resolver since the handler in the collection has to be something the resolver knows.
Alternatively, FunctionMessageMapper
can be used if the handler is a function or an array of functions. It doesn't need a resolver.
Finally, MethodMessageMapper
can be used if the handler is a function or an array of functions. It doesn't need a resolver.
Use the command bus
Consider the following command:
This command communicates the intention to “register a new user”. The message data consists of an email address and a password in plain text. This information is required to execute the desired behavior.
The handler for this command looks like this:
We should register this handler as a service and add the service id to the command handler map. Since we have already fully configured the command bus, we can just start creating a new command object and let the command bus handle it. Eventually the command will be passed as a message to the RegisterUserCommandHandler
:
By design, the command handler shouldn't return anything. It is void method. Anyway, in most cases, we have interested to understand if the command was executed correctly or not since we want to adapt our application to successful or failure.
Observers
To do that, just subscribe to the handle:
The bus handles the command by calling the command handler even if you don't subscribe to the observer returned by handle method.
Promises
If you want to use promises instead of observers just call the toPromise
method:
Last updated