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

Design for succcess with react and storybook.js
Design for succcess with react and storybook.jsDesign for succcess with react and storybook.js
Design for succcess with react and storybook.jsChris Saylor
 
Version Control History and Git Basics
Version Control History and Git BasicsVersion Control History and Git Basics
Version Control History and Git BasicsSreedath N S
 
Git - Get Ready To Use It
Git - Get Ready To Use ItGit - Get Ready To Use It
Git - Get Ready To Use ItDaniel Kummer
 
Evolving a Clean, Pragmatic Architecture - A Craftsman's Guide
Evolving a Clean, Pragmatic Architecture - A Craftsman's GuideEvolving a Clean, Pragmatic Architecture - A Craftsman's Guide
Evolving a Clean, Pragmatic Architecture - A Craftsman's GuideVictor Rentea
 
Introduction to Akka - Atlanta Java Users Group
Introduction to Akka - Atlanta Java Users GroupIntroduction to Akka - Atlanta Java Users Group
Introduction to Akka - Atlanta Java Users GroupRoy Russo
 
Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!Jorge Vásquez
 
Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017Roman Elizarov
 
Laravel Design Patterns
Laravel Design PatternsLaravel Design Patterns
Laravel Design PatternsBobby Bouwmann
 
API Asynchrones en Java 8
API Asynchrones en Java 8API Asynchrones en Java 8
API Asynchrones en Java 8José Paumard
 
Analysing in depth work manager
Analysing in depth work managerAnalysing in depth work manager
Analysing in depth work managerbhatnagar.gaurav83
 
Devwork.vn Tài liệu lập trình PHP Laravel
Devwork.vn Tài liệu lập trình PHP LaravelDevwork.vn Tài liệu lập trình PHP Laravel
Devwork.vn Tài liệu lập trình PHP LaravelDevwork
 
Introduction To Angular's reactive forms
Introduction To Angular's reactive formsIntroduction To Angular's reactive forms
Introduction To Angular's reactive formsNir Kaufman
 
Git vs SVN
Git vs SVNGit vs SVN
Git vs SVNneuros
 

What's hot (20)

Closure
ClosureClosure
Closure
 
Design for succcess with react and storybook.js
Design for succcess with react and storybook.jsDesign for succcess with react and storybook.js
Design for succcess with react and storybook.js
 
Git & Github for beginners
Git & Github for beginnersGit & Github for beginners
Git & Github for beginners
 
Git & GitHub for Beginners
Git & GitHub for BeginnersGit & GitHub for Beginners
Git & GitHub for Beginners
 
Version Control History and Git Basics
Version Control History and Git BasicsVersion Control History and Git Basics
Version Control History and Git Basics
 
Git in 10 minutes
Git in 10 minutesGit in 10 minutes
Git in 10 minutes
 
Git Init (Introduction to Git)
Git Init (Introduction to Git)Git Init (Introduction to Git)
Git Init (Introduction to Git)
 
Git - Get Ready To Use It
Git - Get Ready To Use ItGit - Get Ready To Use It
Git - Get Ready To Use It
 
Evolving a Clean, Pragmatic Architecture - A Craftsman's Guide
Evolving a Clean, Pragmatic Architecture - A Craftsman's GuideEvolving a Clean, Pragmatic Architecture - A Craftsman's Guide
Evolving a Clean, Pragmatic Architecture - A Craftsman's Guide
 
Introduction to Akka - Atlanta Java Users Group
Introduction to Akka - Atlanta Java Users GroupIntroduction to Akka - Atlanta Java Users Group
Introduction to Akka - Atlanta Java Users Group
 
Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!
 
Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017
 
Laravel Design Patterns
Laravel Design PatternsLaravel Design Patterns
Laravel Design Patterns
 
API Asynchrones en Java 8
API Asynchrones en Java 8API Asynchrones en Java 8
API Asynchrones en Java 8
 
Analysing in depth work manager
Analysing in depth work managerAnalysing in depth work manager
Analysing in depth work manager
 
Devwork.vn Tài liệu lập trình PHP Laravel
Devwork.vn Tài liệu lập trình PHP LaravelDevwork.vn Tài liệu lập trình PHP Laravel
Devwork.vn Tài liệu lập trình PHP Laravel
 
Introduction To Angular's reactive forms
Introduction To Angular's reactive formsIntroduction To Angular's reactive forms
Introduction To Angular's reactive forms
 
Recyclerview in action
Recyclerview in action Recyclerview in action
Recyclerview in action
 
Git
GitGit
Git
 
Git vs SVN
Git vs SVNGit vs SVN
Git vs SVN
 

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

Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
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
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native ApplicationsWSO2
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024The Digital Insurer
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 
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
 
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot ModelNavi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot ModelDeepika Singh
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businesspanagenda
 
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
 
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
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxRustici Software
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 

Recently uploaded (20)

Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
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
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
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...
 
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot ModelNavi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
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
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 

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() ]);