Collecting events and the event dispatching command bus

Posted on by Matthias Noback

It was quite a ride so far. We have seen commands, command buses, events and event buses. We distilled some more knowledge about them while formulating answers to some interesting questions from readers.

Why you should not dispatch events while handling a command

In a previous post we discussed a sample event (the UserSignedUp 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;
    }
}

An instance of such an event can be handed over to the event bus. It will look for any number of event handlers that wants to be notified about the event. In the case of the UserSignedUp event, one of the interested event handlers is the SendWelcomeMailWhenUserSignedUp handler:

class SendWelcomeMailWhenUserSignedUp implements EventHandler
{
    ...

    public function handle(Event $event)
    {
        $user = $this->userRepository->getById($event->userId());

        $this->userMailer->sendWelcomeMailTo($user);
    }
}

The UserSignedUp event occurs whenever a SignUpUser command has been handled. We might be tempted to inject the event bus into the command handler and let it handle the event immediately:

class SignUpHandler
{
    public function __construct(EventBus $eventBus, ...)
    {
        $this->eventBus = $eventBus;
        ...
    }

    public function handle($command)
    {
        ...

        $this->userRepository->add($user);

        $event = new UserSignedUp($user->id());

        $this->eventBus->handle($event);
    }
}

We should not forget however that this command is handled within a database transaction created by one of the command buses. And as I already mentioned, we don't want the event to be handled within the same transaction, since it could endanger the success of that transaction. Also, when the transaction fails in the end, we might end up handling an event for entities whose actual state is insecure.

So instead, we should merely collect events while handling the command and only hand them over to the event bus when the transaction was committed, i.e. when we know that the command has been successfully and completely handled.

Event providers

Of course, SimpleBus would not be complete without an out-of-the-box implementation of this kind of delayed event handling. The connecting concept between events and the command bus is the ProvidesEvents interface, which is part of the SimpleBus/EventBus package.

namespace SimpleBus\Event\Provider;

use SimpleBus\Event\Event;

interface ProvidesEvents
{
    /**
     * @return Event[]
     */
    public function releaseEvents();
}

You can provide a simple implementation for such an EventProvider by defining a class for it, which uses the trait EventProviderCapabilities. This adds the ability to collect events (using the raise() method) and release them later, using the releaseEvents() method:

use SimpleBus\Event\Provider\EventProviderCapabilities;

class EventProvider implements ProvidesEvents
{
    use EventProviderCapabilities;
}

We could inject this EventProvider into any command handler and use the raise() method of the EventProvider to raise new events (which will not be handled right-away, but only stored in-memory):

class SignUpHandler
{
    public function __construct(EventProvider $eventProvider, ...)
    {
        $this->eventProvider = $eventProvider;
        ...
    }

    public function handle($command)
    {
        ...

        $event = new UserSignedUp($user->id());

        $this->eventProvider->raise($event);
    }
}

Now, at what point could we safely release the events collected by the EventProvider and hand those events over to the event bus? The answer is: in yet another command bus! We simply wrap the new event-aware command bus which starts and commits the database transaction. Only then can we be sure that the transaction was successful and we can proceed to safely dispatch the collected events.

Since SimpleBus/CommandBus and SimpleBus/EventBus are stand-alone packages, I introduced a bridge package between the two, SimpleBus/CommandEventBridge. It contains this class, which is a CommandBus, but asks the event provider to release its events, and lets the EventBus instance handle them:

class DispatchesEvents implements CommandBus
{
    use RemembersNext;

    private $eventProvider;
    private $eventBus;

    public function __construct(ProvidesEvents $eventProvider, EventBus $eventBus)
    {
        $this->eventBus = $eventBus;
        $this->eventProvider = $eventProvider;
    }

    public function handle(Command $command)
    {
        $this->next($command);

        foreach ($this->eventProvider->releaseEvents() as $event) {
            $this->eventBus->handle($event);
        }
    }
}

In a diagram this would look something like this (from left to right it shows you the passage of time):

Diagram

Conclusion

After reading this series about command and event buses, you can start using commands to interact with your application and events to act upon the things that happened. Because both the command and the event bus are highly extensible you have many options for introducing more advanced behavior. You could think of:

  • Collecting events in an store
  • Handling commands asynchronously
  • The same for events
  • ...

For now we are more or less done talking about command and event buses. If you have questions, just write a comment below this post. I'm currently working on version 2.0 of the SimpleBus packages. You can at least expect a new post when I'm done, to announce the changes with respect to 1.0. To give you a sneak peek:

  • Code for command and event buses will be generalized and bundled in the MessageBus package.
  • Instead of wrapping message buses using composition, this new package introduces the concept of message bus middlewares, offering greater flexibility and cleaner code for your own specialized message bus features.
PHP hexagonal architecture commands command bus events event bus SimpleBus