Event Sourcing in Laravel with EventSauce
Dries Vints

Slides: https://eventsauce.io/docs/event-sourcing/

Initial reading material: https://eventsauce.io/docs/event-sourcing/

What will we be using?

What is EventSauce?

It's an easy way to introduce event sourcing into PHP projects.

Using GitHub and Git-Workflow as domain and process

Just to clear up confusion

In this talk, Model isn't referring to an Eloquent Model, Command isn't referring to a Console Command

What is event sourcing?

Well, a bit like magic. It brings along other constraints implementing it. But you get a good bit out of it.

Writing a model without event sourcing, CRUD-like operations performened on models which will overwrite any state we currently have in our persistent storage

With event sourcing, Commands performed which validate data based on business rules, then events are emitted and saved in an event store

$pr->open(new OpenPullRequest(...));
// emits OpensPullRequest

Read model without ES, data needs to be fetched from three individual tables: pull requests, authors and comments.

With event sourcing, you make reads for their specific use case. Data can be fetched from only one table: pull_requests

$pr = PullRequest::find($id);
$author = $pullRequest['author'];

Reads become easier, writes become a little harder

Event sourcing makes the easy thing hard but the hard thing easy

No matter what, history doesn't change. Since you're stacking events and saving that historical state and deriving state from those historical events...yeah, can't change history.

So...why event source?

Not much time, but cool stuff you can do:

How do you implement event sourcing?

First, the traditional stateful way.

Store method gets the user, creates a pull request on the user, redirects the user based on the created pull request's ID

For the event sourcing way, dispatch an openPullRequest with a generated ID, user ID, and all the other fields. Then redirect the user to the generated ID

Use commands to transfer data, retrieve aggregate root and handle commands

Eloquent Models vs AggregateRoots

Eloquent Models represent your data model, but AggregateRoots represent your processes and guard your business rules. AggregateRoots can consist of multiple Aggregates

final class PullRequest implements AggregateRoot
{
  public function open(OpenPullRequest $command): void {
    if ($command->origin() === $command->target()) {
      throw OpenPullRequestException()
    }
  }
}

You can also record the unhappy path ($this->recordThat(new UnapprovedMergeWasAttempted()); throw MergePullRequestException;)

Recording the event

protected function recordThat(object $event)

Applying events

$this->{'apply' . $name}

Applying the event is also run every time the aggregate is retrieved from its repository

Persisting events. Exceptions cascade through, but events are still saved (like using a try-finally)

Rebuilding the aggregate. Root has a reconstituteFromEvents method

Event store can be stored like in a database table (domain_messages with id, event_id, event_type, aggregate_root_id, recorded_at, payload)

Event stores are append- and read-only.

It can live outside the main system, live independlty by AggregateRoot, and it's the only part you can't lose!

Domain messages, events transported.

Dispatching messages: messages are dispatched by the message dispatcher. Can be asyncronous, synchronous, both. Messages handled by Consumers

Consumers

Projections (processes events to update read models), and process managers (listen to events and perform some action)

Why are projections useful? Super-optimized. Separate presentational state from process you're modelling, read models are free from any constraints your domain model has, your read models can be very optimized, create as many read models as you like...

Rebuilding projections...allows you to discard your current state of your read model and rebuild it from its event stream. Unfortunately, not baked into EventSauce, but maybe in Laravel/EventSauce?

Can rebuild from a backup, might look into that

Process mangers used to react to events (e.g. send email notices, trigger builds, clear logs...)

Useful for one-time actions, separate multiple steps for your process, handle non-user interactions, can be performed in the background and aren't blocking

Process manager might look like this (handle method, check the event instanceof)

Other things EventSauce can do

Generate commands/events from yaml, message decoration to add metadata to your messages, event versioning handling concurrency, testing in a BDD-like way (given X, when Y, then Z)

Sorry, I know that was a lot, but event sourcing is a really cool thing. Not for every single scenario, but it really shines when you have a really rich domain, lot of stakeholders, lot of business rules

Reading Material

GitHub spatie/laravel-eventsauce and spatie/laravel-event-projector packages already exist!