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

Redis data modeling examples
Redis data modeling examplesRedis data modeling examples
Redis data modeling examplesTerry Cho
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux IntroductionNikolaus Graf
 
Memory Management: What You Need to Know When Moving to Java 8
Memory Management: What You Need to Know When Moving to Java 8Memory Management: What You Need to Know When Moving to Java 8
Memory Management: What You Need to Know When Moving to Java 8AppDynamics
 
Angular - Chapter 5 - Directives
 Angular - Chapter 5 - Directives Angular - Chapter 5 - Directives
Angular - Chapter 5 - DirectivesWebStackAcademy
 
Angular - Chapter 4 - Data and Event Handling
 Angular - Chapter 4 - Data and Event Handling Angular - Chapter 4 - Data and Event Handling
Angular - Chapter 4 - Data and Event HandlingWebStackAcademy
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesCiaranMcNulty
 
Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...
Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...
Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...Andrejs Prokopjevs
 
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldFunctional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldJorge Vásquez
 
Capabilities for Resources and Effects
Capabilities for Resources and EffectsCapabilities for Resources and Effects
Capabilities for Resources and EffectsMartin Odersky
 
Scalable JavaScript Application Architecture
Scalable JavaScript Application ArchitectureScalable JavaScript Application Architecture
Scalable JavaScript Application ArchitectureNicholas Zakas
 
Yarn by default (Spark on YARN)
Yarn by default (Spark on YARN)Yarn by default (Spark on YARN)
Yarn by default (Spark on YARN)Ferran Galí Reniu
 
Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Julien Truffaut
 
How to implement internationalization (i18n) in angular application(multiple ...
How to implement internationalization (i18n) in angular application(multiple ...How to implement internationalization (i18n) in angular application(multiple ...
How to implement internationalization (i18n) in angular application(multiple ...Katy Slemon
 
ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...
ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...
ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...ScyllaDB
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Scott Wlaschin
 

What's hot (20)

Redis data modeling examples
Redis data modeling examplesRedis data modeling examples
Redis data modeling examples
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux Introduction
 
Memory Management: What You Need to Know When Moving to Java 8
Memory Management: What You Need to Know When Moving to Java 8Memory Management: What You Need to Know When Moving to Java 8
Memory Management: What You Need to Know When Moving to Java 8
 
Angular - Chapter 5 - Directives
 Angular - Chapter 5 - Directives Angular - Chapter 5 - Directives
Angular - Chapter 5 - Directives
 
Angular - Chapter 4 - Data and Event Handling
 Angular - Chapter 4 - Data and Event Handling Angular - Chapter 4 - Data and Event Handling
Angular - Chapter 4 - Data and Event Handling
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing Strategies
 
ReactPHP + Symfony
ReactPHP + SymfonyReactPHP + Symfony
ReactPHP + Symfony
 
Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...
Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...
Optimize DR and Cloning with Logical Hostnames in Oracle E-Business Suite (OA...
 
Rich domain model
Rich domain modelRich domain model
Rich domain model
 
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldFunctional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorld
 
Capabilities for Resources and Effects
Capabilities for Resources and EffectsCapabilities for Resources and Effects
Capabilities for Resources and Effects
 
Scalable JavaScript Application Architecture
Scalable JavaScript Application ArchitectureScalable JavaScript Application Architecture
Scalable JavaScript Application Architecture
 
Introducing Kogito
Introducing KogitoIntroducing Kogito
Introducing Kogito
 
Yarn by default (Spark on YARN)
Yarn by default (Spark on YARN)Yarn by default (Spark on YARN)
Yarn by default (Spark on YARN)
 
Map reduce vs spark
Map reduce vs sparkMap reduce vs spark
Map reduce vs spark
 
Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!
 
How to implement internationalization (i18n) in angular application(multiple ...
How to implement internationalization (i18n) in angular application(multiple ...How to implement internationalization (i18n) in angular application(multiple ...
How to implement internationalization (i18n) in angular application(multiple ...
 
ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...
ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...
ceph::errorator<> throw/catch-free, compile time-checked exceptions for seast...
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013
 
Rxjs ngvikings
Rxjs ngvikingsRxjs ngvikings
Rxjs ngvikings
 

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

The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DaySri Ambati
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 

Recently uploaded (20)

The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 

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