Mixing event sourcing in a traditional Laravel app
Tags: php, eventsourcing
Published: 2020-05-04 12:15:02

Together with my colleague [Brent](https://twitter.com/brendt_gd), I'm working on designing the architecture of a massive Laravel application. In that application, we'll have traditional parts and event sourced parts. In this blog post, I'd like to give a practical example of how we think to achieve this.
This post is the second one in a two-part series. I highly encourage [to read the first part](https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems) before continuing to read this post. You also should have a good understanding of event sourcing, the role of aggregates, and projectors. Get up to speed by going through [this introduction](https://docs.spatie.be/laravel-event-sourcing/v3/introduction/).
 
## Setting the stage

First, let's do a quick recap of [Brent's post](https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems). We need event sourcing in our application. In certain parts, we need to make decisions based on the past and, as time progresses, we need to be able to generate reports without knowing upfront what should be in those reports. 

These problems can be solved by using event sourcing. We just store all events that happen in the app, so we can have aggregate roots that can make decisions based on the past when they are reconstituted from the events. We can generate new reports by creating projectors and replaying all recorded events to them. 

A wise man, by the name of Frank de Jonge, once said: "Event sourcing makes the hard parts easy, and the easy parts hard". And it's true. Setting up event sourcing takes some time, and it's not as straightforward. Some overhead gets in the way when you want to do simple things.

In our application, we also have parts that are rather cruddy and don't need the power of event sourcing. Wouldn't it be nice to be able to have the best of both worlds in a single app? Brent and I researched this subject and pair programmed on some experiments. We'd like to share one of these experiments with you.

## A practical example

We're going to implement some of the parts mentioned in [Brent's post](https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems): products and orders. `Product`  is a boring cruddy part. We don't need history nor reports here. For the orders part, we'll use event sourcing. 

You can find the source code of the example in [this repo on GitHub](https://github.com/spatie/laravel-context-demo).  Keep in mind that this is a very simple example. In a real-world app the logic involved would probably be more complicated. In this experiment, we're just concentrating on how such to part should/would communicate with each other.

### The non-event-sourced part

If you take a look in the `Context` directory, you'll see the two parts: `Order` and `Product`. Let's take a look at `Product first:

![screenshot](https://freek.dev/uploads/media/context/product.png)

As you can see, this part is very simple: we just have models and events. The idea here is that we can just build it as we're used to, with not too much overhead. The only thing that we need to do is to fire off specific events when something changes, so the other parts of the app can hook into that. 

In the `Product` model [you'll see this code](https://github.com/spatie/laravel-context-demo/blob/dc52265fe620bc709dc0acb71ab1536acc53e10b/app/Context/Product/Models/Product.php):

```
protected $dispatchesEvents = [
    'created' => ProductCreatedEvent::class,
    'updated' => ProductUpdatedEvent::class,
    'deleting' => DeletingProductEvent::class,
    'deleted' => ProductDeletedEvent::class,
];
```

In `$dispatchesEvents`, you can define which events should be fired when certain things happen to the model. This is standard Laravel functionality. Sure, we could rely on the events that eloquent natively fires, but we want to make this very explicit by using event classes of our own.

Let's take a look at [one of these events](https://github.com/spatie/laravel-context-demo/blob/dc52265fe620bc709dc0acb71ab1536acc53e10b/app/Context/Product/Events/ProductCreatedEvent.php).

```
namespace App\Context\Product\Events;

use App\Context\Product\Models\Product;

class ProductCreatedEvent
{
    public Product $product;

    public function __construct(Product $product)
    {
        $this->product = $product;
    }
}
```

As you can see, there's nothing special going on here. We're just using the model as an event property.

### The event sourced part

Let's now turn our eye to the `Order` part of the app, which is event sourced. This is how that structure looks like:

![screenshot](https://freek.dev/uploads/media/context/order.png)

You see projectors and an aggregate root here, so it's clear that this part uses event sourcing. We're using [our homegrown event sourcing package](https://docs.spatie.be/laravel-event-sourcing) to handle the logic around storing events, projectors, aggregate roots, ...

In the `Product` part of the app, we fired off events. These events were just regular Laravel events, we didn't store them. The `ProductEventSubscriber` is listening for these events. Let's take a look at the code.

```
namespace App\Context\Order\Subscribers;

use App\Context\Product\Events\DeletingProductEvent as AdminDeletingProductEvent;
use App\Context\Product\Events\ProductCreatedEvent as AdminProductCreatedEvent;
use App\Context\Order\Events\ProductCreatedEvent;
use App\Context\Order\Events\ProductDeletedEvent;
use App\Support\Events\EventSubscriber as BaseEventSubscriber;
use App\Support\Events\SubscribesToEvents;

class ProductEventSubscriber implements BaseEventSubscriber
{
    use SubscribesToEvents;

    protected array $handlesEvents = [
        AdminProductCreatedEvent::class => 'onProductCreated',
        AdminDeletingProductEvent::class => 'onDeletingProduct',
    ];

    public function onProductCreated(AdminProductCreatedEvent $event): void
    {
        $event = $event->product;

        event(new ProductCreatedEvent(
            $event->getUuid(),
            $event->stock,
        ));
    }

    public function onDeletingProduct(AdminDeletingProductEvent $event): void
    {
        $event = $event->product;

        event(new ProductDeletedEvent(
            $event->getUuid(),
        ));
    }
}
```

What happens here is quite interesting. We are going to listen for the events that are coming from the non event sourced part. When an event from the `Product` context, that is interesting to the `Order` context, comes in, we are going to fire off another event. That other event is part of the `Product` context. 

So when `App\Context\Product\Events\ProductCreatedEvent` comes in (and it is aliased to `AdminProductCreatedEvent` because probably an action by an admin on the UI will have caused that action), we are going to fire off `App\Context\Order\Events\ProductCreatedEvent` (from the `Order` context).

We are going to take all the properties that are interesting to `Order` context and put them in the `App\Context\Order\Events\ProductCreatedEvent`.

```
public function onProductCreated(AdminProductCreatedEvent $event): void
{
    $event = $event->product;

    event(new ProductCreatedEvent(
        $event->getUuid(),
        $event->stock,
    ));
}
```

Let's take a look at that event itself.

```
namespace App\Context\Order\Events;

use App\Support\ValueObjects\ProductUuid;
use Spatie\EventSourcing\ShouldBeStored;

class ProductCreatedEvent extends ShouldBeStored
{
    public ProductUuid $productUuid;

    public int $stock;

    public function __construct(ProductUuid $productUuid, int $stock)
    {
        $this->productUuid = $productUuid;

        $this->stock = $stock;
    }
}

```

You can see that this event extends `ShouldBeStored`. That base class is part of our event sourcing package. This will cause the event to be stored.

We immediately use that stored event to build up a projection that holds the stock. Let's take a look at `ProductStockProjector`.

```
namespace App\Context\Order\Projectors;

use App\Context\Order\Events\OrderCancelledEvent;
use App\Context\Order\Events\ProductCreatedEvent;
use App\Context\Order\Events\ProductDeletedEvent;
use App\Context\Order\Events\OrderCreatedEvent;
use App\Context\Order\Models\Order;
use App\Context\Order\Models\ProductStock;
use App\Support\ValueObjects\ProductStockUuid;
use Spatie\EventSourcing\Projectors\Projector;
use Spatie\EventSourcing\Projectors\ProjectsEvents;

class ProductStockProjector implements Projector
{
    use ProjectsEvents;

    protected array $handlesEvents = [
        ProductCreatedEvent::class => 'onProductCreated',
        ProductDeletedEvent::class => 'onProductDeleted',
        OrderCreatedEvent::class => 'onOrderCreated',
        OrderCancelledEvent::class => 'onOrderCancelled',
    ];

    public function onProductCreated(ProductCreatedEvent $event): void
    {
        ProductStock::create([
            'uuid' => ProductStockUuid::create(),
            'product_uuid' => $event->productUuid,
            'stock' => $event->stock,
        ]);
    }

    public function onProductDeleted(ProductDeletedEvent $event): void
    {
        ProductStock::forProduct($event->productUuid)->delete();
    }

    public function onOrderCreated(OrderCreatedEvent $event): void
    {
        $productStock = ProductStock::forProduct($event->productUuid);

        $productStock->update([
            'stock' => $productStock->stock - $event->quantity,
        ]);
    }

    public function onOrderCancelled(OrderCancelledEvent $event): void
    {
        $order = Order::findByUuid($event->aggregateRootUuid());

        $productStock = ProductStock::forProduct($order->product->uuid);

        $productStock->update([
            'stock' => $productStock->stock + $order->quantity,
        ]);
    }
}
```

When `ProductCreatedEvent` is fired, we will call `ProductStock::create`. `ProductStock` is a regular Eloquent model.

```
namespace App\Context\Order\Models;

use App\Context\Product\Models\Product;
use Illuminate\Database\Eloquent\Model;

class ProductStock extends Model
{
    /**
     * @param \App\Context\Product\Models\Product|\App\Support\ValueObjects\ProductUuid $productUuid
     *
     * @return \App\Context\Order\Models\ProductStock
     */
    public static function forProduct($productUuid): ProductStock
    {
        if ($productUuid instanceof Product) {
            $productUuid = $productUuid->getUuid();
        }

        return static::query()
            ->where('product_uuid', $productUuid)
            ->first();
    }
}
```

In the `ProductStockProjector` the `ProductStock` model will be updated when orders come in, when they get canceled, or when an order is deleted.

Currently, we have an agreement with every member working on the project that it is not allowed to write to model from other contexts. The `Order` context may write and read from this model, but the `Product` context may only read it. Because this is such an important rule, we will probably technically enforce it soon.

Finally, let's take a look at the `OrderAggregateRoot` where the `ProductStock` model is being used to make a decision.

```
namespace App\Context\Order;

use App\Context\Product\Models\Product;
use App\Context\Order\Events\CouldNotCreateOrderBecauseInsufficientStock;
use App\Context\Order\Events\OrderCreatedEvent;
use App\Context\Order\Events\OrderCancelledEvent;
use App\Context\Order\Exceptions\CannotCreateOrderBecauseInsufficientStock;
use App\Context\Order\Models\ProductStock;
use Spatie\EventSourcing\AggregateRoot;

class OrderAggregateRoot extends AggregateRoot
{
    public function createOrder(Product $product, int $quantity): self
    {
        $eventAvailability = ProductStock::forProduct($product);

        if ($eventAvailability->availability < $quantity) {
            $this->recordThat(new CouldNotCreateOrderBecauseInsufficientStock(
                $product->getUuid(),
                $quantity
            ));

            throw CannotCreateOrderBecauseInsufficientStock::make();
        }

        $unitPrice = $product->unit_price;

        $totalPrice = $unitPrice * $quantity;

        $this->recordThat(new OrderCreatedEvent(
            $product->getUuid(),
            $quantity,
            $unitPrice,
            $totalPrice,
        ));

        return $this;
    }

    public function cancelOrder(): self
    {
        $this->recordThat(new OrderCancelledEvent());

        return $this;
    }
}
``` 

In a complete app, the `createOrder` function would probably be triggered by an action that is performed in the UI. A product can be ordered for a certain amount. Using the `ProductStock,` the aggregate root will decide if there is enough stock for the order to be created.

## Summary

A non event sourced part of the app fires of regular events. The event sourced parts listen for these events and record data interesting for them in events of their own. These events, own to the context, will be stored. These stored events are being used to feed projectors. These projectors create models that can be read by the non-event-sourced parts. The non-events-sourced parts may only read these models. Aggregate roots can use the events and projection to make decisions.

This approach brings a lot of benefits:
- a non-event-sourced part can be built like we're used to. We just have to take care that events are being fired
- an event sourced part can record its own events to act on
- we could create new projectors and replay all recorded events to build up new state

I know this all is a lot to take in. This way of doing things is by no means set in stone for us. We're in a discovery phase, but we have a good feeling about what we've already discovered. Brent and/or I will probably write some follow-up posts soon.