cult3

Implementing Domain Events

Sep 22, 2014

Table of contents:

  1. Using Events in application development
  2. What are Domain Events?
  3. How do Domain Events work?
  4. Adding the Domain Events package
  5. Domain Events and Aggregate Roots
  6. Creating Domain Events
  7. Listening for Events
  8. Dispatching Events
  9. Conclusion

Recently we’ve been taking a deeper dive into the world of Domain Driven Design to discover how we can understand the requirements of an application and model it in code.

A big problem with application development is often that the two opposing sides of business and development fail to communicate effectively and so the end product does not meet the end user’s requirements.

Domain Driven Design aims to mitigate this problem.

A common requirement within an application is an action should be triggered as a consequence of some other action occurring.

In Domain Driven Development this pattern is known as Domain Events, and so that is what we’ll be looking at today.

Using Events in application development

The usage of events within the context of an application is more commonly known as The Publish-Subscribe pattern.

I’ve already covered using Events in web applications a couple of times, so instead of covering old ground I’ll link you up with those older posts so you can have a read through them instead.

First I looked at using Events in Laravel 4. Laravel is built around the idea of events and so the framework ships with it’s own event dispatcher that allows you to hook on to the framework or define your infrastructure events.

Next I looked at Creating Registration Events in Laravel 4. This tutorial centred around using Laravel’s event dispatcher within the context of a registration process. Typically when a new user signs up for your application you will probably want to kick off a series of events such as sending that user a confirmation email. Using events we can decouple those actions from the code that actually registers the new user.

Finally I looked at Understanding Doctrine 2 Lifecycle Events. In the previous tutorials I was looking at events within the architecture of Laravel as a framework. However events are commonly used in a variety of different contexts. In this tutorial I looked at how Doctrine 2 uses lifecycle events to allow you to hook on to the various stages of the ORM process.

So we’ve previously looked at two very different types of event based systems. Laravel has a fantastic event dispatcher that can be used to trigger events within the framework layer of your application. Doctrine 2 has events that allow you to hook on to the various stages of persisting data to the database. Both of these event systems accomplish the same job of decoupling actions from their triggers.

Domain Events also decouple actions based on their triggers and so conceptually all three event systems are satisfying the same requirement.

However there is a strict line between where you use the three different event systems and for what purpose.

Doctrine 2 events should only ever be used as part of the data persistence process. These types of events are internal to how Doctrine 2 functions and should not leak outside of this layer of abstraction.

Laravel’s event dispatcher should only be used on the surface layer of the application. Laravel’s event dispatcher is used as part of the infrastructure of your application.

Domain events should only ever be used to model the events of your domain. These events are core to the domain of your application and are critical for implementing your business logic.

Whilst all three different types of events accomplish the same goals, you should maintain this separation. Data persistence or framework concerns should never leak into your domain related code.

What are Domain Events?

So what are Domain Events? Well, a Domain Event is an event that is a concern of the domain of the application you are building. The requirement for Domain Events usually arrises from the direct conversations with the client or business expert.

Non-technical people won’t refer to Domain Events explicitly, rather they will mention the fact that something needs to occur as a consequence of another separate action. It is your job as the technical person to understand these statements and translate them into the correct tool for the job.

As with Value Objects, Specification Objects and Entities, Domain Events should model a particular part of the domain.

So for instance, if the domain of your application requires that an email confirmation is sent to a new user when they sign up, this would be a Domain Event because it is a rule of the business you are modelling.

How do Domain Events work?

Domain Events work in exactly the same way that an event based architecture works in other contexts.

You will typically create a new event such as UserWasRegistered. This will be a class that holds the required details of the event that just took place, in this case an instance a $user object.

Next you will write listeners to listen for the event. For example, you might have a listener called SendNewUserWelcomeEmail. This would be a class that accepts the UserWasRegistered event and uses the $user object to send the email.

The SendNewUserWelcomeEmail is responsible for having the ability to send the email and so the process for registering a new user is completely decoupled from the process of sending the email.

You can also register multiple listeners for events so you can very easily add or remove actions that should be fired whenever an event takes place.

Adding the Domain Events package

Domain Events are certainly not a new concept and so it doesn’t make sense to reinvent the wheel on how they should work. Whenever you can use an open source library to solve your problem you should do so because it is one less concern for your application.

We’re going to need an event dispatcher that will allow us to register new events and listeners. I’m going to be using the Domain Event Dispatcher by Mitchell van Wijngaarden.

Add the following line to your composer.json file to include this package as a dependency of your project:

"heybigname/event-dispatcher": "~1.1"

Domain Events and Aggregate Roots

As I briefly mentioned last week, Aggregate Roots form the backbone of our domain model.

You can think of Aggregate Roots as the main entity that controls access to other entities. So for example in the context of a forum, a Post entity would be the Aggregate Root and so it would act as a gateway to the Comment objects. A Comment does not make sense without a Post and so by restricting access to Comment objects through the Post Aggregate Root, we make our code much easier to work with.

The Aggregate Root also controls what Domain Events should be used throughout the lifecycle of the entities it controls. We can identity an Aggregate Root by using the interface we defined last week:

<?php namespace Cribbb\Domain\Model\Identity;

use Cribbb\Domain\AggregateRoot;

class User implements AggregateRoot
{
}

Our AggregateRoot interface requires that we implement methods for record() and release(). These two methods will be used for attaching events during application requests and releasing and dispatching them at the appropriate time.

To satisfy this interface we can use the HasEvents trait that we defined last week:

<?php namespace Cribbb\Domain\Model\Identity;

use Cribbb\Domain\HasEvents;
use Cribbb\Domain\AggregateRoot;

class User implements AggregateRoot
{
    use HasEvents;
}

Creating Domain Events

Domain Event objects are very simple PHP classes that capture the data of the event that just took place. These classes can contain whatever is relevant from the event, it’s really up to you.

For example, the UserHasRegistered event might look like this:

<?php namespace Cribbb\Domain\Model\Identity\Events;

use Cribbb\Gettable;
use BigName\EventDispatcher\Event;
use Cribbb\Domain\Model\Identity\User;

class UserHasRegistered implements Event
{
    use Gettable;

    /**
     * @var User
     */
    private $user;

    /**
     * Create a new UserHasRegistered event
     *
     * @param User $user
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Return the name of the event
     *
     * @return string
     */
    public function getName()
    {
        return "UserHasRegistered";
    }
}

You will notice that I’ve created this class under the Events namespace deep within the Domain\Model\Identity namespace. Domain Events should live within your Domain related code.

The Domain Event implements the Event interface from the dispatcher package. This interface requires that we have a getName() method that returns the name of the event.

The rest of the class is really up to us. In this example I’m simply injecting the User $user object that was just created during the registration process.

The event is recorded in the User entity when a new object is created:

/**
 * Create a new User
 *
 * @param UserId $userId
 * @param Email $email
 * @param Username $username
 * @param HashedPassword $password
 * @return void
 */
private function __construct(UserId $userId, Email $email, Username $username, HashedPassword $password)
{
    $this->setId($userId);
    $this->setEmail($email);
    $this->setUsername($username);
    $this->setPassword($password);

    $this->record(new UserHasRegistered($this));
}

Listening for Events

The beautiful thing about this pattern is the fact that the listeners who are interested in the events are totally decoupled from the process that triggers the events.

This means you can have multiple separate listeners for each event, and you can add or remove listeners without touching any of your existing code.

This also makes listeners really small and easily testable!

When a new user has registered we might want to send them an email to welcome them to the application.

Your listener class might look something like this:

<?php namespace Cribbb\Domain\Model\Identity\Listeners;

use Cribbb\Mailer;
use BigName\EventDispatcher\Event;
use BigName\EventDispatcher\Listener;

class SendWelcomeEmailListener implements Listener
{
    /**
     * @var Cribbb\Mailer
     */
    private $mailer;

    /**
     * Create a new SendWelcomeEmailListener listener
     *
     * @param Mailer $mailer
     * @return void
     */
    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    /**
     * Handle the Event
     *
     * @param Event $event
     * @return void
     */
    public function handle(Event $event)
    {
        // Send the welcome email
    }
}

As you can see, the listener is responsible for sending the email to the new user.

With the listener created, we can attach it to the event within the Dispatcher:

use Cribbb\Mailer;
use BigName\EventDispatcher\Dispatcher;
use Cribbb\Domain\Model\Identity\Listeners\SendWelcomeEmailListener;

$listener = new SendWelcomeEmailListener(new Mailer());

$dispatcher = new Dispatcher();
$dispatcher->addListener("UserWasRegistered", $listener);

You can register any number of listeners to any events that you define within your application. The code above would typically be found during the bootstrap process of your application.

Dispatching Events

Now that a new User $user object has been created we can dispatch the events that have occurred to trigger the interested listeners.

The User $user object has these pent-up events ready to go. In our example this is only the UserHasRegistered event, but it could be any number of additional events also.

To dispatch the events we simply need to provide the array of events from the User $user object to the dispatch() method of the Dispatcher:

$dispatcher->dispatch($user->release());

This will cycle through each event and trigger the listeners to execute their code. In this example that would mean the SendWelcomeEmailListener would send the welcome email to the new user.

Conclusion

Domain Events are an extremely important aspect of Domain Driven Design and so they form one of the fundamental building blocks around modelling an application. A good place to start when planning the build of an application is often to think of what Domain Events are required.

Domain Events allow you to decouple events and listeners. This makes it really easy to add or remove listeners that should be fired as a consequence of an event.

Separating your code into events and listeners also has the added benefit that it promotes the separation of concerns. In the example we looked at today, the SendWelcomeEmailListener has the single concern of sending that single email. This makes testing your code a whole lot easier!

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.