Initial reading material: https://eventsauce.io/docs/event-sourcing/
What will we be using?
- EventSaucePHP/EventSauce
- EventSaucePHP/LaravelEventSauce (WIP)
- laravel/framework
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:
- lightning-fast reads,
- predict the future // save all the state you've ever gotten
- time travel // back to a past state
- auditing
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
- eventsauce.io/docs/event-sourcing
- eventsauce.io/docs/architecture
GitHub spatie/laravel-eventsauce and spatie/laravel-event-projector packages already exist!
- driesvints.com
- @driesvints
- fullstackbelgium.be
- fullstackeurope.com