From commands to events

Posted on by 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 Commands, the CommandBus and CommandHandlers: 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.

If you're interested to learn more about these subjects and start using commands and events in your (Symfony) application, check out my Hexagonal Architecture training website.
PHP hexagonal architecture command bus events event bus SimpleBus