From commands to events
Matthias Noback
In the previous posts we looked at commands and the command bus. Commands are simple objects which express a user’s intention to change something. Internally, the command object is handed over to the command bus, which performs the change that has been requested. While it eventually delegates this task to a dedicated command handler, it also takes care of several other things, like wrapping the command execution in a database transaction and protecting the original order of commands.
Secondary tasks
When a command has been handled by its corresponding command handler, there may be other things that need to be done.
For example, when the SignUp
command from the previous examples was successfully handled by the SignUpHandler
, it
may be useful to afterwards:
- Send a welcome message to the new user by email,
- Set up a personal profile page for the new user,
- Increase the “current number of users” statistic, which will be displayed on the administrator’s dashboard,
- And so on…
We may be tempted to perform all of these actions inside the SignUpHandler
:
class SignUpHandler
{
public function __construct(
UserRepository $userRepository,
UserMailer $userMailer,
ProfileService $profileService,
UserStatistics $userStatistics
) {
$this->userRepository = $userRepository;
$this->userMailer = $userMailer;
$this->profileService = $profileService;
$this->userStatistics = $userStatistics;
}
public function handle($command)
{
$user = User::signUp(
$command->emailAddress,
$command->password
);
$this->userRepository->add($user);
$this->userMailer->sendWelcomeMailTo($user);
$this->profileService->setUpProfilePageForUser($user);
$this->userStatistics->increment('number_of_users');
}
}
Why the command handler should not perform secondary tasks
There are several major disadvantages to this approach.
First, we need to be aware that the code of this handler is executed within a single database transaction. When
something fails along the way (sending the email, talking to the Redis server that contains the number_of_users
statistic, etc.), the entire operation fails. The user is not signed up after all, because the transaction is being
rolled back. If we are very unlucky, a welcome mail has been sent to the user, after which incrementing the statistic
fails. This would cause the transaction to be rolled back and the data of the signed up user will never actually be
persisted, meaning that the user experience becomes quite inconsistent.
Second, we need to be clear about what it means to sign a user up. Of course, the welcome mail is important, and so is
the profile page and the statistic (although that one is merely a “nice to have”). Still, signing up a user is mainly
making sure that the user (which is represented in our application as a User
object) will be around longer than the
current request or session; the User
object should be persistent, i.e. added to the “repository” of users. Everything
else that happens while handling the SignUpUser
command should be considered secondary, or after the fact.
Third, the SignUpHandler
has responsibilities which cut across several application layers. Its main concern is
persisting the signed up user. All the secondary tasks currently performed by the SignUpHandler
are mostly related to
infrastructural concerns (e.g. sending emails, talking to a statistics server, etc.). This indicates a problem with
responsibilities: the SignUpHandler
violates the Single responsibility principle (SRP).
Fourth, even though the code in the SignUpHandler
still looks quite simple, I’ve left out all the details. You
probably need much more code to perform all the tasks well. This would quickly result in an unmaintainable, inflexible
class. Since you have to actually modify the code in SignUpHandler
to change any of its secondary behaviors, the class
appears to be closed for extension, meaning that the Open/closed principle (OCP) has not been honoured.
Introducing events
An excellent way to solve issues with SRP and OCP and to separate primary tasks from secondary tasks is to introduce events. Events, like many other things, are objects. And like commands, they only contain some data and display no behavior at all. They are messages.
Event objects can be created while handling a command. They should be published after the command has been fully handled. Event subscribers are then able to hook into the event system and perform their specific task when a particular type of event has taken place:
class SignUpHandler
{
...
public function handle($command)
{
$user = User::signUp($command->emailAddress, $command->password);
$this->userRepository->add($user);
// create the event
$event = new UserSignedUp($user->id());
// dispatch the event
$this->eventDispatcher->dispatch($event);
}
}
Looking for the best event dispatcher
You are probably already familiar with some kind of event system. Maybe you’ve used the Symfony event dispatcher, the Laravel event dispatcher, or the EventEmitter package from the League of Extraordinary Packages. Most of these event dispatchers/managers/emitters are not suitable for the task at hand. Let’s first characterize our perfect event dispatcher for the situation described above:
- We want to pass an event object to the event dispatcher
- Every interested subscriber should be notified of the event
- Event subscribers should not be able to prevent other subscribers from being notified
- There should be no talking back to whoever dispatched the event
- It should be possible to store event messages and/or send them to some kind of a queue, for asynchronous processing
- The type of an event object corresponds to one particular event - we don’t want to separately provide the event name
That’s a nice list of requirements which is unfortunately not met by all of the event dispatchers I know:
- Some use arrays for the event data
- Most of them allow event subscribers to stop further propagation of the event
- Most of them offer event subscribers a way to talk back
- Most of them have options for prioritizing event handlers
- Most of them require two arguments when dispatching the event: an event name, and an event object
Introducing SimpleBus/EventBus
All of these features are undesirable or simply unnecessary in our use case. So for the dispatching of events from
within command handlers (or from any part of the application really), I created the
SimpleBus/EventBus package. It offers its own classes and interfaces for event
handling. Please note that you are always free to use the event dispatcher that you like best. Nothing in the the
CommandBus
package forces you to use the EventBus
package as well. The other way around applies here too: there’s
nothing in the EventBus
package that forces you to use the CommandBus
package as well. They are complete decoupled.
The SimpleBus/EventBus
package contains what looks pretty much like an event dispatcher. However, to make it blend in
with the other SimpleBus
packages, I decided to mirror the terminology of Command
s, the CommandBus
and
CommandHandler
s: the EventBus
package has the interfaces Event
, EventBus
and EventHandler
.
An EventBus
, just like the CommandBus
, is just an anonymous bus to which you hand over an Event
object. And just
like CommandBus
, usually an EventBus
instance is wrapped by another EventBus
by composition. Another similarity is
that the EventBus
handles one event completely before handling the next event.
However, there are some major differences:
- While commands have exactly one corresponding command handler, there is a one-to-many correspondence between an event and its handlers. There may even be zero handlers for a particular event.
- Events are not (supposed to be) handled in a separate database transaction, like commands are.
Instead of “dispatching” an event, we let the EventBus
handle an Event
:
class UserSignedUp implements Event
{
public function name()
{
return 'user_signed_up';
}
public function __construct($userId)
{
$this->userId = $userId;
}
public function userId()
{
return $this->userId;
}
}
// $eventBus is an instance of EventBus
$eventBus = ...;
$userId = ...;
$event = new UserSignedUp($userId);
$eventBus->handle($event);
The secondary task of sending the welcome mail would be executed by one of the event handlers, like this:
class SendWelcomeMailWhenUserSignedUp implements EventHandler
{
public function __construct(
UserRepository $userRepository,
UserMailer $userMailer
) {
$this->userRepository = $userRepository;
$this->userMailer = $userMailer;
}
public function handle(Event $event)
{
$user = $this->userRepository->getById($event->userId());
$this->userMailer->sendWelcomeMailTo($user);
}
}
Conclusion
In this post we have seen the merits of using events and event handlers to move secondary tasks from command handlers.
We discussed the usage of the EventBus
and why and how it is provided as part of the SimpleBus/EventBus
package.
In most cases you would not directly call the event bus yourself. This should usually be done by the command bus after a command was successfully handled. In another post we will look at how the command bus can be made aware of the event bus and how we can collect events during the handling of a command.