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

More Related Content

PPTX
PPT
php 2 Function creating, calling, PHP built-in function
PPT
Introduction to php
PDF
Anonymous Functions in PHP 5.3 - Matthew Weier O’Phinney
PDF
Symfony (Unit, Functional) Testing.
DOCX
Example code for the SADI BMI Calculator Web Service
DOCX
Sadi service
ODP
Symfony2, creare bundle e valore per il cliente
php 2 Function creating, calling, PHP built-in function
Introduction to php
Anonymous Functions in PHP 5.3 - Matthew Weier O’Phinney
Symfony (Unit, Functional) Testing.
Example code for the SADI BMI Calculator Web Service
Sadi service
Symfony2, creare bundle e valore per il cliente

What's hot (20)

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

Viewers also liked (10)

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

Similar to Event Sourcing with php (20)

PDF
Doctrine For Beginners
PDF
Why is crud a bad idea - focus on real scenarios
PDF
Application Layer in PHP
PDF
Developing Cacheable PHP Applications - PHP SP 2024
PDF
Bootstrat REST APIs with Laravel 5
PDF
WordPress REST API hacking
PDF
How kris-writes-symfony-apps-london
PDF
Unveiling the Future: Sylius 2.0 New Features
PDF
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
PDF
WordPress REST API hacking
KEY
Phpne august-2012-symfony-components-friends
PDF
Unittests für Dummies
PDF
How I started to love design patterns
PDF
Symfony components in the wild, PHPNW12
PDF
Bag Of Tricks From Iusethis
PDF
Zendcon 2007 Api Design
PPT
Php my sql - functions - arrays - tutorial - programmerblog.net
PDF
Refactoring using Codeception
PDF
Beyond symfony 1.2 (Symfony Camp 2008)
PDF
関西PHP勉強会 php5.4つまみぐい
Doctrine For Beginners
Why is crud a bad idea - focus on real scenarios
Application Layer in PHP
Developing Cacheable PHP Applications - PHP SP 2024
Bootstrat REST APIs with Laravel 5
WordPress REST API hacking
How kris-writes-symfony-apps-london
Unveiling the Future: Sylius 2.0 New Features
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
WordPress REST API hacking
Phpne august-2012-symfony-components-friends
Unittests für Dummies
How I started to love design patterns
Symfony components in the wild, PHPNW12
Bag Of Tricks From Iusethis
Zendcon 2007 Api Design
Php my sql - functions - arrays - tutorial - programmerblog.net
Refactoring using Codeception
Beyond symfony 1.2 (Symfony Camp 2008)
関西PHP勉強会 php5.4つまみぐい

Recently uploaded (20)

PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
AI in Product Development-omnex systems
PDF
System and Network Administration Chapter 2
PPTX
ManageIQ - Sprint 268 Review - Slide Deck
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
Introduction to Artificial Intelligence
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
Digital Strategies for Manufacturing Companies
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PPTX
L1 - Introduction to python Backend.pptx
Navsoft: AI-Powered Business Solutions & Custom Software Development
Upgrade and Innovation Strategies for SAP ERP Customers
CHAPTER 2 - PM Management and IT Context
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
AI in Product Development-omnex systems
System and Network Administration Chapter 2
ManageIQ - Sprint 268 Review - Slide Deck
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Introduction to Artificial Intelligence
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Internet Downloader Manager (IDM) Crack 6.42 Build 41
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Design an Analysis of Algorithms II-SECS-1021-03
Odoo Companies in India – Driving Business Transformation.pdf
Digital Strategies for Manufacturing Companies
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
2025 Textile ERP Trends: SAP, Odoo & Oracle
L1 - Introduction to python Backend.pptx

Event Sourcing with php

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

Editor's Notes

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