cult3

Creating your own Domain Event Dispatcher

Nov 03, 2014

Table of contents:

  1. Why write your own Domain Event Dispatcher
  2. How is this going to work?
  3. The Domain Event interface
  4. The Listener interface
  5. The Dispatcher
  6. Testing Domain Events
  7. What goes in to a Domain Event?
  8. Conclusion

A couple of weeks ago we looked at implementing Domain Events. Domain Events form a crucial building block in Domain Driven Design by allowing us to effectively decouple our application using the Publish-subscribe pattern.

Whilst the vast majority of the time I will be the first to advocate using an existing Open Source package, I do believe there is a time and a place to write your own solution.

In today’s tutorial we’re going to be looking at writing our own Domain Event Dispatcher.

Why write your own Domain Event Dispatcher

Before I jump into the code, first I want clarify why I think this is important.

The majority of the time I will always using an existing Open Source solution that meets my requirements for the problem at hand. Open Source packages tend to be of higher quality, with less bugs and more active maintenance than home grown solutions.

The Domain Event Dispatcher package we used a couple of weeks ago certainly meets those requirements.

However for certain aspects of a project, I think it’s often better to bring it in-house.

Domain Events are an integral component of any Domain Driven Design application. A lot of the functionality of the application will be driven by events, and so arguably events are as important as Entities and Value Objects.

With Domain Event dispatching being such an integral part of my application, I want to be able to control exactly how the process works and what the public API looks like.

My future requirements for a Domain Event Dispatcher are also likely going to change as this project evolves. Being able to introduce breaking changes to meet my requirements is something I can’t rely on with an Open Source project.

So hopefully you can see my point. I’m a strong advocate for not reinventing the wheel. Using an Open Source package to solve a generic problem is always a better solution than trying to solve it yourself.

However, if you want to have complete control over core parts of your application, and you want to be able to evolve the code at your own pace, I think bringing functionality in-house and writing it yourself is also a totally legit choice.

With that out of the way, let’s look at building a Domain Event Dispatcher.

How is this going to work?

The process for dispatching events is broken down into three main areas.

1. Events Firstly we create a new object that holds all the required data of the Event. For example, if we had a Domain Event that was triggered when a user changed their email address, the Domain Event would hold the appropriate data from that event. Domain Events are essentially just simple PHP objects that hold data.

2. Listeners When an Event is dispatched it is passed to all the Listeners who are Listening for that event. The Event object is passed to the Listener so the Listener can do something with the data.

3. Dispatcher The Dispatcher is the glue that holds the process together. Listeners are associated with Events during the bootstrapping process of the application. This means the Dispatcher knows what Listeners should be called whenever an Event is dispatched.

When the Event is ready to be dispatched, it is passed to the Dispatcher who then hands it to each of the interested Listeners.

The process of registering and dispatching Events can seem a bit like magic when you first encounter it, but it’s actually really simple when you peek under the hood.

The Domain Event interface

The first thing to do is to write the Event interface. All of our Domain Events should implement this interface so we know that the required methods are available:

<?php namespace Cribbb\Domain;

interface Event
{
    /**
     * Return the name of the Domain Event
     *
     * @return string
     */
    public function name();
}

So far we only require a single name() method to return the name of the Event.

The Listener interface

As with Events, all Listeners should implement the Listener interface so we know that the object has the methods we need available:

<?php namespace Cribbb\Domain;

interface Listener
{
    /**
     * Handler a Domain Event
     *
     * @return void
     */
    public function handle(Event $event);
}

Each Listener should have a handle() method that accepts an instance of Event. This means we have a generic way of passing any type of Event to any type of Listener without having to know what particular Event or Listener we are working with.

The Dispatcher

Now that we have the Event and Listener interfaces in place we can start to create Domain Events and Event Listeners in our project.

However in order to make the whole process work, we need to create the Dispatcher:

<?php namespace Cribbb\Domain;

class Dispatcher
{
}

As you can see, the Dispatcher is just a plain old PHP object, nothing special here.

At this point we can also create the DispatcherTest class too:

<?php namespace Cribbb\Tests\Domain;

use Cribbb\Domain\Dispatcher;

class DispatcherTest extends \PHPUnit_Framework_TestCase
{
    /** @var Dispatcher */
    private $dispatcher;

    public function setUp()
    {
        $this->dispatcher = new Dispatcher();
    }
}

In order for the Dispatcher to know what Listeners to invoke when a Domain Event is called, we need a way of associating Listeners with Events:

/**
 * @var array
 */
private $listeners;

/**
 * Add a Listener
 *
 * @param string $name
 * @param Listener $listener
 * @return void
 */
public function add($name, Listener $listener)
{
    $this->listeners[$name][] = $listener;
}

The first argument is the string name of the Event and the second argument is the Listener instance. We can store these registered events as a private array class property.

When an Event is passed to the Dispatcher, we need a way of getting all of the registered Listeners so we can pass the Event to each of them:

/**
 * Return the registered Listeners for a given Event name
 *
 * @param string $name
 * @return array
 */
public function registered($name)
{
    if (isset($this->listeners[$name])) {
        return $this->listeners[$name];
    }

    return [];
}

The registered() method will first check to see if any Listeners are registered for the Event and return them. If no Listeners are registered, an empty array can be returned.

We can test both the add() and registered() methods with the following tests:

/** @test */
public function should_return_empty_array_when_no_listeners_registered()
{
    $listeners = $this->dispatcher->registered('EventStub');

    $this->assertEquals([], $listeners);
}

/** @test */
public function should_add_listener()
{
    $this->assertEquals(0, count($this->dispatcher->registered('EventStub')));

    $this->dispatcher->add('EventStub', new ListenerStub);

    $this->assertEquals(1, count($this->dispatcher->registered('EventStub')));
}

The first test asserts that an empty array should be returned when there are no registered Listeners.

The second test asserts that when you are able to register a Listener with an Event. First we check to make sure there are no Listeners registered. Next we add a new Listener, and then we check to see if the Listener was registered.

You will notice that I’m using EventStub and ListenerStub in these tests. Stubs are an important part of testing an application and so I’ve created these reusable stubs as part of my code.

First I added a new namespace to my composer.json file:

"autoload-dev": {
    "psr-4": {
        "Cribbb\\Tests\\": "tests/",
        "Cribbb\\Stubs\\": "stubs/"
    }
}

Next I created the following two stub classes under the stubs directory:

<?php namespace Cribbb\Stubs;

use stdClass;
use Cribbb\Gettable;
use Cribbb\Domain\Event;

class EventStub implements Event
{
    use Gettable;

    /**
     * @var stdClass
     */
    private $counter;

    /**
     * Create a new Event Stub
     *
     * @param stdClass $counter
     * @return void
     */
    public function __construct(stdClass $counter)
    {
        $this->counter = $counter;
    }

    /**
     * Return the name of the Domain Event
     *
     * @return string
     */
    public function name()
    {
        return "EventStub";
    }
}
<?php namespace Cribbb\Stubs;

use Cribbb\Domain\Event;
use Cribbb\Domain\Listener;

class ListenerStub implements Listener
{
    /**
     * Handler a Domain Event
     *
     * @return void
     */
    public function handle(Event $event)
    {
        $counter = $event->counter;

        $counter->count++;
    }
}

Testing Domain Events

You will notice that I’ve my EventStub has a $counter object that is passed to the ListenerStub and incremented.

This allows me to write the following test:

/** @test */
public function should_handler_domain_event()
{
    $this->dispatcher->add('EventStub', new ListenerStub);

    $counter = new stdClass;
    $counter->count = 0;

    $event = new EventStub($counter);

    $this->dispatcher->dispatch([$event]);

    $this->assertEquals(1, $counter->count);
}

When the $counter is created it has a count of 0.

After the Event has been dispatched to the Listener, the count should be incremented to 1.

The problem with this test is, the data in an Event should be immutable. This means, it should not change. By writing a test that explicitly requires that the data does change, I’m violating the basic rules of Domain Events.

I’ve seen examples in other Open Source projects of testing Domain Events in various ways, but I’m still unsure of what the best approach is. If you have a suggestion, please leave a comment!

What goes in to a Domain Event?

Finally we should look at what goes in to a Domain Event. As I mentioned above, the data in a Domain Event should be immutable so that it doesn’t change.

So far in this series I’ve been passing an instance of the Entity into the Domain Event. However, this isn’t a good practice.

Instead you should pass in either an array of data attributes or an immutable object that makes those attributes available through methods or properties.

As we continue in this series you will see lots of practical examples of this!

Conclusion

Working in an industry where everyone shares their work through Open Source is amazing! Using someone else’s solution to a problem is usually by far the best decision you can make.

However, I do think there are times when you just need to write your own component.

I think you should avoid it whenever possible, but sometimes writing your own core functionality is the best bet. When you need to have complete control over your code, you can’t beat rolling your own.

If you are not interested in rolling your own Domain Event dispatcher I would highly recommend using this package.

Event based architecture can seem like magic when you first see it in action, but hopefully this post has shown you that it is actually very simple. We only need three basic plain old PHP classes to make the whole thing work.

However testing Domain Events is still a bit of a conundrum to me. I’m really not sure what the best approach is. I’ll probably look back on this post in a couple of weeks and laugh at how obvious the solution was, but for now I just don’t know.

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.