★ Implementing event sourcing: improving the developer experience
Tags: php, laravel
Published: 2020-01-02 23:23:13

---
title: ★ Implementing event sourcing: improving the developer experience
published: true
date: 2019-04-25 23:28:06 UTC
tags: php, laravel
canonical_url: https://freek.dev/1329-implementing-event-sourcing-improving-the-developer-experience
---

Recently we've released [v2 of laravel-event-projector](https://freek.dev/laravel-event-projector-v2-has-been-released). The package is probably the easiest way to get started with event sourcing in Laravel. In v2 we've introduced two "invisible" features that improve the developer experience: auto-detection of event handling methods and auto-discovery of event handlers.

Even if you don't know anything about event sourcing, you should be able to follow most of this post. To get a basic understanding of what our package does, head over to [the intro section of the laravel-event-projector docs](https://docs.spatie.be/laravel-event-projector/v2/getting-familiar-with-event-sourcing/introduction).

## What are event handlers? #

Event handlers are classes that will get called when certain events are fired. Here's an example projector (a projector is a particular type of event handler).

```php
class BalanceProjector implements Projector
{
    use ProjectsEvents;

    protected $handlesEvents = [
        MoneyAddedEvent::class => 'onMoneyAdded',
        MoneySubtractedEvent::class => 'onMoneySubtracted',
    ];

    public function onMoneyAdded(MoneyAddedEvent $event)
    {
        // do some work
    }

    public function onMoneySubtracted(MoneySubtractedEvent $event)
    {
        // do some work
    }
}
```

## Auto detect event handling methods #

That `handlesEvent` array determines which functions should should get called when specific events come in. For example, when this projector receives a `MoneyAddedEvent` the `onMoneyAdded` function should get called. If you take a look at that function, you see that the event we want to receive is type hinted.

Wouldn't it be cool if users wouldn't have to code up an `handlesEvent` array and that the package would automatically call functions that typehint an event? In the newest version of event-projector, we did just that.

Let's take a look at the relevant code from [the `HandlesEvents`-trait](https://github.com/spatie/laravel-event-projector/blob/47f9d7a6479a1860e91956229a4463a706559c68/src/EventHandlers/HandlesEvents.php) which is applied to every event handler. The `autoDetectHandlesEvents` will use some reflection to get each method on the event handler. Next, for each function that was found, it will take a look if the first parameter is a class that implements `ShouldBeStored`.

```php
private function autoDetectHandlesEvents(): Collection
{
    return collect((new ReflectionClass($this))->getMethods())
        ->flatMap(function (ReflectionMethod $method) {
            $method = new ReflectionMethod($this, $method->name);
            $eventClass = collect($method->getParameters())
                ->map(function (ReflectionParameter $parameter) {
                    return optional($parameter->getType())->getName();
                })
                ->first(function ($typeHint) {
                    return is_subclass_of($typeHint, ShouldBeStored::class);
                });
                
            if (! $eventClass) {
                return;
            }
            
            return [$eventClass => $method->name];
        })
        ->filter();
```

The `autoDetectHandlesEvents` will return an array that [is used](https://github.com/spatie/laravel-event-projector/blob/47f9d7a6479a1860e91956229a4463a706559c68/src/EventHandlers/HandlesEvents.php#L21-L42) to route the events to the right method name.

## Auto register event handlers #

In v1 of the package, each event handler had to be registered at the `Projectionist`. The `Projectionist` is a class that will make sure that event handlers will get called when certain events are fired.

```php
Projectionist::addProjector(MyProjector::class);
Projectionist::addReactor(MyReactor::class);
```

In the newest version of the package, you don't need to do this manually anymore. The package will automatically find and use event handlers. The functionality is inspired by how Laravel [auto discovers its events and event listeners](https://blog.laravel.com/automatic-event-listener-discovery). Let's take a look at how event handler auto-discovery works in our package.

In the [service provider](https://github.com/spatie/laravel-event-projector/blob/a7d6eb956ec7f225aa2fe0cd85ff81710dbe048c/src/EventProjectorServiceProvider.php) you'll find the `discoverEventHandlers` function which [is called](https://github.com/spatie/laravel-event-projector/blob/47f9d7a6479a1860e91956229a4463a706559c68/src/EventProjectorServiceProvider.php#L35) from the `boot` method.

```php
private function discoverEventHandlers()
{
    $projectionist = app(Projectionist::class);

    $cachedEventHandlers = $this->getCachedEventHandlers();

    if (! is_null($cachedEventHandlers)) {
        $projectionist->addEventHandlers($cachedEventHandlers);

        return;
    }

    (new DiscoverEventHandlers())
        ->within(config('event-projector.auto_discover_projectors_and_reactors'))
        ->useBasePath(base_path())
        ->ignoringFiles(Composer::getAutoloadedFiles(base_path('composer.json')))
        ->addToProjectionist($projectionist);
}
```

If the event handlers are cached, it'll use those cached registrations and not perform auto-discovery. We'll talk about this later.

The real auto-discovering happens inside the `DiscoverEventHandlers` class. We pass it the directory where to look for the event handlers classes, by default this will be `app_path()`. We'll also pass it the `base_path()`. This is needed `DiscoverEventHandlers` will use that path to convert path names to class names.

Let's take a look at the implementation last function in the chain: `addToProjectionist`. This is the most important function of [`DiscoverEventHandlers`](https://github.com/spatie/laravel-event-projector/blob/47f9d7a6479a1860e91956229a4463a706559c68/src/DiscoverEventHandlers.php):

```php
public function addToProjectionist(Projectionist $projectionist)
{
    $files = (new Finder())->files()->in($this->directories);

    return collect($files)
        ->reject(function (SplFileInfo $file) {
            return in_array($file->getPathname(), $this->ignoredFiles);
        })
        ->map(function (SplFileInfo $file) {
            return $this->fullQualifiedClassNameFromFile($file);
        })
        ->filter(function (string $eventHandlerClass) {
            return is_subclass_of($eventHandlerClass, EventHandler::class);
        })
        ->pipe(function (Collection $eventHandlers) use ($projectionist) {
            $projectionist->addEventHandlers($eventHandlers->toArray());
        });
}
```

First, it will get all the files inside the directory where we should be looking for event handlers (as mentioned before this is `app_path()` by default).

Next, we will reject some files we don't want to process now. By default, these are non-class files that are loaded up by composer.

After that we will map each file path to the class should be in there. So [the `fullQualifiedClassNameFromFile` function](https://github.com/spatie/laravel-event-projector/blob/a7d6eb956ec7f225aa2fe0cd85ff81710dbe048c/src/DiscoverEventHandlers.php#L73-L84) will convert `/home/username/myproject.com/app/Projectors/MyProjector.php` to `App\Projectors\MyProjector`.

We'll continue by filtering for all classes that extend our `EventHandler` class. And finally, all remaining classes will be registered at the projectionist.

Now, you probably don't want to scan all of the classes when you're in production. That's why the package also contains a command to [cache all registered event handlers](https://github.com/spatie/laravel-event-projector/blob/master/src/Console/CacheEventHandlersCommand.php). In short this class will write all registered event handler classes to a file. Like we've already seen, the service provider will [check for the existence of that file](https://github.com/spatie/laravel-event-projector/blob/a7d6eb956ec7f225aa2fe0cd85ff81710dbe048c/src/EventProjectorServiceProvider.php#L97-L103) and not perform auto-discovery when that file is present.

## In closing #

With auto-detection of event handling methods and auto-discovery of event handlers, users of the package can simply create a class like this in their project, and it'll just work. No need route events to methods yourself and no need to register anything.

```php
class BalanceProjector implements Projector
{
    use ProjectsEvents;

    public function onMoneyAdded(MoneyAddedEvent $event)
    {
        // perform some work
    }

    public function onMoneySubtracted(MoneySubtractedEvent $event)
    {
        // perform some work
    }
}
```

I believe this kind of polishing makes a big difference for the users of our package. Thanks you to [Sebastian De Deyne](https://twitter.com/sebdedeyne) for [suggesting](https://github.com/spatie/laravel-event-projector/issues/143) these features.

If you want to know more about laravel-event-projector itself, head over to [the docs of the package](https://docs.spatie.be/laravel-event-projector/v2/introduction).