SlideShare une entreprise Scribd logo
1  sur  41
Event Sourcing
with php
#sfPot @VeryLastRoom 2016/09
Speaker: @sebastienHouze
Event Sourcing
with php
This talk is not yet another talk to:
- Convince you to use ES because its qualities.
- Let you listen me or having some rest, stay aware!
- Just talk about theoretical things, we will put ES in
practice (and in php, not that usual).
- Sell you a had hoc solution, because as everything
in computer engineering - it depends™
Event Sourcing
with php
- core principles
- es in your domain
- projections
- persistence
main concepts definitions
use es in your root aggregate lifecycle to restore it at any state
how to adapt streams persistence in your infra
cherry on the cake!
Event Sourcing
core principles
Storing all the changes to the system,
rather than just its current state.∫
Event Sourcing
core principles
change state/
Event Sourcing
core principles
change statevs
something that happened
result of some event processingresult of some command handling
snapshot at a given time
what we store in databaseswhat you probably don’t store?
usually
Event Sourcing
core principles
a change is the result of an action on some
entity / root aggregate
in an event sourced system
Changes of each root aggregate are persisted
in a dedicated event stream
Event Sourcing
event stream
Registered
Vehicle
Parked
Vehicle
…
Vehicle CX897BC
Event Sourcing
event stream(s)
Registered
Vehicle
Parked
Vehicle
…
Vehicle AM069GG
Registered
Vehicle
Parked
Vehicle
…
Vehicle CX897BC
Event Sourcing
es in your domain
Disclaimer: forget setters/reflection made
by your ORM on your entities.∫
Event Sourcing
es in your domain
Let’s build the next unicorn!
parkedLife™
PRAGMATIC USE CASE
Event Sourcing
es in your domain
Let’s start with parkedLife app, a service which
offer a pretty simple way to locate where your
vehicle(s) (car, truck, ...) has been parked.
Your vehicle(s) need to be registered on the
service the first time with a plate number.
When you have many vehicles you own a
vehicle fleet.
Event Sourcing
es in your domain
Let’s start with parkedLife app, a service which
offer a pretty simple way to locate where your
vehicle(s) (car, truck, ...) has been parked.
Your vehicle(s) need to be registered on the
service the first time with a plate number.
When you have many vehicles you own a
vehicle fleet.
Event Sourcing
es in your domain
1. Emit change(s)
2. Persist them in a stream
3. Reconstitute state from stream
Our goal: endless thing
Event Sourcing
es in your domain
class VehicleFleet
{
public function registerVehicle(string $platenumber, string $description)
{
$vehicle = Vehicle::register($platenumber, $this->userId);
$vehicle->describe($description);
$this->vehicles[] = $vehicle;
return $vehicle;
}
}
FROM THE BASICS
Event Sourcing
es in your domain
READY?
Event Sourcing
es in your domain
class VehicleFleet
{
public function registerVehicle(string $platenumber, string $description)
{
$this->whenVehicleWasRegistered(new VehicleWasRegistered($platenumber, (string)$this->userId));
$this->whenVehicleWasDescribed(new VehicleWasDescribed($platenumber, $description));
return $this->vehicleWithPlatenumber($platenumber);
}
protected function whenVehicleWasRegistered($change)
{
$this->vehicles[] = Vehicle::register(
$change->getPlatenumber(),
new UserId($change->getUserId())
);
}
protected function describeVehicle(string $platenumber, string $description)
{
$this->whenVehicleWasDescribed(new VehicleWasDescribed($platenumber, $description));
}
public function whenVehicleWasDescribed($change)
{
$vehicle = $this->vehicleWithPlatenumber($change->getPlatenumber());
$vehicle->describe($change->getDescription());
}
}
LET’S INTRODUCE EVENTS
event
event handler
class VehicleFleet
{
public function registerVehicle(string $platenumber, string $description)
{
$changes = [
new VehicleWasRegistered($platenumber, (string)$this->userId),
new VehicleWasDescribed($platenumber, $description)
];
foreach ($changes as $change) {
$handler = sprintf('when%s', implode('', array_slice(explode('', get_class($change)), -1))
$this->{$handler}($change);
}
return $this->vehicleWithPlatenumber($platenumber);
}
}
Event Sourcing
es in your domain
AND THEN (VERY BASIC) ES
very basic local event stream
very basic sourcing of stream
1. Emit change(s)
2. Persist them in a stream
3. Reconstitute state from stream
Event Sourcing
es in your domain
Our goal: endless thing
Event Sourcing
es in your domain
Well… no we don’t really permit to reconstitute
the state from some event stream from the
outside of the root aggregate, let’s refine that!
final class VehicleFleet extends AggregateRoot
{
public function registerVehicle(string $platenumber, string $description)
{
$this->record(new VehicleWasRegistered($this->getAggregateId(), $platenumber));
$this->record(new VehicleWasDescribed($this->getAggregateId(), $platenumber, $description));
return $this->vehicleWithPlatenumber($platenumber);
}
public function whenVehicleWasRegistered(VehicleWasRegistered $change)
{
$this->vehicles[] = Vehicle::register($change->getPlatenumber(), new UserId($change->getAggregat
}
public function describeVehicle(string $platenumber, string $description)
{
$this->record(new VehicleWasDescribed($this->getAggregateId(), $platenumber, $description));
}
public function whenVehicleWasDescribed(VehicleWasDescribed $change)
{
$vehicle = $this->vehicleWithPlatenumber($change->getPlatenumber());
$vehicle->describe($change->getDescription());
}
}
Event Sourcing
es in your domain
Look ‘ma, I’m an Aggregate root!
Generic logic managed by AggregateRoot
abstract class AggregateRoot
{
private $aggregateId;
private $recordedChanges = [];
protected function __construct(string $aggregateId)
public function getAggregateId(): string
public static function reconstituteFromHistory(Iterator $history)
public function popRecordedChanges(): Iterator
protected function record(Change $change)
}
Event Sourcing
es in your domain
Prefer (explicit) named constructor if
you’re applying DDD
That simple, we’re ready to source events,
from an event store for example
Event Sourcing
es in your domain
We’re done with our domain!
let’s talk about how to persist our events.
Event Sourcing
persistence
You just have to adapt your domain,
choose your infra weapon!∫
Event Sourcing
persistence
Let’s try with one of the simplest
implementations: filesystem event store.
Event Sourcing
persistence
Pretty easy from the very deep nature of events
- Append only: as events happen
- One file per stream (so by aggregate root)
Event Sourcing
persistence
EVENT STORE INTERFACE
interface EventStore
{
public function commit(Stream $eventStream);
public function fetch(StreamName $streamName): Stream;
}
Advanced ES introduce at least a
$version arg not covered by this talk
Event Sourcing
persistence
class FilesystemEventStore implements EventStore
{
public function commit(Stream $eventStream)
{
$filename = $this->filename($eventStream->getStreamName());
$content = '';
foreach ($eventStream->getChanges() as $change) {
$content .= $this->eventSerializer->serialize($change).PHP_EOL;
}
$this->fileHelper->appendSecurely($filename, $content);
}
public function fetch(StreamName $streamName): Stream
{
$filename = $this->filename($streamName);
$lines = $this->fileHelper->readIterator($this->filename($streamName));
$events = new ArrayIterator();
foreach ($lines as $serializedEvent) {
$events->append($this->eventSerializer->deserialize($serializedEvent));
}
$lines = null; // immediately removes the descriptor.
return new Stream($streamName, $events);
}
}
EVENT STORE IMPLEMENTATION
stream name to file name
association
Event Sourcing
persistenceAPP.PH
Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports};
// 1. We start from pure domain code
$userId = new DomainUserId('shouze');
$fleet = DomainVehicleFleet::ofUser($userId);
$platenumber = 'AM 069 GG';
$fleet->registerVehicle($platenumber, 'My benz');
$fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable());
Event Sourcing
persistenceAPP.PH
Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports};
// 1. We start from pure domain code
$userId = new DomainUserId('shouze');
$fleet = DomainVehicleFleet::ofUser($userId);
$platenumber = 'AM 069 GG';
$fleet->registerVehicle($platenumber, 'My benz');
$fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable());
// 2. We build our sourceable stream
$streamName = new EventSourcingStreamName(sprintf('vehicle_fleet-%s', $userId));
$stream = new EventSourcingStream($streamName, $fleet->popRecordedChanges());
Event Sourcing
persistenceAPP.PH
Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports};
// 1. We start from pure domain code
$userId = new DomainUserId('shouze');
$fleet = DomainVehicleFleet::ofUser($userId);
$platenumber = 'AM 069 GG';
$fleet->registerVehicle($platenumber, 'My benz');
$fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable());
// 2. We build our sourceable stream
$streamName = new EventSourcingStreamName(sprintf('vehicle_fleet-%s', $userId));
$stream = new EventSourcingStream($streamName, $fleet->popRecordedChanges());
// 3. We adapt the domain to the infra through event sourcing
$serializer = new EventSourcingEventSerializer(
new DomainEventMapping,
new SymfonyComponentSerializerSerializer(
[
new SymfonyComponentSerializerNormalizerPropertyNormalizer(
null,
new SymfonyComponentSerializerNameConverterCamelCaseToSnakeCaseNameConverter
)
],
[ new SymfonyComponentSerializerEncoderJsonEncoder ]
)
);
$eventStore = new AdaptersFilesystemEventStore(__DIR__.'/var/eventstore', $serializer, new
PortsFileHelper);
$eventStore->commit($stream);
Event Sourcing
persistenceAPP.PH
Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports};
// 1. We start from pure domain code
$userId = new DomainUserId('shouze');
$fleet = DomainVehicleFleet::ofUser($userId);
$platenumber = 'AM 069 GG';
$fleet->registerVehicle($platenumber, 'My benz');
$fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable());
// 2. We build our sourceable stream
$streamName = new EventSourcingStreamName(sprintf('vehicle_fleet-%s', $userId));
$stream = new EventSourcingStream($streamName, $fleet->popRecordedChanges());
// 3. We adapt the domain to the infra through event sourcing
$serializer = …
$eventStore = new AdaptersFilesystemEventStore(__DIR__.'/var/eventstore', $serializer, new
PortsFileHelper);
$eventStore->commit($stream);
Event Sourcing
persistence
$ docker run -w /app --rm -v $(pwd):/app -it php:zts-alpine php app.php
$ docker run -w /app --rm -v $(pwd):/app -it php:zts-alpine sh -c 'find var -type f | xargs cat'
{"event_name":"vehicle_was_registered.fleet.parkedlife",
"data":{"user_id":"shouze","platenumber":"AM 069 GG"}}
{"event_name":"vehicle_was_described.fleet.parkedlife",
"data":{"user_id":"shouze","platenumber":"AM 069 GG",
"description":"My benz"}}
{"event_name":"vehicle_was_parked.fleet.parkedlife", "data":{"user_id":"shouze","platenumber":"AM
069 GG","latitude":4.1,"longitude":3.12,"timestamp":1474838529}}
LET’S RUN IT
YEAH, IT’S OUR FIRST
TRULY PERSISTED EVENT
STREAM!
Event Sourcing
projections
How to produce state representation(s) at
any time from the very first event of your
stream to any point of your stream.∫
Event Sourcing
projections
Ok, we saw that actions on your system
produce state (through events)
But when the time come to read that state, did you
notice that we often have use cases where we
want to express it through many representations?
Event Sourcing
projections
Projection is about deriving state
from the stream of events.
As we can produce any state representation from
the very first emitted event, we can produce every
up to date state representation derivation.
Event Sourcing
projections
Projections deserve an event bus as it permit to
introduce eventual consistency (async build) of
projection(s) and loose coupling of course.
For projections of an aggregate you will
(soon) need a projector.
Event Sourcing
projections
So you quickly need Read Models, very simple
objects far from you root aggregate.
Event Sourcing
projections
EVENT BUS IMPLEMENTATION
class InMemoryEventBus implements EventSourcingEventBus
{
public function __construct(EventSourcingEventSerializer $eventSerializer,
SymfonyComponentSerializerSerializer
$serializer)
{
$this->eventSerializer = $eventSerializer;
$this->serializer = $serializer;
$this->mapping = [
'vehicle_was_registered.fleet.parkedlife' => 'VehicleWasRegistred',
'vehicle_was_described.fleet.parkedlife' => 'VehicleWasDescribed',
'vehicle_was_parked.fleet.parkedlife' => 'VehicleWasParked'
];
}
public function publish(EventSourcingChange $change)
{
$eventNormalized = $this->eventSerializer->normalize($change);
$projector = new DomainReadModelVehicleFleetProjector(new
AdaptersJsonProjector(__DIR__.'/var/eventstore', $this->serializer));
if (array_key_exists($eventNormalized['event_name'], $this->mapping)) {
$handler = $this->mapping[$eventNormalized['event_name']];
$projector->{'project'.$handler}($eventNormalized['data']);
}
}
}
Event Sourcing
with php
QUESTIONS?
Event Sourcing
with php
https://github.com/shouze/parkedLife.git
MORE CODE AT:

Contenu connexe

Tendances

PHP Unit 3 functions_in_php_2
PHP Unit 3 functions_in_php_2PHP Unit 3 functions_in_php_2
PHP Unit 3 functions_in_php_2
Kumar
 
SPL: The Missing Link in Development
SPL: The Missing Link in DevelopmentSPL: The Missing Link in Development
SPL: The Missing Link in Development
jsmith92
 

Tendances (20)

Functions in php
Functions in phpFunctions in php
Functions in php
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC Hydra
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
 
A Functional Guide to Cat Herding with PHP Generators
A Functional Guide to Cat Herding with PHP GeneratorsA Functional Guide to Cat Herding with PHP Generators
A Functional Guide to Cat Herding with PHP Generators
 
Sylius and Api Platform The story of integration
Sylius and Api Platform The story of integrationSylius and Api Platform The story of integration
Sylius and Api Platform The story of integration
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Php server variables
Php server variablesPhp server variables
Php server variables
 
Php functions
Php functionsPhp functions
Php functions
 
PHP Unit 3 functions_in_php_2
PHP Unit 3 functions_in_php_2PHP Unit 3 functions_in_php_2
PHP Unit 3 functions_in_php_2
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Php pattern matching
Php pattern matchingPhp pattern matching
Php pattern matching
 
PHP 8.1: Enums
PHP 8.1: EnumsPHP 8.1: Enums
PHP 8.1: Enums
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
SPL: The Missing Link in Development
SPL: The Missing Link in DevelopmentSPL: The Missing Link in Development
SPL: The Missing Link in Development
 
LPW: Beginners Perl
LPW: Beginners PerlLPW: Beginners Perl
LPW: Beginners Perl
 
Php Chapter 2 3 Training
Php Chapter 2 3 TrainingPhp Chapter 2 3 Training
Php Chapter 2 3 Training
 
Neatly Hashing a Tree: FP tree-fold in Perl5 & Perl6
Neatly Hashing a Tree: FP tree-fold in Perl5 & Perl6Neatly Hashing a Tree: FP tree-fold in Perl5 & Perl6
Neatly Hashing a Tree: FP tree-fold in Perl5 & Perl6
 

En vedette

En vedette (10)

Real World Event Sourcing and CQRS
Real World Event Sourcing and CQRSReal World Event Sourcing and CQRS
Real World Event Sourcing and CQRS
 
Event sourcing with the GetEventStore
Event sourcing with the GetEventStoreEvent sourcing with the GetEventStore
Event sourcing with the GetEventStore
 
Pulling them in: Content Marketing for Conference Organisers
Pulling them in: Content Marketing for Conference Organisers   Pulling them in: Content Marketing for Conference Organisers
Pulling them in: Content Marketing for Conference Organisers
 
Conference Marketing
Conference MarketingConference Marketing
Conference Marketing
 
Building and deploying microservices with event sourcing, CQRS and Docker (Be...
Building and deploying microservices with event sourcing, CQRS and Docker (Be...Building and deploying microservices with event sourcing, CQRS and Docker (Be...
Building and deploying microservices with event sourcing, CQRS and Docker (Be...
 
A pattern language for microservices (#gluecon #gluecon2016)
A pattern language for microservices (#gluecon #gluecon2016)A pattern language for microservices (#gluecon #gluecon2016)
A pattern language for microservices (#gluecon #gluecon2016)
 
Microservices + Events + Docker = A Perfect Trio (dockercon)
Microservices + Events + Docker = A Perfect Trio (dockercon)Microservices + Events + Docker = A Perfect Trio (dockercon)
Microservices + Events + Docker = A Perfect Trio (dockercon)
 
Developing microservices with aggregates (SpringOne platform, #s1p)
Developing microservices with aggregates (SpringOne platform, #s1p)Developing microservices with aggregates (SpringOne platform, #s1p)
Developing microservices with aggregates (SpringOne platform, #s1p)
 
Akka persistence == event sourcing in 30 minutes
Akka persistence == event sourcing in 30 minutesAkka persistence == event sourcing in 30 minutes
Akka persistence == event sourcing in 30 minutes
 
Handling Eventual Consistency in JVM Microservices with Event Sourcing (javao...
Handling Eventual Consistency in JVM Microservices with Event Sourcing (javao...Handling Eventual Consistency in JVM Microservices with Event Sourcing (javao...
Handling Eventual Consistency in JVM Microservices with Event Sourcing (javao...
 

Similaire à Event Sourcing with php

Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
Michael Peacock
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
unodelostrece
 
Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)
Fabien Potencier
 

Similaire à Event Sourcing with php (20)

Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
Application Layer in PHP
Application Layer in PHPApplication Layer in PHP
Application Layer in PHP
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
 
Php my sql - functions - arrays - tutorial - programmerblog.net
Php my sql - functions - arrays - tutorial - programmerblog.netPhp my sql - functions - arrays - tutorial - programmerblog.net
Php my sql - functions - arrays - tutorial - programmerblog.net
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
 
Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
Modern Web Development with Perl
Modern Web Development with PerlModern Web Development with Perl
Modern Web Development with Perl
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 

Dernier

%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
masabamasaba
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
masabamasaba
 

Dernier (20)

WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT  - Elevating Productivity in Today's Agile EnvironmentHarnessing ChatGPT  - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 

Event Sourcing with php

  • 1. Event Sourcing with php #sfPot @VeryLastRoom 2016/09 Speaker: @sebastienHouze
  • 2. Event Sourcing with php This talk is not yet another talk to: - Convince you to use ES because its qualities. - Let you listen me or having some rest, stay aware! - Just talk about theoretical things, we will put ES in practice (and in php, not that usual). - Sell you a had hoc solution, because as everything in computer engineering - it depends™
  • 3. Event Sourcing with php - core principles - es in your domain - projections - persistence main concepts definitions use es in your root aggregate lifecycle to restore it at any state how to adapt streams persistence in your infra cherry on the cake!
  • 4. Event Sourcing core principles Storing all the changes to the system, rather than just its current state.∫
  • 6. Event Sourcing core principles change statevs something that happened result of some event processingresult of some command handling snapshot at a given time what we store in databaseswhat you probably don’t store? usually
  • 7. Event Sourcing core principles a change is the result of an action on some entity / root aggregate in an event sourced system Changes of each root aggregate are persisted in a dedicated event stream
  • 9. Event Sourcing event stream(s) Registered Vehicle Parked Vehicle … Vehicle AM069GG Registered Vehicle Parked Vehicle … Vehicle CX897BC
  • 10. Event Sourcing es in your domain Disclaimer: forget setters/reflection made by your ORM on your entities.∫
  • 11. Event Sourcing es in your domain Let’s build the next unicorn! parkedLife™ PRAGMATIC USE CASE
  • 12. Event Sourcing es in your domain Let’s start with parkedLife app, a service which offer a pretty simple way to locate where your vehicle(s) (car, truck, ...) has been parked. Your vehicle(s) need to be registered on the service the first time with a plate number. When you have many vehicles you own a vehicle fleet.
  • 13. Event Sourcing es in your domain Let’s start with parkedLife app, a service which offer a pretty simple way to locate where your vehicle(s) (car, truck, ...) has been parked. Your vehicle(s) need to be registered on the service the first time with a plate number. When you have many vehicles you own a vehicle fleet.
  • 14. Event Sourcing es in your domain 1. Emit change(s) 2. Persist them in a stream 3. Reconstitute state from stream Our goal: endless thing
  • 15. Event Sourcing es in your domain class VehicleFleet { public function registerVehicle(string $platenumber, string $description) { $vehicle = Vehicle::register($platenumber, $this->userId); $vehicle->describe($description); $this->vehicles[] = $vehicle; return $vehicle; } } FROM THE BASICS
  • 16. Event Sourcing es in your domain READY?
  • 17. Event Sourcing es in your domain class VehicleFleet { public function registerVehicle(string $platenumber, string $description) { $this->whenVehicleWasRegistered(new VehicleWasRegistered($platenumber, (string)$this->userId)); $this->whenVehicleWasDescribed(new VehicleWasDescribed($platenumber, $description)); return $this->vehicleWithPlatenumber($platenumber); } protected function whenVehicleWasRegistered($change) { $this->vehicles[] = Vehicle::register( $change->getPlatenumber(), new UserId($change->getUserId()) ); } protected function describeVehicle(string $platenumber, string $description) { $this->whenVehicleWasDescribed(new VehicleWasDescribed($platenumber, $description)); } public function whenVehicleWasDescribed($change) { $vehicle = $this->vehicleWithPlatenumber($change->getPlatenumber()); $vehicle->describe($change->getDescription()); } } LET’S INTRODUCE EVENTS event event handler
  • 18. class VehicleFleet { public function registerVehicle(string $platenumber, string $description) { $changes = [ new VehicleWasRegistered($platenumber, (string)$this->userId), new VehicleWasDescribed($platenumber, $description) ]; foreach ($changes as $change) { $handler = sprintf('when%s', implode('', array_slice(explode('', get_class($change)), -1)) $this->{$handler}($change); } return $this->vehicleWithPlatenumber($platenumber); } } Event Sourcing es in your domain AND THEN (VERY BASIC) ES very basic local event stream very basic sourcing of stream
  • 19. 1. Emit change(s) 2. Persist them in a stream 3. Reconstitute state from stream Event Sourcing es in your domain Our goal: endless thing
  • 20. Event Sourcing es in your domain Well… no we don’t really permit to reconstitute the state from some event stream from the outside of the root aggregate, let’s refine that!
  • 21. final class VehicleFleet extends AggregateRoot { public function registerVehicle(string $platenumber, string $description) { $this->record(new VehicleWasRegistered($this->getAggregateId(), $platenumber)); $this->record(new VehicleWasDescribed($this->getAggregateId(), $platenumber, $description)); return $this->vehicleWithPlatenumber($platenumber); } public function whenVehicleWasRegistered(VehicleWasRegistered $change) { $this->vehicles[] = Vehicle::register($change->getPlatenumber(), new UserId($change->getAggregat } public function describeVehicle(string $platenumber, string $description) { $this->record(new VehicleWasDescribed($this->getAggregateId(), $platenumber, $description)); } public function whenVehicleWasDescribed(VehicleWasDescribed $change) { $vehicle = $this->vehicleWithPlatenumber($change->getPlatenumber()); $vehicle->describe($change->getDescription()); } } Event Sourcing es in your domain Look ‘ma, I’m an Aggregate root! Generic logic managed by AggregateRoot
  • 22. abstract class AggregateRoot { private $aggregateId; private $recordedChanges = []; protected function __construct(string $aggregateId) public function getAggregateId(): string public static function reconstituteFromHistory(Iterator $history) public function popRecordedChanges(): Iterator protected function record(Change $change) } Event Sourcing es in your domain Prefer (explicit) named constructor if you’re applying DDD That simple, we’re ready to source events, from an event store for example
  • 23. Event Sourcing es in your domain We’re done with our domain! let’s talk about how to persist our events.
  • 24. Event Sourcing persistence You just have to adapt your domain, choose your infra weapon!∫
  • 25. Event Sourcing persistence Let’s try with one of the simplest implementations: filesystem event store.
  • 26. Event Sourcing persistence Pretty easy from the very deep nature of events - Append only: as events happen - One file per stream (so by aggregate root)
  • 27. Event Sourcing persistence EVENT STORE INTERFACE interface EventStore { public function commit(Stream $eventStream); public function fetch(StreamName $streamName): Stream; } Advanced ES introduce at least a $version arg not covered by this talk
  • 28. Event Sourcing persistence class FilesystemEventStore implements EventStore { public function commit(Stream $eventStream) { $filename = $this->filename($eventStream->getStreamName()); $content = ''; foreach ($eventStream->getChanges() as $change) { $content .= $this->eventSerializer->serialize($change).PHP_EOL; } $this->fileHelper->appendSecurely($filename, $content); } public function fetch(StreamName $streamName): Stream { $filename = $this->filename($streamName); $lines = $this->fileHelper->readIterator($this->filename($streamName)); $events = new ArrayIterator(); foreach ($lines as $serializedEvent) { $events->append($this->eventSerializer->deserialize($serializedEvent)); } $lines = null; // immediately removes the descriptor. return new Stream($streamName, $events); } } EVENT STORE IMPLEMENTATION stream name to file name association
  • 29. Event Sourcing persistenceAPP.PH Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports}; // 1. We start from pure domain code $userId = new DomainUserId('shouze'); $fleet = DomainVehicleFleet::ofUser($userId); $platenumber = 'AM 069 GG'; $fleet->registerVehicle($platenumber, 'My benz'); $fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable());
  • 30. Event Sourcing persistenceAPP.PH Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports}; // 1. We start from pure domain code $userId = new DomainUserId('shouze'); $fleet = DomainVehicleFleet::ofUser($userId); $platenumber = 'AM 069 GG'; $fleet->registerVehicle($platenumber, 'My benz'); $fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable()); // 2. We build our sourceable stream $streamName = new EventSourcingStreamName(sprintf('vehicle_fleet-%s', $userId)); $stream = new EventSourcingStream($streamName, $fleet->popRecordedChanges());
  • 31. Event Sourcing persistenceAPP.PH Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports}; // 1. We start from pure domain code $userId = new DomainUserId('shouze'); $fleet = DomainVehicleFleet::ofUser($userId); $platenumber = 'AM 069 GG'; $fleet->registerVehicle($platenumber, 'My benz'); $fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable()); // 2. We build our sourceable stream $streamName = new EventSourcingStreamName(sprintf('vehicle_fleet-%s', $userId)); $stream = new EventSourcingStream($streamName, $fleet->popRecordedChanges()); // 3. We adapt the domain to the infra through event sourcing $serializer = new EventSourcingEventSerializer( new DomainEventMapping, new SymfonyComponentSerializerSerializer( [ new SymfonyComponentSerializerNormalizerPropertyNormalizer( null, new SymfonyComponentSerializerNameConverterCamelCaseToSnakeCaseNameConverter ) ], [ new SymfonyComponentSerializerEncoderJsonEncoder ] ) ); $eventStore = new AdaptersFilesystemEventStore(__DIR__.'/var/eventstore', $serializer, new PortsFileHelper); $eventStore->commit($stream);
  • 32. Event Sourcing persistenceAPP.PH Puse ShouzeParkedLifeDomain{Domain, EventSourcing, Adapters, Ports}; // 1. We start from pure domain code $userId = new DomainUserId('shouze'); $fleet = DomainVehicleFleet::ofUser($userId); $platenumber = 'AM 069 GG'; $fleet->registerVehicle($platenumber, 'My benz'); $fleet->parkVehicle($platenumber, DomainLocation::fromString('4.1, 3.12'), new DateTimeImmutable()); // 2. We build our sourceable stream $streamName = new EventSourcingStreamName(sprintf('vehicle_fleet-%s', $userId)); $stream = new EventSourcingStream($streamName, $fleet->popRecordedChanges()); // 3. We adapt the domain to the infra through event sourcing $serializer = … $eventStore = new AdaptersFilesystemEventStore(__DIR__.'/var/eventstore', $serializer, new PortsFileHelper); $eventStore->commit($stream);
  • 33. Event Sourcing persistence $ docker run -w /app --rm -v $(pwd):/app -it php:zts-alpine php app.php $ docker run -w /app --rm -v $(pwd):/app -it php:zts-alpine sh -c 'find var -type f | xargs cat' {"event_name":"vehicle_was_registered.fleet.parkedlife", "data":{"user_id":"shouze","platenumber":"AM 069 GG"}} {"event_name":"vehicle_was_described.fleet.parkedlife", "data":{"user_id":"shouze","platenumber":"AM 069 GG", "description":"My benz"}} {"event_name":"vehicle_was_parked.fleet.parkedlife", "data":{"user_id":"shouze","platenumber":"AM 069 GG","latitude":4.1,"longitude":3.12,"timestamp":1474838529}} LET’S RUN IT YEAH, IT’S OUR FIRST TRULY PERSISTED EVENT STREAM!
  • 34. Event Sourcing projections How to produce state representation(s) at any time from the very first event of your stream to any point of your stream.∫
  • 35. Event Sourcing projections Ok, we saw that actions on your system produce state (through events) But when the time come to read that state, did you notice that we often have use cases where we want to express it through many representations?
  • 36. Event Sourcing projections Projection is about deriving state from the stream of events. As we can produce any state representation from the very first emitted event, we can produce every up to date state representation derivation.
  • 37. Event Sourcing projections Projections deserve an event bus as it permit to introduce eventual consistency (async build) of projection(s) and loose coupling of course. For projections of an aggregate you will (soon) need a projector.
  • 38. Event Sourcing projections So you quickly need Read Models, very simple objects far from you root aggregate.
  • 39. Event Sourcing projections EVENT BUS IMPLEMENTATION class InMemoryEventBus implements EventSourcingEventBus { public function __construct(EventSourcingEventSerializer $eventSerializer, SymfonyComponentSerializerSerializer $serializer) { $this->eventSerializer = $eventSerializer; $this->serializer = $serializer; $this->mapping = [ 'vehicle_was_registered.fleet.parkedlife' => 'VehicleWasRegistred', 'vehicle_was_described.fleet.parkedlife' => 'VehicleWasDescribed', 'vehicle_was_parked.fleet.parkedlife' => 'VehicleWasParked' ]; } public function publish(EventSourcingChange $change) { $eventNormalized = $this->eventSerializer->normalize($change); $projector = new DomainReadModelVehicleFleetProjector(new AdaptersJsonProjector(__DIR__.'/var/eventstore', $this->serializer)); if (array_key_exists($eventNormalized['event_name'], $this->mapping)) { $handler = $this->mapping[$eventNormalized['event_name']]; $projector->{'project'.$handler}($eventNormalized['data']); } } }

Notes de l'éditeur

  1. This is not so common to have pragmatic talk on how to implement event sourcing with languages like php, you can find a lot of literature about implementations in Java, .Net stack (C# or event F#), and some more exotic ones about implementations in GO or Javascript.
  2. To sum up aside of this talk, some of Event Sourcing benefits are: Better traceability than most of your current systems implementations. Easier debugging because event streams can be used like breakpoints and step by step like in a good debugging tool. Very big read performance as projections are always ready & loose coupling against event streams writes. Very bug write performance as event streams are append only.
  3. Here we are follow the 1st part with attention and the 3 following ones will be piece of cake.
  4. This is the only thing to remember about event sourcing. Everything else is about the way to implement that once you’ve understood what it means.
  5. Let’s fight, you will learn that you’ve probably made the bad choice in your system since the beginning.
  6. Ok if you store states in your system, keep reading. Otherwise you system is already event sourced I guess.
  7. I talk about root aggregates here. It’s more a pure DDD (Domain Driven Design) concept but DDD fit very well with ES (Event Sourcing). If you don’t apply DDD principles in your project replace Aggregate Root by Entity everywhere as en Aggregate Root is an entity that just have the particularity to be the most essential entity of your bounded context. In other terms it’s the entity that create a clear boundary of one part of the system you focus on.
  8. 1 Identified Vehicle = 1 Event Stream for this Vehicle
  9. N Identified Vehicle(s) = N Event Stream(s)
  10. You can find out a lot of talks/posts about anemic models requesting your favorite search engine.
  11. Actions, Subjects and everything that help understand what our app should manage is highlighted. You designate a user, as a user can have many vehicles we guess that the Vehicle Fleet will be the boundary of our app so the Root Aggregate.
  12. It’s an early reminder, we express like a simple algorithm what we said at the beginning: « storing all the changes rather than the state ». Storing the changes and be able to restore the state from the changes.
  13. Here’s a very basic Vehicle Fleet implementation extract that we could have coded - event sourcing concepts free. Let’s see how to apply our recipe seen on the previous slide.
  14. Tada! Notice that this part is almost something that you can do in any of your legacy systems if you want to make an easy step by step transition to ES. It involves discussion with your domain experts to find out the events, this kind of talks are called event storming sessions.
  15. Ok another intermediate step that hilight the recipe steps, at leeast 1, 2 (emit changes, apply them) in a very basic stream way (stream implie loops in the implementation).
  16. Sure?
  17. In fact we missed point 3. I guess. We should be able to reconstitute.
  18. Ok this time it’s almost the perfect step to see the first move you can make on your own legacy code. It won’t break and you can even start to use event sourcing in your tests instead of involving heavy/difficult fixtures loading solutions (hello ORM users).
  19. Here’s the abstract class interface inherited by our Aggregate Root. Not so complex, the main thing is reconstituteFromHistory and propRecordedChanges, this is the key of real event sourcing on our model and the only publicly exposed thing, keeping our model interface very clear and the most domain oriented possible.
  20. Yes of course we just saw how to have transient event sourcing, nothing is persisted anywhere at the moment but we don’t have to worry about that, it’s very easy since we can pop changes applied and then reconstitute our model at any state.
  21. That simple indeed. ES guide us to apply some of the Hexagonal Architecture or DDD principles: the infra implementation should be something we don’t have to worry at the beginning of our project, it should be easily swappable and we can start with very simple implementation that we can change when we want to deliver the final product or when we face to a big interest/load from users in our project. It won’t change the design of our domain objects at all.
  22. I told you: we choose the one we consider the fastest/easier to implement at the beginning of the project. We will see later if we need to replace it.
  23. It will be piece of cake to implement!
  24. Ok commit here will effectively write in the file in our implementation. Quite clear this store as an operation and the mutual one, this way we can read or write in the right stream, designated by a name that in our implementation will drive us to a filename.
  25. As we said! Very few lines of codes, we’re done!
  26. Ok so we can start our minimal app demo and we will see that it will persist events in a file after they’ve been applied on our domain object.
  27. We source events in a stream, we’re ready to write that in our store!
  28. Ok… please go the the next slide, it’s infra code so serializer instantiation is quite boring and confusing to read.
  29. Yes, 2 more lines and we’re done, we can write our stream, we’re safe now, our changes are not transient anymore :)
  30. It event works for real <3
  31. Ok looks complicated but let’s see it’s very powerful & simple.
  32. The same events can be used to produce many data representations, like PKI/dashboards, detailed listings and so on. If as usual you persist the state, it’s very acrobatic then to produce this myriad of representations. Worse - sometimes you can’t as state don’t preserve history of changes!
  33. Yes, from the very first emitted event, so when your marketing guy comes to you for a new PKI, you can impress him he will have the PKI not only from data produced after you’ve coded & deployed this PKI feature, but from the origins of the system, pretty cool no?
  34. You can choose to implement your way, I just tell you here that you probably soon need to implement that.
  35. I won’t show you read models here, you can go by yourself in the github repository linked at the end of the talk to see what they looks like. You only need to remember that a read model as nothing in common with your root aggregate. ES fit well by nature with another concept named CQRS (Command Query Responsibility Segregation). In other terms, the entity you use to trigger actions in your system is the Write (command) part of your system, and the Read Model the read (query) part.
  36. Here’s a very basic event bus implementation example. It’s not async here but it’s easy to understand the main responsibility of this kind of bus.
  37. Ok in fact in this ES talk you’ve seen some SOLID, DDD & CQRS principles applied too. You can embrace all of this principles - or not - in your system implementation. SOLID is not really an option and probably the first thing to check in your own apps. ES is easy to apply, even partially to make a transition. Do you have to use ES in your project? How can you know if it’s useful for you? The main answer is: are you still ok in your system with the fact to store the state rather than the changes? If yes then don’t implement ES on this project.
  38. Code shown in this talk can vary a little from the repository.