I first heard about ‘message oriented software design’ at SymfonyLive back in September at Matthias Noback’s talk “Hexagonal Architecture”.
A command bus consists of three parts: the command, which is essentially a message, the handler which processes the message and the bus which sends the command to the correct handler.
Using the command bus can help you follow good design principles and prevent code duplication. It’s also easy to extend using middleware (which we’ll look at later on). Most importantly for me, it allows me to abstract away authentication from the solution: we can write self-contained functionality. In my case, when used in a privileged area, the operation is protected by the Symfony firewall. When the same command is run in the terminal, I don’t need to authenticate. This is done without duplicating code or requiring major refactoring.
The command bus can be used in most environments, however, it’d be most useful in large codebases where there are similar operations.
Limpid Markets is a real-time trading application for precious metals swaps. We’ve recently been working on a new product that shares some functionality with the existing app so we’ve used this opportunity to clean up the codebase.
Each step in a process must run and if any part of the process fails (for example, database connection failing), then the whole transaction needs to be rolled back. This was was previously implemented as a group of steps but was tied closely to the original product.
The backend of Limpid Markets is written in PHP with the Symfony framework so we were looking for a library which worked out of the box with Symfony. There are a few good command bus libraries for PHP such as SimpleBus, Tactition and Broadway. Laravel 5 even ships with its own command bus. We ended up choosing SimpleBus, written by the speaker of the Hexagonal Architecture talk, as there was already a Symfony bridge and good documentation.
In this post, we’re going to look at implementing ‘delete user’ functionality using the command bus. I’ve also used the FOS User Bundle in my example.
Install with composer
Firstly, we need to install the required packages below:
composer require simple-bus/message-bus
composer require simple-bus/symfony-bridge
Creating the command
Next, we’re going to make a command. A command is a simple class which is used to set up the required parameters. I created the class below in the AppBundle/Command/DeleteUserCommand namespace.
In order to delete the user entity, the command needs to know which user to delete. We’ve used primitive types, in this case the id of the user, as opposed the to User object as we don’t want to limit the use of our command. We’ll ideally have any instance of deleting a user go through this command. Although we may have the user object in the controller, if we make a terminal command we wouldn’t necessarily have the user object available. However, we can always pass an id as an argument to a terminal command. It’s also important to remember to give a unique name to the command as this name will be referenced in the next section.
Handling the command
Each command needs a handler which is where we’re going to put our logic, in this case, deleting a user. If an exception is thrown, our command bus will fail. We’ll explore this further when we look at transactions in the middleware section.
Registering the Command Handler
Now we have our command and handler, we need to register them. The command handler is registered like most other services: simply add in any arguments passed to the constructor like normal. The important part is to assign the name of the command so that it can be handled. In our case, this is “delete_user_command” which we specified earlier.
Calling the command bus
We’re now ready to use our DeleteUserCommand. In this case, I’m going to create and handle the command in my controller but this could be done in several places.
Getting data out
Our command handler doesn’t return a response. This is because we don’t know where our command is being run from or how long it will take to process. For the most part, this is good as it helps to keep our controllers thin. However, there may be occasions when we need to know about the object we’ve modified. Let’s say we want to create a user. As part of the command, we can set a UUID (Universally Unique Identifier). As before, we can create the command in the controller. When the command is handled, we can persist the UUID to the new User object. Assuming our command successfully completes, we now have a unique identifier of the User object in the controller which we can use. Mostly this shouldn’t be necessary as it’s rare that you’d need to do operations in the controller using an object you’ve just persisted.
When working on our application we had two main uses for middleware. Firstly, we have a sequence number which is updated in each step of a trade. The other use was to wrap each command in a transaction so that if it fails, the data will roll back to its original state. Middleware is similar to the handler we previously looked at. We need to create a class which implements MessageBusMiddleware. As before, we need a handle method where we can add our logic:
Now we have some middleware, we need to extend the MessageBusSupportingMiddleware that comes with SimpleBus to include our middleware. We can now inject our own command bus instead of the default implementation.
Was is a success?
The main benefit of using the command bus is that it allowed us to clear out a lot of old code and replace it with much smaller and simpler code. It also made it a lot easier to keep code DRY (Don’t Repeat Yourself). As we used the command bus, we’ve been able to refactor existing similar code and have outlined a few areas in the application to refactor in the future.
It’s also given me a better understanding and improved confidence in the application. A side effect of the work we’ve done is it’s lowered the barrier to entry as we’re now using a standard approach which is well documented and has some good examples.
If you want to learn more about the command bus (and in particular the SimpleBus package) then I’ve highly recommend reading though the maintainers blog posts on the subject.