SlideShare a Scribd company logo
1 of 60
Download to read offline
Sourcing
Event
Practical
@mathiasverraes
Mathias Verraes
Student of Systems
Meddler of Models
Labourer of Legacy
verraes.net
mathiasverraes
Elephant in the Room
Podcast with @everzet
elephantintheroom.io
@EitRoom
DDDinPHP.org
The Big Picture
Client
Write
Model
Read
Model
DTO
Commands
Events
CQRS: http://verraes.net/2013/12/fighting-bottlenecks-with-cqrs/
Write
Model
EventsEvents
Read
Model
This talk
Event Sourcing
Using on object’s
history
to reconstitute its
State
Express
history
as a series of
Domain Events
Something that has
happened in the past
that is of
interest to the business
Domain Event
!
happened in the past
!
Express
history
in the
Ubiquitous Language
Relevant
to the business.
!
First class citizens of the
Domain Model
Domain Events
interface DomainEvent
{
/**
* @return IdentifiesAggregate
*/
public function getAggregateId();
}
final class ProductWasAddedToBasket implements DomainEvent
{
private $basketId, $productId, $productName;
!
public function __construct(
BasketId $basketId, ProductId $productId, $productName
) {
$this->basketId = $basketId;
$this->productName = $productName;
$this->productId = $productId;
}
!
public function getAggregateId()
{
return $this->basketId;
}
!
public function getProductId()
{
return $this->productId;
}
!
public function getProductName()
{
return $this->productName;
}
}
final class ProductWasRemovedFromBasket implements DomainEvent
{
private $basketId;
private $productId;
!
public function __construct(BasketId $basketId, ProductId $productId)
{
$this->basketId = $basketId;
$this->productId = $productId;
}
!
public function getAggregateId()
{
return $this->basketId;
}
!
public function getProductId()
{
return $this->productId;
}
}
final class BasketWasPickedUp implements DomainEvent
{
private $basketId;
!
public function __construct(BasketId $basketId)
// You may want to add a date, user, …
{
$this->basketId = $basketId;
}
!
public function getAggregateId()
{
return $this->basketId;
}
}
Domain Events
are
immutable
RecordsEvents
$basket = Basket::pickUp(BasketId::generate());
$basket->addProduct(new ProductId('AV001'), “The Last Airbender");
$basket->removeProduct(new ProductId('AV001'));
!
!
$events = $basket->getRecordedEvents();
!
it("should have recorded 3 events",
3 == count($events));
!
it("should have a BasketWasPickedUp event",
$events[0] instanceof BasketWasPickedUp);
!
it("should have a ProductWasAddedToBasket event",
$events[1] instanceof ProductWasAddedToBasket);
!
it("should have a ProductWasRemovedFromBasket event",
$events[2] instanceof ProductWasRemovedFromBasket);
!
!
// Output:
✔ It should have recorded 3 events
✔ It should have a BasketWasPickedUp event
✔ It should have a ProductWasAddedToBasket event
✔ It should have a ProductWasRemovedFromBasket event
TestFrameworkInATweet https://gist.github.com/mathiasverraes/9046427
final class Basket implements RecordsEvents
{
public static function pickUp(BasketId $basketId)
{
$basket = new Basket($basketId);
$basket->recordThat(
new BasketWasPickedUp($basketId)
);
return $basket;
}
!
public function addProduct(ProductId $productId, $name)
{
$this->recordThat(
new ProductWasAddedToBasket($this->basketId, $productId, $name)
);
}
!
public function removeProduct(ProductId $productId)
{
$this->recordThat(
new ProductWasRemovedFromBasket($this->basketId, $productId)
);
}
!
// continued on next slide
// continued: final class Basket implements RecordsEvents
!
private $basketId;
!
private $latestRecordedEvents = [];
!
private function __construct(BasketId $basketId)
{
$this->basketId = $basketId;
}
!
public function getRecordedEvents()
{
return new DomainEvents($this->latestRecordedEvents);
}
!
public function clearRecordedEvents()
{
$this->latestRecordedEvents = [];
}
!
private function recordThat(DomainEvent $domainEvent)
{
$this->latestRecordedEvents[] = $domainEvent;
}
!
}
Protecting Invariants
$basket = Basket::pickUp(BasketId::generate());
!
$basket->addProduct(new ProductId('AV1'), “The Last Airbender");
$basket->addProduct(new ProductId('AV2'), "The Legend of Korra");
$basket->addProduct(new ProductId('AV3'), “The Making Of Avatar”);
!
it("should disallow adding a fourth product",
throws(‘BasketLimitReached’, function () use($basket) {
$basket->addProduct(new ProductId('AV4'), “The Last Airbender Movie”);
})
!
);
final class Basket implements RecordsEvents
{
private $productCount = 0;
!
public function addProduct(ProductId $productId, $name)
{
$this->guardProductLimit();
$this->recordThat(
new ProductWasAddedToBasket($this->basketId, $productId, $name)
);
++$this->productCount;
}
!
private function guardProductLimit()
{
if ($this->productCount >= 3) {
throw new BasketLimitReached;
}
}
!
public function removeProduct(ProductId $productId)
{
$this->recordThat(
new ProductWasRemovedFromBasket($this->basketId, $productId)
);
--$this->productCount;
}
// ...
}
$basket = Basket::pickUp(BasketId::generate());
!
$productId = new ProductId(‘AV1');
!
$basket->addProduct($productId, “The Last Airbender");
$basket->removeProduct($productId);
$basket->removeProduct($productId);
!
it(“shouldn't record an event when removing a Product
that is no longer in the Basket”,
!
count($basket->getRecordedEvents()) == 3
!
);
1
2
3
4
final class Basket implements RecordsEvents
{
private $productCountById = [];
!
public function addProduct(ProductId $productId, $name)
{
$this->guardProductLimit();
$this->recordThat(new ProductWasAddedToBasket(…));
!
if(!$this->productIsInBasket($productId)) {
$this->productCountById[$productId] = 0;
}
!
++$this->productCountById[$productId];
}
!
public function removeProduct(ProductId $productId)
{
if(! $this->productIsInBasket($productId)) {
return;
}
!
$this->recordThat(new ProductWasRemovedFromBasket(…);
!
--$this->productCountById;
}
private function productIsInBasket(ProductId $productId) {…}
Aggregates
record events
Aggregates
protect invariants
Possible outcomes
!
nothing
one or more events
exception
Aggregates do not
expose state
Reconstituting
Aggregates
!
$basket = Basket::pickUp($basketId);
$basket->addProduct($productId, “The Last Airbender");
!
$events = $basket->getRecordedEvents();
!
// persist events in an event store, retrieve at a later time
!
$reconstitutedBasket = Basket::reconstituteFrom(
new AggregateHistory($basketId, $retrievedEvents)
);
!
it("should be the same after reconstitution",
$basket == $reconstitutedBasket
);
final class Basket implements RecordsEvents, IsEventSourced
{
public function addProduct(ProductId $productId, $name)
{
$this->guardProductLimit();
$this->recordThat(new ProductWasAddedToBasket(…));
!
// No state is changed!
}
!
public function removeProduct(ProductId $productId)
{
if(! $this->productIsInBasket($productId)) {
return;
}
!
$this->recordThat(new ProductWasRemovedFromBasket(…));
!
// No state is changed!
}
!
private function recordThat(DomainEvent $domainEvent)
{
$this->latestRecordedEvents[] = $domainEvent;
!
$this->apply($domainEvent);
}
private function applyProductWasAddedToBasket(
ProductWasAddedToBasket $event)
{
!
$productId = $event->getProductId();
!
if(!$this->productIsInBasket($productId)) {
$this->products[$productId] = 0;
}
!
++$this->productCountById[$productId];
!
}
!
private function applyProductWasRemovedFromBasket(
ProductWasRemovedFromBasket $event)
{
$productId = $event->getProductId();
--$this->productCountById[$productId];
}
public static function reconstituteFrom(
AggregateHistory $aggregateHistory)
{
$basketId = $aggregateHistory->getAggregateId();
$basket = new Basket($basketId);
!
foreach($aggregateHistory as $event) {
$basket->apply($event);
}
return $basket;
}
!
private function apply(DomainEvent $event)
{
$method = 'apply' . get_class($event);
$this->$method($event);
}
!
Projections
final class BasketProjector
{
public function projectProductWasAddedToBasket(
ProductWasAddedToBasket $event)
{
INSERT INTO baskets_readmodel
SET
`basketId` = $event->getBasketId(),
`productId` = $event->getProductId(),
`name` = $event->getName()
}
public function projectProductWasRemovedFromBasket(
ProductWasRemovedFromBasket $event)
{
DELETE FROM baskets_readmodel
WHERE
`basketId` = $event->getBasketId()
AND `productId` = $event->getProductId()
}
}
Fat events
The good kind of duplication
Individual read models for
every unique
use case
final class BlueProductsSoldProjection
{
public function projectProductWasIntroducedInCatalog(
ProductWasIntroducedInCatalog $event)
{
if($event->getColor() == 'blue') {
$this->redis->sAdd('blueProducts', $event->getProductId());
}
}
!
public function projectProductWasAddedToBasket(
ProductWasAddedToBasket $event)
{
if($this->redis->sIsMember($event->getProductId())) {
$this->redis->incr('blueProductsSold');
}
}
!
public function projectProductWasRemovedFromBasket(
ProductWasRemovedFromBasket $event)
{
if($this->redis->sIsMember($event->getProductId())) {
$this->redis->decr('blueProductsSold');
}
}
}
LessonWasScheduled
{ SchoolId, GroupId, TeacherId, Subject, WeekDay, Timeslot }
!
=>
!
GroupScheduleProjector
Group 1A Monday Tuesday Wednesday Thursday Friday
09:00
Math
Ada
German
Friedrich
Math
Ada
Chemistry
Niels
Economy
Nicholas
10:00
French
Albert
Math
Ada
Physics
Isaac
PHP
Rasmus
History
Julian
11:00
Sports
Felix
PHP
Rasmus
PHP
Rasmus
German
Friedrich
Math
Ada
LessonWasScheduled
{ SchoolId, GroupId, TeacherId, Subject, WeekDay, Timeslot }
!
=>
!
TeacherScheduleProjector
Ada!
Math
Monday Tuesday Wednesday Thursday Friday
09:00
Group 1A
School 5
Group 1A
School 5
Group 6C
School 9
Group 5B
School 9
10:00
Group 1B
School 5
Group 1A
School 5
Group 6C
School 9
Group 5B
School 9
11:00
Group 2A
School 5
Group 5B
School 9
Group 1A
School 5
PupilWasEnlistedInGroup
{ PupilId, SchoolId, GroupId }
LessonWasScheduled
{ SchoolId, GroupId, TeacherId, Subject, WeekDay, Timeslot }
!
=>
!
TeacherPermissionsProjector
Ada Pupil 1
Ada Pupil 3
Friedrich Pupil 1
Friedrich Pupil 7
Ada Pupil 8
Julian Pupil 3
Event Store
Immutable
Append-only
You can’t change history
interface NaiveEventStore
{
public function commit(DomainEvents $events);
!
/** @return AggregateHistory */
public function getAggregateHistoryFor(IdentifiesAggregate $id);
!
/** @return DomainEvents */
public function getAll();
}
!
CREATE TABLE `buttercup_eventstore` (
`streamId` varbinary(16) NOT NULL,
`streamVersion` bigint(20) unsigned NOT NULL,
`streamContract` varchar(255) NOT NULL,
`eventDataContract` varchar(255) NOT NULL,
`eventData` text NOT NULL,
`eventMetadataContract` varchar(255) NOT NULL,
`eventMetadata` text NOT NULL,
`utcStoredTime` datetime NOT NULL,
`correlationId` varbinary(16) NOT NULL,
`causationId` varbinary(16) NOT NULL,
`causationEventOrdinal` bigint(20) unsigned,
PRIMARY KEY (`streamId`,`streamVersion`,`streamContract`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Performance
The Event Store is an
immutable, append-only
database:
infinite caching
Querying events happens by
aggregate id only
Read models are
faster than joins
Aggregate snapshots,
if need be
Testing
// it should disallow evaluating pupils without planning them first
!
$scenario->given([
new EvaluationWasPlanned(…)
]);
!
$scenario->when(
new EvaluatePupil(…)
);
!
$scenario->then([
$scenario->throws(new CantEvaluateUnplannedPupil(…))
]);
!
——————————————————————————————————————————————————————————————————————————-
!
$scenario->given([
new EvaluationWasPlanned(…),
new PupilWasPlannedForEvaluation(…)
]);
!
$scenario->when(
new EvaluatePupil(…)
);
!
$scenario->then([
new PupilWasEvaluated()
]);
verraes.net
!
joind.in/10911
!
buttercup-php/protects
!
mathiasverraes
verraes.net
!
joind.in/10911
!
buttercup-php/protects
!
mathiasverraes

More Related Content

What's hot

Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)
Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)
Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)涛 吴
 
HBaseCon 2015: Taming GC Pauses for Large Java Heap in HBase
HBaseCon 2015: Taming GC Pauses for Large Java Heap in HBaseHBaseCon 2015: Taming GC Pauses for Large Java Heap in HBase
HBaseCon 2015: Taming GC Pauses for Large Java Heap in HBaseHBaseCon
 
Debugging concurrency programs in go
Debugging concurrency programs in goDebugging concurrency programs in go
Debugging concurrency programs in goAndrii Soldatenko
 
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)涛 吴
 
Ozone and HDFS's Evolution
Ozone and HDFS's EvolutionOzone and HDFS's Evolution
Ozone and HDFS's EvolutionDataWorks Summit
 
Meet hbase 2.0
Meet hbase 2.0Meet hbase 2.0
Meet hbase 2.0enissoz
 
Load Balancing and Scaling with NGINX
Load Balancing and Scaling with NGINXLoad Balancing and Scaling with NGINX
Load Balancing and Scaling with NGINXNGINX, Inc.
 
Secrets of Performance Tuning Java on Kubernetes
Secrets of Performance Tuning Java on KubernetesSecrets of Performance Tuning Java on Kubernetes
Secrets of Performance Tuning Java on KubernetesBruno Borges
 
The Art of Metaprogramming in Java
The Art of Metaprogramming in Java  The Art of Metaprogramming in Java
The Art of Metaprogramming in Java Abdelmonaim Remani
 
Using Morphlines for On-the-Fly ETL
Using Morphlines for On-the-Fly ETLUsing Morphlines for On-the-Fly ETL
Using Morphlines for On-the-Fly ETLCloudera, Inc.
 
Web hdfs and httpfs
Web hdfs and httpfsWeb hdfs and httpfs
Web hdfs and httpfswchevreuil
 
Jabber design and configuration
Jabber design and configurationJabber design and configuration
Jabber design and configurationsolarisyougood
 
Rouault imbert alpc_rpc_pacsec
Rouault imbert alpc_rpc_pacsecRouault imbert alpc_rpc_pacsec
Rouault imbert alpc_rpc_pacsecPacSecJP
 
Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...
Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...
Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...GeoSolutions
 
PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?Sam Thomas
 
Effectuation: Entscheiden unter Ungewissheit
Effectuation: Entscheiden unter UngewissheitEffectuation: Entscheiden unter Ungewissheit
Effectuation: Entscheiden unter UngewissheitHeiko Bartlog
 

What's hot (20)

Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)
Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)
Behind Pegasus, What matters in a Distributed System (Arch summit shenzhen_2017)
 
HBaseCon 2015: Taming GC Pauses for Large Java Heap in HBase
HBaseCon 2015: Taming GC Pauses for Large Java Heap in HBaseHBaseCon 2015: Taming GC Pauses for Large Java Heap in HBase
HBaseCon 2015: Taming GC Pauses for Large Java Heap in HBase
 
ATG Best Practices
ATG Best Practices ATG Best Practices
ATG Best Practices
 
Debugging concurrency programs in go
Debugging concurrency programs in goDebugging concurrency programs in go
Debugging concurrency programs in go
 
Nginx dhruba mandal
Nginx dhruba mandalNginx dhruba mandal
Nginx dhruba mandal
 
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
 
Ozone and HDFS's Evolution
Ozone and HDFS's EvolutionOzone and HDFS's Evolution
Ozone and HDFS's Evolution
 
SOLID PRINCIPLES
SOLID PRINCIPLESSOLID PRINCIPLES
SOLID PRINCIPLES
 
Meet hbase 2.0
Meet hbase 2.0Meet hbase 2.0
Meet hbase 2.0
 
Golang workshop
Golang workshopGolang workshop
Golang workshop
 
Load Balancing and Scaling with NGINX
Load Balancing and Scaling with NGINXLoad Balancing and Scaling with NGINX
Load Balancing and Scaling with NGINX
 
Secrets of Performance Tuning Java on Kubernetes
Secrets of Performance Tuning Java on KubernetesSecrets of Performance Tuning Java on Kubernetes
Secrets of Performance Tuning Java on Kubernetes
 
The Art of Metaprogramming in Java
The Art of Metaprogramming in Java  The Art of Metaprogramming in Java
The Art of Metaprogramming in Java
 
Using Morphlines for On-the-Fly ETL
Using Morphlines for On-the-Fly ETLUsing Morphlines for On-the-Fly ETL
Using Morphlines for On-the-Fly ETL
 
Web hdfs and httpfs
Web hdfs and httpfsWeb hdfs and httpfs
Web hdfs and httpfs
 
Jabber design and configuration
Jabber design and configurationJabber design and configuration
Jabber design and configuration
 
Rouault imbert alpc_rpc_pacsec
Rouault imbert alpc_rpc_pacsecRouault imbert alpc_rpc_pacsec
Rouault imbert alpc_rpc_pacsec
 
Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...
Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...
Crunching Data In GeoServer: Mastering Rendering Transformations, WPS Process...
 
PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?
 
Effectuation: Entscheiden unter Ungewissheit
Effectuation: Entscheiden unter UngewissheitEffectuation: Entscheiden unter Ungewissheit
Effectuation: Entscheiden unter Ungewissheit
 

Similar to Practical Event Sourcing

Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
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 scenariosDivante
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonKris Wallsmith
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
Pim Elshoff "Technically DDD"
Pim Elshoff "Technically DDD"Pim Elshoff "Technically DDD"
Pim Elshoff "Technically DDD"Fwdays
 
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011Alessandro Nadalin
 
Getting the Most Out of jQuery Widgets
Getting the Most Out of jQuery WidgetsGetting the Most Out of jQuery Widgets
Getting the Most Out of jQuery Widgetsvelveeta_512
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
 
When cqrs meets event sourcing
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcingManel Sellés
 
What is the difference between a good and a bad repository? (Forum PHP 2018)
What is the difference between a good and a bad repository? (Forum PHP 2018)What is the difference between a good and a bad repository? (Forum PHP 2018)
What is the difference between a good and a bad repository? (Forum PHP 2018)Arnaud Langlade
 
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 PimpleHugo Hamon
 
Migrating to dependency injection
Migrating to dependency injectionMigrating to dependency injection
Migrating to dependency injectionJosh Adell
 
Multilingualism makes better programmers
Multilingualism makes better programmersMultilingualism makes better programmers
Multilingualism makes better programmersAlexander Varwijk
 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Fabien Potencier
 
Matching Game In Java
Matching Game In JavaMatching Game In Java
Matching Game In Javacmkandemir
 

Similar to Practical Event Sourcing (20)

Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
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
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Pim Elshoff "Technically DDD"
Pim Elshoff "Technically DDD"Pim Elshoff "Technically DDD"
Pim Elshoff "Technically DDD"
 
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 
Getting the Most Out of jQuery Widgets
Getting the Most Out of jQuery WidgetsGetting the Most Out of jQuery Widgets
Getting the Most Out of jQuery Widgets
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
Hooks WCSD12
Hooks WCSD12Hooks WCSD12
Hooks WCSD12
 
When cqrs meets event sourcing
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcing
 
What is the difference between a good and a bad repository? (Forum PHP 2018)
What is the difference between a good and a bad repository? (Forum PHP 2018)What is the difference between a good and a bad repository? (Forum PHP 2018)
What is the difference between a good and a bad repository? (Forum PHP 2018)
 
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
 
G* on GAE/J 挑戦編
G* on GAE/J 挑戦編G* on GAE/J 挑戦編
G* on GAE/J 挑戦編
 
Migrating to dependency injection
Migrating to dependency injectionMigrating to dependency injection
Migrating to dependency injection
 
Multilingualism makes better programmers
Multilingualism makes better programmersMultilingualism makes better programmers
Multilingualism makes better programmers
 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010
 
Matching Game In Java
Matching Game In JavaMatching Game In Java
Matching Game In Java
 

More from Mathias Verraes

Towards Modelling Processes
Towards Modelling ProcessesTowards Modelling Processes
Towards Modelling ProcessesMathias Verraes
 
Small Controlled Experiments
Small Controlled ExperimentsSmall Controlled Experiments
Small Controlled ExperimentsMathias Verraes
 
DDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
DDD Basics: Bounded Contexts, Modelling - Kortrijk EditionDDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
DDD Basics: Bounded Contexts, Modelling - Kortrijk EditionMathias Verraes
 
Why Domain-Driven Design Matters
Why Domain-Driven Design MattersWhy Domain-Driven Design Matters
Why Domain-Driven Design MattersMathias Verraes
 
Unbreakable Domain Models PHPUK 2014 London
Unbreakable Domain Models PHPUK 2014 LondonUnbreakable Domain Models PHPUK 2014 London
Unbreakable Domain Models PHPUK 2014 LondonMathias Verraes
 
Domain-Driven Design Basics
Domain-Driven Design BasicsDomain-Driven Design Basics
Domain-Driven Design BasicsMathias Verraes
 
Model Storming Workshop PHP Benelux 2014
Model Storming Workshop PHP Benelux 2014Model Storming Workshop PHP Benelux 2014
Model Storming Workshop PHP Benelux 2014Mathias Verraes
 
Fighting Bottlencks with CQRS - ResearchGate
Fighting Bottlencks with CQRS - ResearchGateFighting Bottlencks with CQRS - ResearchGate
Fighting Bottlencks with CQRS - ResearchGateMathias Verraes
 
Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13Mathias Verraes
 

More from Mathias Verraes (12)

Towards Modelling Processes
Towards Modelling ProcessesTowards Modelling Processes
Towards Modelling Processes
 
Modelling Heuristics
Modelling HeuristicsModelling Heuristics
Modelling Heuristics
 
Small Controlled Experiments
Small Controlled ExperimentsSmall Controlled Experiments
Small Controlled Experiments
 
Managed Technical Debt
Managed Technical DebtManaged Technical Debt
Managed Technical Debt
 
DDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
DDD Basics: Bounded Contexts, Modelling - Kortrijk EditionDDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
DDD Basics: Bounded Contexts, Modelling - Kortrijk Edition
 
Why Domain-Driven Design Matters
Why Domain-Driven Design MattersWhy Domain-Driven Design Matters
Why Domain-Driven Design Matters
 
Unbreakable Domain Models PHPUK 2014 London
Unbreakable Domain Models PHPUK 2014 LondonUnbreakable Domain Models PHPUK 2014 London
Unbreakable Domain Models PHPUK 2014 London
 
Domain-Driven Design Basics
Domain-Driven Design BasicsDomain-Driven Design Basics
Domain-Driven Design Basics
 
Model Storming Workshop PHP Benelux 2014
Model Storming Workshop PHP Benelux 2014Model Storming Workshop PHP Benelux 2014
Model Storming Workshop PHP Benelux 2014
 
Fighting Bottlencks with CQRS - ResearchGate
Fighting Bottlencks with CQRS - ResearchGateFighting Bottlencks with CQRS - ResearchGate
Fighting Bottlencks with CQRS - ResearchGate
 
DDDBE Modellathon 2013
DDDBE Modellathon 2013DDDBE Modellathon 2013
DDDBE Modellathon 2013
 
Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13
 

Recently uploaded

[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdfSandro Moreira
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...apidays
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...DianaGray10
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodJuan lago vázquez
 
JohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptxJohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptxJohnPollard37
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Zilliz
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesrafiqahmad00786416
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoffsammart93
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Victor Rentea
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontologyjohnbeverley2021
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024The Digital Insurer
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamUiPathCommunity
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native ApplicationsWSO2
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingEdi Saputra
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Jeffrey Haguewood
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWERMadyBayot
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityWSO2
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistandanishmna97
 

Recently uploaded (20)

[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
JohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptxJohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptx
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontology
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 

Practical Event Sourcing

  • 2. Mathias Verraes Student of Systems Meddler of Models Labourer of Legacy verraes.net mathiasverraes
  • 3. Elephant in the Room Podcast with @everzet elephantintheroom.io @EitRoom
  • 9. Using on object’s history to reconstitute its State
  • 10. Express history as a series of Domain Events
  • 11. Something that has happened in the past that is of interest to the business Domain Event
  • 14. Relevant to the business. ! First class citizens of the Domain Model
  • 16. interface DomainEvent { /** * @return IdentifiesAggregate */ public function getAggregateId(); }
  • 17. final class ProductWasAddedToBasket implements DomainEvent { private $basketId, $productId, $productName; ! public function __construct( BasketId $basketId, ProductId $productId, $productName ) { $this->basketId = $basketId; $this->productName = $productName; $this->productId = $productId; } ! public function getAggregateId() { return $this->basketId; } ! public function getProductId() { return $this->productId; } ! public function getProductName() { return $this->productName; } }
  • 18. final class ProductWasRemovedFromBasket implements DomainEvent { private $basketId; private $productId; ! public function __construct(BasketId $basketId, ProductId $productId) { $this->basketId = $basketId; $this->productId = $productId; } ! public function getAggregateId() { return $this->basketId; } ! public function getProductId() { return $this->productId; } }
  • 19. final class BasketWasPickedUp implements DomainEvent { private $basketId; ! public function __construct(BasketId $basketId) // You may want to add a date, user, … { $this->basketId = $basketId; } ! public function getAggregateId() { return $this->basketId; } }
  • 22. $basket = Basket::pickUp(BasketId::generate()); $basket->addProduct(new ProductId('AV001'), “The Last Airbender"); $basket->removeProduct(new ProductId('AV001')); ! ! $events = $basket->getRecordedEvents(); ! it("should have recorded 3 events", 3 == count($events)); ! it("should have a BasketWasPickedUp event", $events[0] instanceof BasketWasPickedUp); ! it("should have a ProductWasAddedToBasket event", $events[1] instanceof ProductWasAddedToBasket); ! it("should have a ProductWasRemovedFromBasket event", $events[2] instanceof ProductWasRemovedFromBasket); ! ! // Output: ✔ It should have recorded 3 events ✔ It should have a BasketWasPickedUp event ✔ It should have a ProductWasAddedToBasket event ✔ It should have a ProductWasRemovedFromBasket event TestFrameworkInATweet https://gist.github.com/mathiasverraes/9046427
  • 23. final class Basket implements RecordsEvents { public static function pickUp(BasketId $basketId) { $basket = new Basket($basketId); $basket->recordThat( new BasketWasPickedUp($basketId) ); return $basket; } ! public function addProduct(ProductId $productId, $name) { $this->recordThat( new ProductWasAddedToBasket($this->basketId, $productId, $name) ); } ! public function removeProduct(ProductId $productId) { $this->recordThat( new ProductWasRemovedFromBasket($this->basketId, $productId) ); } ! // continued on next slide
  • 24. // continued: final class Basket implements RecordsEvents ! private $basketId; ! private $latestRecordedEvents = []; ! private function __construct(BasketId $basketId) { $this->basketId = $basketId; } ! public function getRecordedEvents() { return new DomainEvents($this->latestRecordedEvents); } ! public function clearRecordedEvents() { $this->latestRecordedEvents = []; } ! private function recordThat(DomainEvent $domainEvent) { $this->latestRecordedEvents[] = $domainEvent; } ! }
  • 26. $basket = Basket::pickUp(BasketId::generate()); ! $basket->addProduct(new ProductId('AV1'), “The Last Airbender"); $basket->addProduct(new ProductId('AV2'), "The Legend of Korra"); $basket->addProduct(new ProductId('AV3'), “The Making Of Avatar”); ! it("should disallow adding a fourth product", throws(‘BasketLimitReached’, function () use($basket) { $basket->addProduct(new ProductId('AV4'), “The Last Airbender Movie”); }) ! );
  • 27. final class Basket implements RecordsEvents { private $productCount = 0; ! public function addProduct(ProductId $productId, $name) { $this->guardProductLimit(); $this->recordThat( new ProductWasAddedToBasket($this->basketId, $productId, $name) ); ++$this->productCount; } ! private function guardProductLimit() { if ($this->productCount >= 3) { throw new BasketLimitReached; } } ! public function removeProduct(ProductId $productId) { $this->recordThat( new ProductWasRemovedFromBasket($this->basketId, $productId) ); --$this->productCount; } // ... }
  • 28. $basket = Basket::pickUp(BasketId::generate()); ! $productId = new ProductId(‘AV1'); ! $basket->addProduct($productId, “The Last Airbender"); $basket->removeProduct($productId); $basket->removeProduct($productId); ! it(“shouldn't record an event when removing a Product that is no longer in the Basket”, ! count($basket->getRecordedEvents()) == 3 ! ); 1 2 3 4
  • 29. final class Basket implements RecordsEvents { private $productCountById = []; ! public function addProduct(ProductId $productId, $name) { $this->guardProductLimit(); $this->recordThat(new ProductWasAddedToBasket(…)); ! if(!$this->productIsInBasket($productId)) { $this->productCountById[$productId] = 0; } ! ++$this->productCountById[$productId]; } ! public function removeProduct(ProductId $productId) { if(! $this->productIsInBasket($productId)) { return; } ! $this->recordThat(new ProductWasRemovedFromBasket(…); ! --$this->productCountById; } private function productIsInBasket(ProductId $productId) {…}
  • 32. Possible outcomes ! nothing one or more events exception
  • 35. ! $basket = Basket::pickUp($basketId); $basket->addProduct($productId, “The Last Airbender"); ! $events = $basket->getRecordedEvents(); ! // persist events in an event store, retrieve at a later time ! $reconstitutedBasket = Basket::reconstituteFrom( new AggregateHistory($basketId, $retrievedEvents) ); ! it("should be the same after reconstitution", $basket == $reconstitutedBasket );
  • 36. final class Basket implements RecordsEvents, IsEventSourced { public function addProduct(ProductId $productId, $name) { $this->guardProductLimit(); $this->recordThat(new ProductWasAddedToBasket(…)); ! // No state is changed! } ! public function removeProduct(ProductId $productId) { if(! $this->productIsInBasket($productId)) { return; } ! $this->recordThat(new ProductWasRemovedFromBasket(…)); ! // No state is changed! } ! private function recordThat(DomainEvent $domainEvent) { $this->latestRecordedEvents[] = $domainEvent; ! $this->apply($domainEvent); }
  • 37. private function applyProductWasAddedToBasket( ProductWasAddedToBasket $event) { ! $productId = $event->getProductId(); ! if(!$this->productIsInBasket($productId)) { $this->products[$productId] = 0; } ! ++$this->productCountById[$productId]; ! } ! private function applyProductWasRemovedFromBasket( ProductWasRemovedFromBasket $event) { $productId = $event->getProductId(); --$this->productCountById[$productId]; }
  • 38. public static function reconstituteFrom( AggregateHistory $aggregateHistory) { $basketId = $aggregateHistory->getAggregateId(); $basket = new Basket($basketId); ! foreach($aggregateHistory as $event) { $basket->apply($event); } return $basket; } ! private function apply(DomainEvent $event) { $method = 'apply' . get_class($event); $this->$method($event); } !
  • 40. final class BasketProjector { public function projectProductWasAddedToBasket( ProductWasAddedToBasket $event) { INSERT INTO baskets_readmodel SET `basketId` = $event->getBasketId(), `productId` = $event->getProductId(), `name` = $event->getName() } public function projectProductWasRemovedFromBasket( ProductWasRemovedFromBasket $event) { DELETE FROM baskets_readmodel WHERE `basketId` = $event->getBasketId() AND `productId` = $event->getProductId() } }
  • 41. Fat events The good kind of duplication
  • 42. Individual read models for every unique use case
  • 43. final class BlueProductsSoldProjection { public function projectProductWasIntroducedInCatalog( ProductWasIntroducedInCatalog $event) { if($event->getColor() == 'blue') { $this->redis->sAdd('blueProducts', $event->getProductId()); } } ! public function projectProductWasAddedToBasket( ProductWasAddedToBasket $event) { if($this->redis->sIsMember($event->getProductId())) { $this->redis->incr('blueProductsSold'); } } ! public function projectProductWasRemovedFromBasket( ProductWasRemovedFromBasket $event) { if($this->redis->sIsMember($event->getProductId())) { $this->redis->decr('blueProductsSold'); } } }
  • 44. LessonWasScheduled { SchoolId, GroupId, TeacherId, Subject, WeekDay, Timeslot } ! => ! GroupScheduleProjector Group 1A Monday Tuesday Wednesday Thursday Friday 09:00 Math Ada German Friedrich Math Ada Chemistry Niels Economy Nicholas 10:00 French Albert Math Ada Physics Isaac PHP Rasmus History Julian 11:00 Sports Felix PHP Rasmus PHP Rasmus German Friedrich Math Ada
  • 45. LessonWasScheduled { SchoolId, GroupId, TeacherId, Subject, WeekDay, Timeslot } ! => ! TeacherScheduleProjector Ada! Math Monday Tuesday Wednesday Thursday Friday 09:00 Group 1A School 5 Group 1A School 5 Group 6C School 9 Group 5B School 9 10:00 Group 1B School 5 Group 1A School 5 Group 6C School 9 Group 5B School 9 11:00 Group 2A School 5 Group 5B School 9 Group 1A School 5
  • 46. PupilWasEnlistedInGroup { PupilId, SchoolId, GroupId } LessonWasScheduled { SchoolId, GroupId, TeacherId, Subject, WeekDay, Timeslot } ! => ! TeacherPermissionsProjector Ada Pupil 1 Ada Pupil 3 Friedrich Pupil 1 Friedrich Pupil 7 Ada Pupil 8 Julian Pupil 3
  • 49. interface NaiveEventStore { public function commit(DomainEvents $events); ! /** @return AggregateHistory */ public function getAggregateHistoryFor(IdentifiesAggregate $id); ! /** @return DomainEvents */ public function getAll(); } !
  • 50. CREATE TABLE `buttercup_eventstore` ( `streamId` varbinary(16) NOT NULL, `streamVersion` bigint(20) unsigned NOT NULL, `streamContract` varchar(255) NOT NULL, `eventDataContract` varchar(255) NOT NULL, `eventData` text NOT NULL, `eventMetadataContract` varchar(255) NOT NULL, `eventMetadata` text NOT NULL, `utcStoredTime` datetime NOT NULL, `correlationId` varbinary(16) NOT NULL, `causationId` varbinary(16) NOT NULL, `causationEventOrdinal` bigint(20) unsigned, PRIMARY KEY (`streamId`,`streamVersion`,`streamContract`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 51.
  • 53. The Event Store is an immutable, append-only database: infinite caching
  • 54. Querying events happens by aggregate id only
  • 58. // it should disallow evaluating pupils without planning them first ! $scenario->given([ new EvaluationWasPlanned(…) ]); ! $scenario->when( new EvaluatePupil(…) ); ! $scenario->then([ $scenario->throws(new CantEvaluateUnplannedPupil(…)) ]); ! ——————————————————————————————————————————————————————————————————————————- ! $scenario->given([ new EvaluationWasPlanned(…), new PupilWasPlannedForEvaluation(…) ]); ! $scenario->when( new EvaluatePupil(…) ); ! $scenario->then([ new PupilWasEvaluated() ]);