Slide 1

Slide 1 text

 @driesvints  driesvints.com EVENT SOURCING IN LARAVEL EVENT SOURCING IN LARAVEL WITH EVENTSAUCE WITH EVENTSAUCE

Slide 2

Slide 2 text

 @driesvints  driesvints.com WHAT WILL WE BE USING? WHAT WILL WE BE USING? eventsauce/eventsauce eventsauce/laravel-eventsauce (0.1.0) laravel/framework

Slide 3

Slide 3 text

 @driesvints  driesvints.com WHAT IS EVENTSAUCE? WHAT IS EVENTSAUCE? An easy way to introduce event sourcing into PHP projects Designed so storage and queueing mechanisms can be chosen based on your specific requirements It has test tooling, designed to work with an event sourcing mindset By Frank De Jonge (Author of FlySystem)

Slide 4

Slide 4 text

 @driesvints  driesvints.com OUR DOMAIN & PROCESS OUR DOMAIN & PROCESS    

Slide 5

Slide 5 text

 @driesvints  driesvints.com MODEL =/= ELOQUENT MODEL MODEL =/= ELOQUENT MODEL COMMAND =/= CONSOLE COMMAND COMMAND =/= CONSOLE COMMAND

Slide 6

Slide 6 text

 @driesvints  driesvints.com WHAT IS EVENT SOURCING? WHAT IS EVENT SOURCING?

Slide 7

Slide 7 text

 @driesvints  driesvints.com WELL, IT'S A LITTLE BIT LIKE MAGIC... WELL, IT'S A LITTLE BIT LIKE MAGIC...

Slide 8

Slide 8 text

 @driesvints  driesvints.com eventsauce.io/docs/event-sourcing Event sourcing is a way to model so ware where the emphasis is put on why (and how) things change, rather than focusing on the current state of the applica on.

Slide 9

Slide 9 text

 @driesvints  driesvints.com eventsauce.io/docs/event-sourcing Ac ons performed on the system result in events, which are communicated throughout the system.

Slide 10

Slide 10 text

 @driesvints  driesvints.com eventsauce.io/docs/event-sourcing Regular OOP-style modelling is closely modelled towards the data model. Our code reflects this data-centric view. When we test our code, we assert based on current applica on state.

Slide 11

Slide 11 text

 @driesvints  driesvints.com eventsauce.io/docs/event-sourcing In event sourcing this concept is turned upside-down. The model is constructed using events that describe things that have happened in the past. These events are used to communicate change throughout the system.

Slide 12

Slide 12 text

 @driesvints  driesvints.com COMMAND QUERY RESPONSIBILITY COMMAND QUERY RESPONSIBILITY SEGREGATION (CQRS) SEGREGATION (CQRS) Separate your read model from your write model Easily separate concerns and constraints Goes really well alongside Event Sourcing

Slide 13

Slide 13 text

 @driesvints  driesvints.com WRITE MODEL WITHOUT EVENT SOURCING WRITE MODEL WITHOUT EVENT SOURCING CRUD like opera ons are performed on models which will overwrite any state we currently have in our persistent storage. $pullRequest = PullRequest::create($data); $pullRequest->update($data); $pullRequest->delete();

Slide 14

Slide 14 text

 @driesvints  driesvints.com WRITE MODEL WITH EVENT SOURCING WRITE MODEL WITH EVENT SOURCING Commands are performed which will validate the data based on your business rules. Events are emi ed and saved in an event store. $pullRequest->open(new OpenPullRequest(...)); // emits PullRequestWasOpened $pullRequest->approve(new ApprovePullRequest(...)); // emits PullRequestWasApproved $pullRequest->merge(new MergePullRequest(...)); // emits PullRequestWasMerged

Slide 15

Slide 15 text

 @driesvints  driesvints.com READ MODEL WITHOUT EVENT SOURCING READ MODEL WITHOUT EVENT SOURCING Data needs to be fetched from three individual tables: pull_requests, authors & comments $pullRequest = PullRequest::with('author', 'comments') ->find($id); $author = $pullRequest->author; $comments = $pullRequest->comments;

Slide 16

Slide 16 text

 @driesvints  driesvints.com READ MODEL WITH EVENT SOURCING READ MODEL WITH EVENT SOURCING Instead of modelling for mul ple use-cases, you model your reads for their specific use-case. Data can be fetched from only one table: pull_requests $pullRequest = PullRequest::find($id); $author = $pullRequest['author']; $comments = json_decode($pullRequest['comments'], true);

Slide 17

Slide 17 text

 @driesvints  driesvints.com READS BECOME EASIER READS BECOME EASIER WRITES BECOME HARDER WRITES BECOME HARDER

Slide 18

Slide 18 text

 @driesvints  driesvints.com EVENT SOURCING... EVENT SOURCING... ...MAKES THE EASY THING HARD ...MAKES THE EASY THING HARD ...BUT MAKES THE HARD THING EASY ...BUT MAKES THE HARD THING EASY

Slide 19

Slide 19 text

 @driesvints  driesvints.com HISTORY NEVER CHANGES HISTORY NEVER CHANGES NO MATTER WHAT NO MATTER WHAT

Slide 20

Slide 20 text

 @driesvints  driesvints.com

Slide 21

Slide 21 text

 @driesvints  driesvints.com WHY WOULD YOU NEED EVENT SOURCING? WHY WOULD YOU NEED EVENT SOURCING?

Slide 22

Slide 22 text

 @driesvints  driesvints.com COOL STUFF YOU CAN DO COOL STUFF YOU CAN DO ⚡ Lightning Fast Reads ✨ Predict The Future ⏳ Time Travel Audi ng And much more...

Slide 23

Slide 23 text

 @driesvints  driesvints.com WHEN SHOULD YOU USE EVENT SOURCING? WHEN SHOULD YOU USE EVENT SOURCING?

Slide 24

Slide 24 text

 @driesvints  driesvints.com PROCESS MODELLING PROCESS MODELLING Finite flows, complex transi ons and transac ons. eventsauce.io/docs/event-sourcing

Slide 25

Slide 25 text

 @driesvints  driesvints.com AUDIT TRAILS AUDIT TRAILS Cases where audit trails are of paramount importance. eventsauce.io/docs/event-sourcing

Slide 26

Slide 26 text

 @driesvints  driesvints.com WHEN SHOULD YOU NOT USE IT? WHEN SHOULD YOU NOT USE IT? When you have a CMS, CRM, or other CRUD-like systems, maybe you don't need event sourcing.

Slide 27

Slide 27 text

 @driesvints  driesvints.com

Slide 28

Slide 28 text

 @driesvints  driesvints.com HOW DO YOU IMPLEMENT EVENT SOURCING? HOW DO YOU IMPLEMENT EVENT SOURCING?

Slide 29

Slide 29 text

 @driesvints  driesvints.com THE TRADITIONAL STATEFUL WAY THE TRADITIONAL STATEFUL WAY public function store(Request $request) { $user = $request->user(); $pullRequest = $user->pullRequests()->create( $request->only( 'title', 'description', 'origin', 'target' ) ); return redirect( '/laravel/framework/pull/'.$pullRequest->id ); }

Slide 30

Slide 30 text

 @driesvints  driesvints.com THE EVENT SOURCING WAY THE EVENT SOURCING WAY public function store(Request $request) { $this->dispatchNow(new OpenPullRequest( $id = PullRequestId::generate(), // UUID $request->user()->id, $request->get('title'), $request->get('description'), $request->get('origin'), $request->get('target') )); return redirect( '/laravel/framework/pull/'.$id->toString() ); }

Slide 31

Slide 31 text

 @driesvints  driesvints.com USE COMMANDS TO TRANSFER DATA USE COMMANDS TO TRANSFER DATA final class OpenPullRequest { // Properties... public function __construct( PullRequestId $pullRequestId, int $userId, string $title, string $description, string $origin, string $target ) { // Assign dependencies to properties... } }

Slide 32

Slide 32 text

 @driesvints  driesvints.com RETRIEVE AGGREGATEROOT AND HANDLE COMMAND RETRIEVE AGGREGATEROOT AND HANDLE COMMAND use PullRequestAggregateRootRepository as Repository; final class OpenPullRequest { // Properties, constructor and getters... public function handle(Repository $repository): void { $pullRequest = $repository->retrieve( $this->pullRequestId ); $pullRequest->open($this); } }

Slide 33

Slide 33 text

 @driesvints  driesvints.com HOLD ON... THE AGGRE-WHAT? HOLD ON... THE AGGRE-WHAT?

Slide 34

Slide 34 text

 @driesvints  driesvints.com ELOQUENT MODELS VS AGGREGATEROOTS ELOQUENT MODELS VS AGGREGATEROOTS Eloquent Models represent your data model AggregateRoots represent your processes and guard your business rules AggregateRoots can consist of mul ple Aggregates

Slide 35

Slide 35 text

 @driesvints  driesvints.com THE AGGREGATEROOT THE AGGREGATEROOT namespace App\Domain\PullRequest; use EventSauce\EventSourcing\AggregateRoot; use EventSauce\EventSourcing\AggregateRootBehaviour; final class PullRequest implements AggregateRoot { use AggregateRootBehaviour; // ... }

Slide 36

Slide 36 text

 @driesvints  driesvints.com HANDLING THE COMMAND HANDLING THE COMMAND final class PullRequest implements AggregateRoot { public function open(OpenPullRequest $command): void { // Validate business rules... $this->recordThat(new PullRequestWasOpened( $command->userId(), $command->title(), $command->description(), $command->origin(), $command->target() )); } }

Slide 37

Slide 37 text

 @driesvints  driesvints.com VALIDATING BUSINESS RULES VALIDATING BUSINESS RULES final class PullRequest implements AggregateRoot { public function open(OpenPullRequest $command): void { if ($command->origin() === $command->target()) { throw OpenPullRequestFailed::branchesMatch(); } // Record event... } }

Slide 38

Slide 38 text

 @driesvints  driesvints.com RECORDING THE UNHAPPY PATH RECORDING THE UNHAPPY PATH final class PullRequest implements AggregateRoot { public function merge(MergePullRequest $command): void { if ($this->approved === false) { $this->recordThat( new UnapprovedMergeWasAttempted() ); throw MergePullRequestFailed::unapproved(); } // Record event... } }

Slide 39

Slide 39 text

 @driesvints  driesvints.com RECORDING THE EVENT RECORDING THE EVENT trait AggregateRootBehaviour { /** @var object[] */ private $recordedEvents = []; // ... protected function recordThat(object $event) { $this->apply($event); $this->recordedEvents[] = $event; } // ... }

Slide 40

Slide 40 text

 @driesvints  driesvints.com APPLYING EVENTS APPLYING EVENTS trait AggregateRootBehaviour { // ... protected function apply(object $event) { $parts = explode('\\', get_class($event)); $this->{'apply' . end($parts)}($event); $this->aggregateRootVersion++; } // ... }

Slide 41

Slide 41 text

 @driesvints  driesvints.com APPLYING THE EVENT APPLYING THE EVENT Is also run every me the aggregate is retrieved from its repository. final class PullRequest implements AggregateRoot { public function applyPullRequestWasOpened( PullRequestWasOpened $event ): void { $this->userId = $command->userId(); $this->origin = $command->origin(); $this->target = $command->target(); } }

Slide 42

Slide 42 text

 @driesvints  driesvints.com PERSISTING EVENTS PERSISTING EVENTS Excep ons cascade through but events are s ll saved. public function handle(Repository $repository): void { $pullRequest = $repository->retrieve( $this->pullRequestId ); try { $pullRequest->open($this); } finally { $repository->persist($pullRequest); } }

Slide 43

Slide 43 text

 @driesvints  driesvints.com REBUILDING THE AGGREGATE REBUILDING THE AGGREGATE trait AggregateRootBehaviour { public static function reconstituteFromEvents( AggregateRootId $aggregateRootId, Generator $events ): AggregateRoot { $aggregateRoot = new static($aggregateRootId); foreach ($events as $event) { $aggregateRoot->apply($event); } return $aggregateRoot; } }

Slide 44

Slide 44 text

 @driesvints  driesvints.com THE EVENT STORE THE EVENT STORE Schema::create('domain_messages', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('event_id', 36); $table->string('event_type', 100); $table->string('aggregate_root_id', 36)->nullable()->index() $table->dateTime('recorded_at', 6)->index(); $table->text('payload'); });

Slide 45

Slide 45 text

 @driesvints  driesvints.com EVENT STORES EVENT STORES Append- & Read-Only Can live outside the main system Can live independently by AggregateRoot The only part that you cannot lose!

Slide 46

Slide 46 text

 @driesvints  driesvints.com DOMAIN MESSAGES DOMAIN MESSAGES Events are transported in Domain Messages and are saved in an event store Messages contain the event (payload), aggregate root id, recorded mestamp Event payload is serialized before stored

Slide 47

Slide 47 text

 @driesvints  driesvints.com DISPATCHING MESSAGES DISPATCHING MESSAGES Messages are dispatched by the MessageDispatcher Can be Asynchronous, Synchronous, or both! Messages are handled by Consumers

Slide 48

Slide 48 text

 @driesvints  driesvints.com CONSUMERS CONSUMERS Two types of consumers: Projec ons: processes events to update "read models" Process Managers: listen to events and then perform ac ons

Slide 49

Slide 49 text

 @driesvints  driesvints.com PROJECTIONS PROJECTIONS Used to update read models like: Database tables Report files/pdfs Your search indexes like Algolia, Elas cSearch, ... ...

Slide 50

Slide 50 text

 @driesvints  driesvints.com WHY ARE PROJECTIONS USEFUL? WHY ARE PROJECTIONS USEFUL? Separate presenta onal state from the process you're modelling Read models are free from any constraints your domain model has Your read models can be very op mized this way Create as many read models as you like Super easy to rebuild read models

Slide 51

Slide 51 text

 @driesvints  driesvints.com THE PROJECTION THE PROJECTION use App\Models\PullRequestPage; use EventSauce\EventSourcing\Message; use EventSauce\LaravelEventSourcing\Consumer; final class BuildPullRequestPage extends Consumer { public function handlePullRequestWasOpened( PullRequestWasOpened $event, Message $message ): void { PullRequestPage::create(...); } // Handle other events... }

Slide 52

Slide 52 text

 @driesvints  driesvints.com REBUILDING PROJECTIONS 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

Slide 53

Slide 53 text

 @driesvints  driesvints.com PROCESS MANAGERS PROCESS MANAGERS Used to react to events: Send email no fica ons Trigger builds Clear logs ...

Slide 54

Slide 54 text

 @driesvints  driesvints.com WHY ARE PROCESS MANAGERS USEFUL? WHY ARE PROCESS MANAGERS USEFUL? Easily handle one- me ac ons Separate mul ple steps for your process Handle non-user interac ons Can be performed in the background and aren't blocking

Slide 55

Slide 55 text

 @driesvints  driesvints.com THE PROCESS MANAGER THE PROCESS MANAGER use EventSauce\EventSourcing\Message; use EventSauce\LaravelEventSourcing\Consumer; final class PullRequestNotifier extends Consumer { public function handlePullRequestWasOpened( PullRequestWasOpened $event, Message $message ): void { Mail::to($maintainer) ->send(new PullRequestWasOpenedMail); } }

Slide 56

Slide 56 text

 @driesvints  driesvints.com TESTING IN A BDD WAY TESTING IN A BDD WAY class MergingPullRequestTest extends PullRequestTestCase { /** @test */ public function it_can_be_merged() { $pullRequestId = $this->aggregateRootId(); $this->given( new PullRequestWasOpened($pullRequestId, ...), new PullRequestWasApproved($pullRequestId) )->when( new MergePullRequest($pullRequestId) )->then( new PullRequestWasMerged() ); } }

Slide 57

Slide 57 text

 @driesvints  driesvints.com TESTING FAILURES TESTING FAILURES /** @test */ public function it_cannot_be_merged_when_not_approved() { $pullRequestId = $this->aggregateRootId(); $this->given( new PullRequestWasOpened($pullRequestId, ...) )->when( new MergePullRequest($pullRequestId) )->then( new UnapprovedMergeWasAttempted() )->expectToFail( new MergePullRequestFailed() ); }

Slide 58

Slide 58 text

 @driesvints  driesvints.com OTHER THINGS EVENTSAUCE CAN DO OTHER THINGS EVENTSAUCE CAN DO Generate Commands and Events from yaml Message decora on to add metadata to your messages Event versioning (handling concurrency)

Slide 59

Slide 59 text

 @driesvints  driesvints.com

Slide 60

Slide 60 text

 @driesvints  driesvints.com WHAT ABOUT PERFORMANCE? WHAT ABOUT PERFORMANCE?

Slide 61

Slide 61 text

 @driesvints  driesvints.com ONE QUERY TO RETRIEVE ALL EVENTS ONE QUERY TO RETRIEVE ALL EVENTS use EventSauce\EventSourcing\MessageRepository; class LaravelMessageRepository implements MessageRepository { public function retrieveAll(AggregateRootId $id): Generator { $payloads = $this->connection->select(' SELECT payload FROM domain_messages WHERE aggregate_root_id = ? ORDER BY recorded_at ASC ', [$id->toString()]); // Unserialize here and return by "yield"... } }

Slide 62

Slide 62 text

 @driesvints  driesvints.com PERFORMANCE PERFORMANCE Objects usually don't live long enough for it to be a problem One query to retrieve all events from an aggregate In-memory event replaying is quite fast actually Use snapsho ng when you really need it (millions of events on a single aggregate)

Slide 63

Slide 63 text

 @driesvints  driesvints.com TIME TRAVEL TIME TRAVEL ⏳ ⏳

Slide 64

Slide 64 text

 @driesvints  driesvints.com REVISIT YOUR APP IN THE PAST REVISIT YOUR APP IN THE PAST Replay events un l a certain me Only projec ons are replayed Combine with your VCS to also show the app code from that me Neat for debugging, demo'ing, etc

Slide 65

Slide 65 text

 @driesvints  driesvints.com HOW DO I START USING EVENT SOURCING HOW DO I START USING EVENT SOURCING IN AN EXISTING SYSTEM? IN AN EXISTING SYSTEM?

Slide 66

Slide 66 text

 @driesvints  driesvints.com USING EVENT SOURCING IN AN EXISTING SYSTEM USING EVENT SOURCING IN AN EXISTING SYSTEM Start with a simple process Make sure you have your process modelled first (use Event Storming) Introduce a "start event" with all of your current data Don't try to be smart and "guess" the past events because you'll fail miserable

Slide 67

Slide 67 text

 @driesvints  driesvints.com READING MATERIAL READING MATERIAL eventsauce.io/docs/event-sourcing eventsauce.io/docs/architecture eventsauce.io/docs/lifecycle michielrook.nl verraes.net

Slide 68

Slide 68 text

 @driesvints  driesvints.com OTHER LIBRARIES OTHER LIBRARIES github.com/spa e/laravel-eventsauce github.com/spa e/laravel-event-projector

Slide 69

Slide 69 text

 @driesvints  driesvints.com THANKS! THANKS!  driesvints.com  @driesvints fullstackbelgium.be fullstackeurope.com