PHPnews.io

★ laravel-event-sourcing v3 has been released

Written by murze.be / Original link on Feb. 13, 2020

Our spatie/laravel-event-sourcing package is probably the best starting point for event sourcing in Laravel. It has excellent docs, that explain event sourcing from scratch, support for aggregates, projectors, and much more! It's all beautifully integrated in Laravel.

Recently we released v3 of the package. In this blogpost I'd like to walk you through the changes.

This post will probably not make much sense to you if you don't know anything about event sourcing. To get up to speed, watch this introduction video first.

Improve performance by using snapshots #

When an aggregate gets instantiate all events concerning that aggregate will be replayed on it first, so it has the right, current state. Typically, retrieving past events and replaying them is very fast, but if your aggregate needs a very large number of events to be replayed, this might take some time.

To solve this problem, we added snapshots to the package. A snapshot hold the state that an aggregate should have after a certain number of events have been replayed. By loading the aggregate you don't have to replay previous events anymore, and this improves performance.

You can create a new snapshot by calling the ->snapshot() method on your Aggregate.

$myAggregate = MyAggregate::retrieve($uuid);

$myAggregate->snapshot();

This will create a new snapshot in the snapshots table. By default we store the values of all the private, protected and public properties on the aggregate at that point in time. This is the default implementation of the getState method that gets all those properties.

protected function getState(): array
{
    $class = new ReflectionClass($this);

    return collect($class->getProperties())
        ->mapWithKeys(function (ReflectionProperty $property) {
            return [$property->getName() => $this->{$property->getName()}];
        })->toArray();
}

If you don't want all those properties in your snapshot, just override the getState() and useState() methods on the aggregate and implement your own behaviour.

When you retrieve the aggregate the next time, it will find the snapshot, set its internal properties back to what they were at the time of the snapshot, and apply any new events starting from the snapshot.

This uses the snapshots aggregateVersion, this version number is incremented each time an event is applied by the aggregate and stored in the snapshot.

This feature was added by Rias who did a great job!

Better protection against concurrency #

To make snapshots work, we introduced the concept of a version. The version of an aggregate will increment for each events that was saved. This version will also protected your aggregate against concurrent persists. This can happen when there are two concurrent requests that try to update the same aggregate.

When reconstituting an aggregate from previous events the aggregate will remember the highest version id for that aggregate. If that highest version id differs differs from the highest one that the aggregate remembers, an exception will be thrown.

If concurrent persists should be allowed, you can set the $allowConcurrency static property on the aggregate to true.

use Spatie\EventSourcing\AggregateRoot;

class AggregateRootThatAllowsConcurrency extends AggregateRoot
{
    protected static bool $allowConcurrency = true;
}

This feature was suggested by Dries Vints.

In closing #

I'm pretty happy with how the snapshot feature turned out, a lot of the users of the package were asking for this and I think we deliver a good, elegant solution.

To know more about the package, head over to the extensive documentation.

murze

« Validating default PHP session ID values - Migrating to PHPUnit 9 »