CQRS and Event Sourcing in a Symfony application

Samuel ROZE
Samuel ROZEVP of Engineering at Birdie
CQRS and Event Sourcing
in a Symfony application
Samuel Roze
Software Enginner @ Inviqa
4 twitter.com/samuelroze
4 github.com/sroze
4 sroze.io
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
The heart of software is its
ability to solve domain-
related problems for its
user
1
Eric Evans
CQRS and Event Sourcing in a Symfony application
Command Query
Responsibility Segregation
CQRS and Event Sourcing in a Symfony application
CQRS & Event Sourcing
CQRS and Event Sourcing in a Symfony application
How are we going to build that?
1. Our domain
2. Repository and persistence
3. Message buses
4. Automation via "services"
5. Projections
Our domain
A deployment
1. Build Docker images
2. Display the progress
3. Send a notification
An event
interface DeploymentEvent
{
public function getDeploymentUuid() : UuidInterface;
}
Event capability
trait EventsCapability
{
private $events = [];
protected function raise(DeploymentEvent $event)
{
$this->events[] = $event;
}
public function eraseEvents() : void
{
$this->events = [];
}
public function raisedEvents() : array
{
return $this->events;
}
}
Creating the object from events
final class Deployment
{
use RaiseEventsCapability;
private function __construct()
{
}
public static function fromEvents(array $events)
{
$deployment = new self();
foreach ($events as $event) {
$deployment->apply($event);
}
return $deployment;
}
}
Building the object state
final class Deployment
{
private $uuid;
// ...
private function apply(DeploymentEvent $event)
{
if ($event instanceof DeploymentCreated) {
$this->uuid = $event->getUuid();
}
}
}
CQRS and Event Sourcing in a Symfony application
You know... testing!
Scenario:
When I create a deployment
Then a deployment should be created
You know... testing!
Scenario: A deployment need to have at least one image
When I create a deployment with 0 image
Then the deployment should not be valid
Scenario: Deployment with 1 image
When I create a deployment with 1 image
Then a deployment should be created
@When I create a deployment with :number image
public function iCreateADeploymentWithImage($count)
{
try {
$this->deployment = Deployment::create(
Uuid::uuid4(),
array_fill(0, $count, 'image')
);
} catch (Throwable $e) {
$this->exception = $e;
}
}
@Then the deployment should not be valid
public function theDeploymentShouldNotBeValid()
{
if (!$this->exception instanceof InvalidArgumentException) {
throw new RuntimeException(
'The exception found, if any, is not matching'
);
}
}
@Then a deployment should be created
public function aDeploymentShouldBeCreated()
{
$events = $this->deployment->raisedEvents();
$matchingEvents = array_filter($events, function(DeploymentEvent $event) {
return $event instanceof DeploymentCreated;
});
if (count($matchingEvents) === 0) {
throw new RuntimeException('No deployment created found');
}
}
Create... from the beginning!
final class Deployment
{
// ...
public static function create(Uuid $uuid, array $images)
{
if (count($images) == 0) {
throw new InvalidArgumentException('What do you deploy then?');
}
$createdEvent = new DeploymentCreated($uuid, $images);
$deployment = self::fromEvents([$createdEvent]);
$deployment->raise($createdEvent);
return $deployment;
}
}
DeploymentCreated event
final class DeploymentCreated implements DeploymentEvent
{
public function __construct(UuidInterface $uuid, array $images)
{ /* .. */ }
public function getDeploymentUuid()
{
return $this->uuid;
}
public function getImages()
{
return $this->images;
}
}
Wourah!
$ bin/behat -fprogress
....
2 scenarios (2 passed)
4 steps (4 passed)
0m0.12s (40.89Mb)
Starting a deployment?
Scenario: A successfully created deployment can be started
Given a deployment was created
When I start the deployment
Then the deployment should be started
Scenario: A deployment can be started only once
Given a deployment was created and started
When I start the deployment
Then the deployment should be invalid
@Given a deployment was created and started
public function aDeploymentWasCreatedAndStarted()
{
try {
$uuid = Uuid::uuid4();
$this->deployment = Deployment::fromEvents([
new DeploymentCreated($uuid, ['image']),
new DeploymentStarted($uuid),
]);
} catch (Throwable $e) {
$this->exception = $e;
}
}
@When I start the deployment
public function iStartTheDeployment()
{
try {
$this->deployment->start();
} catch (Throwable $e) {
$this->exception = $e;
}
}
starting a deployment
final class Deployment
{
private $uuid;
private $started = false;
// ...
public function start()
{
if ($this->started) {
throw new InvalidArgumentException('Deployment already started');
}
$this->raise(new DeploymentStarted($this->uuid));
}
public function apply(DeploymentEvent $event)
{
// ...
if ($event instanceof DeploymentStarted) {
$this->started = true;
}
}
}
That's too fast...
$ bin/behat -fprogress
.........
4 scenarios (4 passed)
10 steps (10 passed)
0m0.31s (41.22Mb)
We are done!
...with your domain
Repositories & Persistence
Event Store
interface EventStore
{
public function findByDeploymentUuid(UuidInterface $uuid) : array;
public function add(DeploymentEvent $event);
}
Implementation detail: InMemory / Doctrine /
Custom / ...
Our repository contract
interface DeploymentRepository
{
public function find(UuidInterface $uuid) : Deployment;
}
The event-based implementation
final class EventBasedDeploymentRepository implements DeploymentRepository
{
public function __construct(EventStore $eventStore)
{ /** .. **/ }
public function find(UuidInterface $uuid) : Deployment
{
return Deployment::fromEvents(
$this->eventStore->findByDeploymentUuid($uuid)
);
}
}
The plumbing
Message Buses
SimpleBus
4 Written by Matthias Noback
http://simplebus.github.io/SymfonyBridge/
# app/config/config.yml
event_bus:
logging: ~
command_bus:
logging: ~
Our HTTP interface (without commands)
final class DeploymentController
{
private $eventBus;
public function __construct(MessageBus $eventBus)
{ /* ... */ }
public function createAction(Request $request)
{
$deployment = Deployment::create(
Uuid::uuid4(),
$request->request->get('docker-images')
);
foreach ($deployment->raisedEvents() as $event) {
$this->eventBus->handle($event);
}
return new Response(Response::HTTP_CREATED);
}
}
Our HTTP interface (with commands)
final class DeploymentController
{
private $commandBus;
public function __construct(MessageBus $commandBus)
{ /* ... */ }
public function createAction(Request $request)
{
$uuid = Uuid::uuid4();
$this->commandBus->handle(new CreateDeployment(
$uuid,
$request->request->get('docker-images')
));
return new Response(Response::HTTP_CREATED);
}
}
Command Handler
final class CreateDeploymentHandler
{
private $eventBus;
public function __construct(MessageBus $eventBus)
{ /* ... */ }
public function handle(CreateDeployment $command)
{
$deployment = Deployment::create(
$command->getUuid(),
$command->getImages()
);
foreach ($deployment->raisedEvents() as $event) {
$this->eventBus->handle($event);
}
}
}
The plumbing
<service id="app.controller.deployment"
class="AppBundleControllerDeploymentController">
<argument type="service" id="command_bus" />
</service>
<service id="app.handler.create_deployment"
class="AppDeploymentHandlerCreateDeploymentHandler">
<argument type="service" id="event_bus" />
<tag name="command_handler" handles="AppCommandCreateDeployment" />
</service>
What do we have right now?
1. Send a command from an HTTP API
2. The command handler talks to our domain
3. Domain raise an event
4. The event is dispatched to the event bus
Storing our events
final class DeploymentEventStoreMiddleware implements MessageBusMiddleware
{
private $eventStore;
public function __construct(EventStore $eventStore)
{
$this->eventStore = $eventStore;
}
public function handle($message, callable $next)
{
if ($message instanceof DeploymentEvent) {
$this->eventStore->add($message);
}
$next($message);
}
}
We <3 XML
<service id="app.event_bus.middleware.store_events"
class="AppEventBusMiddlewareStoreEvents">
<argument type="service" id="event_store" />
<tag name="event_bus_middleware" />
</service>
Our events are stored!
...so we can get our Deployment from
the repository
Let's start our deployment!
final class StartDeploymentWhenCreated
{
private $commandBus;
public function __construct(MessageBus $commandBus)
{ /* ... */ }
public function notify(DeploymentCreated $event)
{
// There will be conditions here...
$this->commandBus->handle(new StartDeployment(
$event->getDeploymentUuid()
));
}
}
The handler
final class StartDeploymentHandler
{
public function __construct(DeploymentRepository $repository, MessageBus $eventBus)
{ /* ... */ }
public function handle(StartDeployment $command)
{
$deployment = $this->repository->find($command->getDeploymentUuid());
$deployment->start();
foreach ($deployment->raisedEvents() as $event) {
$this->eventBus->handle($event);
}
}
}
The plumbing
<service id="app.deployment.auto_start.starts_when_created"
class="AppDeploymentAutoStartStartsWhenCreated">
<argument type="service" id="command_bus" />
<tag name="event_subscriber"
subscribes_to="AppEventDeploymentCreated" />
</service>
<service id="app.deployment.handler.start_deployment"
class="AppDeploymentHandlerStartDeploymentHandler">
<argument type="service" id="app.deployment_repository" />
<argument type="service" id="event_bus" />
<tag name="command_handler" handles="AppCommandStartDeployment" />
</service>
What happened?
[...]
4. A dispatched DeploymentCreated event
5. A listener created a StartDeployment command
6. The command handler called the start method on the
Deployment
7. The domain validated and raised a DeploymentStarted
event
8. The DeploymentStarted was dispatched on the event-
You'll go further...
final class Deployment
{
// ...
public function finishedBuild(Build $build)
{
if ($build->isFailure()) {
return $this->raise(new DeploymentFailed($this->uuid));
}
$this->builtImages[] = $build->getImage();
if (count($this->builtImages) == count($this->images)) {
$this->raise(new DeploymentSuccessful($this->uuid));
}
}
}
Dependencies... the wrong way
final class Deployment
{
private $notifier;
public function __construct(NotifierInterface $notifier)
{ /* .. */ }
public function notify()
{
$this->notifier->notify($this);
}
}
Dependencies... the right way
final class Deployment
{
public function notify(NotifierInterface $notifier)
{
$notifier->notify($this);
}
}
Projections!
final class DeploymentStatusProjector
{
public function __construct(
DeploymentRepository $repository,
DeploymentStatusProjectionStorage $storage
) { /* ... */ }
public function notify(DeploymentEvent $event)
{
$uuid = $event->getDeploymentUuid();
$deployment = $this->repository->find($uuid);
$percentage = count($deployment->getBuiltImages())
/ count($deployment->getImages());
$this->storage->store($uuid, [
'started' => $deployment->isStarted(),
'percentage' => $percentage,
]);
}
}
You can have many
projections and storage
backends for just one
aggregate.
Testing! (layers)
1. Use your domain objects
2. Create commands and read your event store
3. Uses your API and projections
What we just achieved
1. Incoming HTTP requests
2. Commands to the command bus
3. Handlers talk to your domain
4. Domain produces events
5. Events are stored and dispatched
6. Projections built for fast query
Thank you!
@samuelroze
continuouspipe.io
https://joind.in/talk/62c40
1 of 59

Recommended

Use Symfony Messenger Component and CQRS! by
Use Symfony Messenger Component and CQRS!Use Symfony Messenger Component and CQRS!
Use Symfony Messenger Component and CQRS!Žilvinas Kuusas
904 views48 slides
Event sourcing w PHP (by Piotr Kacała) by
Event sourcing w PHP (by Piotr Kacała)Event sourcing w PHP (by Piotr Kacała)
Event sourcing w PHP (by Piotr Kacała)GOG.com dev team
5K views41 slides
Axon Framework, Exploring CQRS and Event Sourcing Architecture by
Axon Framework, Exploring CQRS and Event Sourcing ArchitectureAxon Framework, Exploring CQRS and Event Sourcing Architecture
Axon Framework, Exploring CQRS and Event Sourcing ArchitectureAshutosh Jadhav
2.4K views30 slides
Managing Egress with Istio by
Managing Egress with IstioManaging Egress with Istio
Managing Egress with IstioSolo.io
1.2K views31 slides
Thrift vs Protocol Buffers vs Avro - Biased Comparison by
Thrift vs Protocol Buffers vs Avro - Biased ComparisonThrift vs Protocol Buffers vs Avro - Biased Comparison
Thrift vs Protocol Buffers vs Avro - Biased ComparisonIgor Anishchenko
240.7K views51 slides
REST API by
REST APIREST API
REST APITofazzal Ahmed
1.8K views17 slides

More Related Content

What's hot

Advanced javascript by
Advanced javascriptAdvanced javascript
Advanced javascriptDoeun KOCH
487 views56 slides
Node.js Express by
Node.js  ExpressNode.js  Express
Node.js ExpressEyal Vardi
4.8K views26 slides
Rxjs ppt by
Rxjs pptRxjs ppt
Rxjs pptChristoffer Noring
3.7K views78 slides
Docker internals by
Docker internalsDocker internals
Docker internalsRohit Jnagal
6.9K views12 slides
Introduction to RxJS by
Introduction to RxJSIntroduction to RxJS
Introduction to RxJSAbul Hasan
117 views34 slides

What's hot(20)

Advanced javascript by Doeun KOCH
Advanced javascriptAdvanced javascript
Advanced javascript
Doeun KOCH487 views
Node.js Express by Eyal Vardi
Node.js  ExpressNode.js  Express
Node.js Express
Eyal Vardi4.8K views
Introduction to RxJS by Abul Hasan
Introduction to RxJSIntroduction to RxJS
Introduction to RxJS
Abul Hasan117 views
React.js - The Dawn of Virtual DOM by Jimit Shah
React.js - The Dawn of Virtual DOMReact.js - The Dawn of Virtual DOM
React.js - The Dawn of Virtual DOM
Jimit Shah1.4K views
Introduction to Node.js by Rob O'Doherty
Introduction to Node.jsIntroduction to Node.js
Introduction to Node.js
Rob O'Doherty2.9K views
RxJS Operators - Real World Use Cases (FULL VERSION) by Tracy Lee
RxJS Operators - Real World Use Cases (FULL VERSION)RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)
Tracy Lee4.8K views
Clean architecture with ddd layering in php by Leonardo Proietti
Clean architecture with ddd layering in phpClean architecture with ddd layering in php
Clean architecture with ddd layering in php
Leonardo Proietti38.9K views
Architecting for the Cloud using NetflixOSS - Codemash Workshop by Sudhir Tonse
Architecting for the Cloud using NetflixOSS - Codemash WorkshopArchitecting for the Cloud using NetflixOSS - Codemash Workshop
Architecting for the Cloud using NetflixOSS - Codemash Workshop
Sudhir Tonse39.6K views
Reactive programming by SUDIP GHOSH
Reactive programmingReactive programming
Reactive programming
SUDIP GHOSH215 views
Developing event-driven microservices with event sourcing and CQRS (svcc, sv... by Chris Richardson
Developing event-driven microservices with event sourcing and CQRS  (svcc, sv...Developing event-driven microservices with event sourcing and CQRS  (svcc, sv...
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...
Chris Richardson55.2K views
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023 by Steve Pember
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
Steve Pember1.9K views
Docker and the Linux Kernel by Docker, Inc.
Docker and the Linux KernelDocker and the Linux Kernel
Docker and the Linux Kernel
Docker, Inc.16.7K views
Javaday Paris 2022 - Java en 2022 : profiter de Java 17 by Jean-Michel Doudoux
Javaday Paris 2022 - Java en 2022 : profiter de Java 17Javaday Paris 2022 - Java en 2022 : profiter de Java 17
Javaday Paris 2022 - Java en 2022 : profiter de Java 17

Viewers also liked

When cqrs meets event sourcing by
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcingManel Sellés
4.3K views45 slides
Symfony 2 : Performances et Optimisations by
Symfony 2 : Performances et OptimisationsSymfony 2 : Performances et Optimisations
Symfony 2 : Performances et OptimisationsLes-Tilleuls.coop
14.3K views20 slides
Introduction to CQRS and Event Sourcing by
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingSamuel ROZE
1.8K views74 slides
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin... by
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...Alexander Lisachenko
8.5K views135 slides
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C... by
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Ryan Weaver
13.7K views103 slides
Transactions redefined by
Transactions redefinedTransactions redefined
Transactions redefinedAlberto Brandolini
4.4K views186 slides

Viewers also liked(20)

When cqrs meets event sourcing by Manel Sellés
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcing
Manel Sellés4.3K views
Symfony 2 : Performances et Optimisations by Les-Tilleuls.coop
Symfony 2 : Performances et OptimisationsSymfony 2 : Performances et Optimisations
Symfony 2 : Performances et Optimisations
Les-Tilleuls.coop14.3K views
Introduction to CQRS and Event Sourcing by Samuel ROZE
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
Samuel ROZE1.8K views
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin... by Alexander Lisachenko
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C... by Ryan Weaver
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Ryan Weaver13.7K views
A year with event sourcing and CQRS by Steve Pember
A year with event sourcing and CQRSA year with event sourcing and CQRS
A year with event sourcing and CQRS
Steve Pember4.6K views
From C to Q one event at a time: Event Sourcing illustrated by Lorenzo Nicora
From C to Q one event at a time: Event Sourcing illustratedFrom C to Q one event at a time: Event Sourcing illustrated
From C to Q one event at a time: Event Sourcing illustrated
Lorenzo Nicora4.9K views
Microservice Architecture with CQRS and Event Sourcing by Ben Wilcock
Microservice Architecture with CQRS and Event SourcingMicroservice Architecture with CQRS and Event Sourcing
Microservice Architecture with CQRS and Event Sourcing
Ben Wilcock8K views
Refactorizando Pccomponentes.com con Symfony by Mario Marín
Refactorizando Pccomponentes.com con SymfonyRefactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con Symfony
Mario Marín1.5K views
PHP 7 et Symfony 3 by Eddy RICHARD
PHP 7 et Symfony 3PHP 7 et Symfony 3
PHP 7 et Symfony 3
Eddy RICHARD3.1K views
Get Soaked - An In Depth Look At PHP Streams by Davey Shafik
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP Streams
Davey Shafik12.1K views
PHP5.5 is Here by julien pauli
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
julien pauli13.6K views
Automation using-phing by Rajat Pandit
Automation using-phingAutomation using-phing
Automation using-phing
Rajat Pandit4.1K views
Electrify your code with PHP Generators by Mark Baker
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
Mark Baker3.7K views
The quest for global design principles (SymfonyLive Berlin 2015) by Matthias Noback
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)
Matthias Noback3.8K views

Similar to CQRS and Event Sourcing in a Symfony application

Decoupling with Design Patterns and Symfony2 DIC by
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
6.5K views97 slides
How Kris Writes Symfony Apps by
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
17.1K views116 slides
Unittests für Dummies by
Unittests für DummiesUnittests für Dummies
Unittests für DummiesLars Jankowfsky
1K views28 slides
Android workshop by
Android workshopAndroid workshop
Android workshopMichael Galpin
1.1K views110 slides
Leaving Flatland: getting started with WebGL by
Leaving Flatland: getting started with WebGLLeaving Flatland: getting started with WebGL
Leaving Flatland: getting started with WebGLgerbille
20.1K views41 slides
Prototype UI by
Prototype UIPrototype UI
Prototype UISébastien Gruhier
717 views25 slides

Similar to CQRS and Event Sourcing in a Symfony application(20)

How Kris Writes Symfony Apps by Kris Wallsmith
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Kris Wallsmith17.1K views
Leaving Flatland: getting started with WebGL by gerbille
Leaving Flatland: getting started with WebGLLeaving Flatland: getting started with WebGL
Leaving Flatland: getting started with WebGL
gerbille20.1K views
JavaScript APIs - The Web is the Platform - MDN Hack Day - Buenos Aires by Robert Nyman
JavaScript APIs - The Web is the Platform - MDN Hack Day - Buenos AiresJavaScript APIs - The Web is the Platform - MDN Hack Day - Buenos Aires
JavaScript APIs - The Web is the Platform - MDN Hack Day - Buenos Aires
Robert Nyman5.2K views
Doctrine For Beginners by Jonathan Wage
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage1.5K views
JavaScript APIs - The Web is the Platform - MDN Hack Day, Sao Paulo by Robert Nyman
JavaScript APIs - The Web is the Platform - MDN Hack Day, Sao PauloJavaScript APIs - The Web is the Platform - MDN Hack Day, Sao Paulo
JavaScript APIs - The Web is the Platform - MDN Hack Day, Sao Paulo
Robert Nyman27.5K views
Mobile Software Engineering Crash Course - C04 Android Cont. by Mohammad Shaker
Mobile Software Engineering Crash Course - C04 Android Cont.Mobile Software Engineering Crash Course - C04 Android Cont.
Mobile Software Engineering Crash Course - C04 Android Cont.
Mohammad Shaker764 views
GDG GeorgeTown Devfest 2014 Presentation: Android Wear: A Developer's Perspec... by mharkus
GDG GeorgeTown Devfest 2014 Presentation: Android Wear: A Developer's Perspec...GDG GeorgeTown Devfest 2014 Presentation: Android Wear: A Developer's Perspec...
GDG GeorgeTown Devfest 2014 Presentation: Android Wear: A Developer's Perspec...
mharkus2.1K views
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires by Robert Nyman
JavaScript APIs - The Web is the Platform - MozCamp, Buenos AiresJavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
Robert Nyman4.2K views
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile by Robert Nyman
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, ChileJavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
Robert Nyman6.2K views
Phpne august-2012-symfony-components-friends by Michael Peacock
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
Michael Peacock2.8K views
ZF2 for the ZF1 Developer by Gary Hockin
ZF2 for the ZF1 DeveloperZF2 for the ZF1 Developer
ZF2 for the ZF1 Developer
Gary Hockin3.3K views
A Series of Fortunate Events - Symfony Camp Sweden 2014 by Matthias Noback
A Series of Fortunate Events - Symfony Camp Sweden 2014A Series of Fortunate Events - Symfony Camp Sweden 2014
A Series of Fortunate Events - Symfony Camp Sweden 2014
Matthias Noback2.5K views
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo by Robert Nyman
JavaScript APIs - The Web is the Platform - MDN Hack Day, MontevideoJavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
Robert Nyman5.3K views
Writing Maintainable JavaScript by Andrew Dupont
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
Andrew Dupont3.1K views

More from Samuel ROZE

Event streaming: what will go wrong? (Symfony World 2020) by
Event streaming: what will go wrong? (Symfony World 2020)Event streaming: what will go wrong? (Symfony World 2020)
Event streaming: what will go wrong? (Symfony World 2020)Samuel ROZE
733 views47 slides
Living documentation by
Living documentationLiving documentation
Living documentationSamuel ROZE
778 views106 slides
How I started to love design patterns by
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
1.4K views44 slides
Symfony Messenger (Symfony Live San Francisco) by
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Samuel ROZE
1.1K views54 slides
Micro services may not be the best idea by
Micro services may not be the best ideaMicro services may not be the best idea
Micro services may not be the best ideaSamuel ROZE
401 views25 slides
How I started to love design patterns by
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
1.3K views47 slides

More from Samuel ROZE(13)

Event streaming: what will go wrong? (Symfony World 2020) by Samuel ROZE
Event streaming: what will go wrong? (Symfony World 2020)Event streaming: what will go wrong? (Symfony World 2020)
Event streaming: what will go wrong? (Symfony World 2020)
Samuel ROZE733 views
Living documentation by Samuel ROZE
Living documentationLiving documentation
Living documentation
Samuel ROZE778 views
How I started to love design patterns by Samuel ROZE
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
Samuel ROZE1.4K views
Symfony Messenger (Symfony Live San Francisco) by Samuel ROZE
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)
Samuel ROZE1.1K views
Micro services may not be the best idea by Samuel ROZE
Micro services may not be the best ideaMicro services may not be the best idea
Micro services may not be the best idea
Samuel ROZE401 views
How I started to love design patterns by Samuel ROZE
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
Samuel ROZE1.3K views
Take care of our micro services by Samuel ROZE
Take care of our micro servicesTake care of our micro services
Take care of our micro services
Samuel ROZE488 views
(micro)services avec Symfony et Tolerance by Samuel ROZE
(micro)services avec Symfony et Tolerance(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance
Samuel ROZE910 views
Using continuouspipe to speed up our workflows by Samuel ROZE
Using continuouspipe to speed up our workflowsUsing continuouspipe to speed up our workflows
Using continuouspipe to speed up our workflows
Samuel ROZE520 views
Symfony CoP: Form component by Samuel ROZE
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
Samuel ROZE815 views
Behat c'est plus que ça | Behat is more than that by Samuel ROZE
Behat c'est plus que ça | Behat is more than thatBehat c'est plus que ça | Behat is more than that
Behat c'est plus que ça | Behat is more than that
Samuel ROZE4.7K views
Docker orchestration with Kubernetes by Samuel ROZE
Docker orchestration with KubernetesDocker orchestration with Kubernetes
Docker orchestration with Kubernetes
Samuel ROZE1.1K views
Symfony et serialization avec JMS serializer by Samuel ROZE
Symfony et serialization avec JMS serializer Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer
Samuel ROZE3.3K views

Recently uploaded

Saikat Chakraborty Java Oracle Certificate.pdf by
Saikat Chakraborty Java Oracle Certificate.pdfSaikat Chakraborty Java Oracle Certificate.pdf
Saikat Chakraborty Java Oracle Certificate.pdfSaikatChakraborty787148
14 views1 slide
Digital Watermarking Of Audio Signals.pptx by
Digital Watermarking Of Audio Signals.pptxDigital Watermarking Of Audio Signals.pptx
Digital Watermarking Of Audio Signals.pptxAyushJaiswal781174
8 views25 slides
CHEMICAL KINETICS.pdf by
CHEMICAL KINETICS.pdfCHEMICAL KINETICS.pdf
CHEMICAL KINETICS.pdfAguedaGutirrez
8 views337 slides
STUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptx by
STUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptxSTUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptx
STUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptxAnnieRachelJohn
31 views34 slides
Electrical Crimping by
Electrical CrimpingElectrical Crimping
Electrical CrimpingIwiss Tools Co.,Ltd
21 views22 slides
Plumbing by
PlumbingPlumbing
PlumbingIwiss Tools Co.,Ltd
15 views14 slides

Recently uploaded(20)

STUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptx by AnnieRachelJohn
STUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptxSTUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptx
STUDY OF SMART MATERIALS USED IN CONSTRUCTION-1.pptx
AnnieRachelJohn31 views
13_DVD_Latch-up_prevention.pdf by Usha Mehta
13_DVD_Latch-up_prevention.pdf13_DVD_Latch-up_prevention.pdf
13_DVD_Latch-up_prevention.pdf
Usha Mehta10 views
Update 42 models(Diode/General ) in SPICE PARK(DEC2023) by Tsuyoshi Horigome
Update 42 models(Diode/General ) in SPICE PARK(DEC2023)Update 42 models(Diode/General ) in SPICE PARK(DEC2023)
Update 42 models(Diode/General ) in SPICE PARK(DEC2023)
Performance of Back-to-Back Mechanically Stabilized Earth Walls Supporting th... by ahmedmesaiaoun
Performance of Back-to-Back Mechanically Stabilized Earth Walls Supporting th...Performance of Back-to-Back Mechanically Stabilized Earth Walls Supporting th...
Performance of Back-to-Back Mechanically Stabilized Earth Walls Supporting th...
ahmedmesaiaoun12 views
MSA Website Slideshow (16).pdf by msaucla
MSA Website Slideshow (16).pdfMSA Website Slideshow (16).pdf
MSA Website Slideshow (16).pdf
msaucla46 views
2_DVD_ASIC_Design_FLow.pdf by Usha Mehta
2_DVD_ASIC_Design_FLow.pdf2_DVD_ASIC_Design_FLow.pdf
2_DVD_ASIC_Design_FLow.pdf
Usha Mehta19 views

CQRS and Event Sourcing in a Symfony application

  • 1. CQRS and Event Sourcing in a Symfony application
  • 2. Samuel Roze Software Enginner @ Inviqa 4 twitter.com/samuelroze 4 github.com/sroze 4 sroze.io
  • 5. The heart of software is its ability to solve domain- related problems for its user 1 Eric Evans
  • 9. CQRS & Event Sourcing
  • 11. How are we going to build that? 1. Our domain 2. Repository and persistence 3. Message buses 4. Automation via "services" 5. Projections
  • 12. Our domain A deployment 1. Build Docker images 2. Display the progress 3. Send a notification
  • 13. An event interface DeploymentEvent { public function getDeploymentUuid() : UuidInterface; }
  • 14. Event capability trait EventsCapability { private $events = []; protected function raise(DeploymentEvent $event) { $this->events[] = $event; } public function eraseEvents() : void { $this->events = []; } public function raisedEvents() : array { return $this->events; } }
  • 15. Creating the object from events final class Deployment { use RaiseEventsCapability; private function __construct() { } public static function fromEvents(array $events) { $deployment = new self(); foreach ($events as $event) { $deployment->apply($event); } return $deployment; } }
  • 16. Building the object state final class Deployment { private $uuid; // ... private function apply(DeploymentEvent $event) { if ($event instanceof DeploymentCreated) { $this->uuid = $event->getUuid(); } } }
  • 18. You know... testing! Scenario: When I create a deployment Then a deployment should be created
  • 19. You know... testing! Scenario: A deployment need to have at least one image When I create a deployment with 0 image Then the deployment should not be valid Scenario: Deployment with 1 image When I create a deployment with 1 image Then a deployment should be created
  • 20. @When I create a deployment with :number image public function iCreateADeploymentWithImage($count) { try { $this->deployment = Deployment::create( Uuid::uuid4(), array_fill(0, $count, 'image') ); } catch (Throwable $e) { $this->exception = $e; } }
  • 21. @Then the deployment should not be valid public function theDeploymentShouldNotBeValid() { if (!$this->exception instanceof InvalidArgumentException) { throw new RuntimeException( 'The exception found, if any, is not matching' ); } }
  • 22. @Then a deployment should be created public function aDeploymentShouldBeCreated() { $events = $this->deployment->raisedEvents(); $matchingEvents = array_filter($events, function(DeploymentEvent $event) { return $event instanceof DeploymentCreated; }); if (count($matchingEvents) === 0) { throw new RuntimeException('No deployment created found'); } }
  • 23. Create... from the beginning! final class Deployment { // ... public static function create(Uuid $uuid, array $images) { if (count($images) == 0) { throw new InvalidArgumentException('What do you deploy then?'); } $createdEvent = new DeploymentCreated($uuid, $images); $deployment = self::fromEvents([$createdEvent]); $deployment->raise($createdEvent); return $deployment; } }
  • 24. DeploymentCreated event final class DeploymentCreated implements DeploymentEvent { public function __construct(UuidInterface $uuid, array $images) { /* .. */ } public function getDeploymentUuid() { return $this->uuid; } public function getImages() { return $this->images; } }
  • 25. Wourah! $ bin/behat -fprogress .... 2 scenarios (2 passed) 4 steps (4 passed) 0m0.12s (40.89Mb)
  • 26. Starting a deployment? Scenario: A successfully created deployment can be started Given a deployment was created When I start the deployment Then the deployment should be started Scenario: A deployment can be started only once Given a deployment was created and started When I start the deployment Then the deployment should be invalid
  • 27. @Given a deployment was created and started public function aDeploymentWasCreatedAndStarted() { try { $uuid = Uuid::uuid4(); $this->deployment = Deployment::fromEvents([ new DeploymentCreated($uuid, ['image']), new DeploymentStarted($uuid), ]); } catch (Throwable $e) { $this->exception = $e; } }
  • 28. @When I start the deployment public function iStartTheDeployment() { try { $this->deployment->start(); } catch (Throwable $e) { $this->exception = $e; } }
  • 29. starting a deployment final class Deployment { private $uuid; private $started = false; // ... public function start() { if ($this->started) { throw new InvalidArgumentException('Deployment already started'); } $this->raise(new DeploymentStarted($this->uuid)); } public function apply(DeploymentEvent $event) { // ... if ($event instanceof DeploymentStarted) { $this->started = true; } } }
  • 30. That's too fast... $ bin/behat -fprogress ......... 4 scenarios (4 passed) 10 steps (10 passed) 0m0.31s (41.22Mb)
  • 31. We are done! ...with your domain
  • 33. Event Store interface EventStore { public function findByDeploymentUuid(UuidInterface $uuid) : array; public function add(DeploymentEvent $event); } Implementation detail: InMemory / Doctrine / Custom / ...
  • 34. Our repository contract interface DeploymentRepository { public function find(UuidInterface $uuid) : Deployment; }
  • 35. The event-based implementation final class EventBasedDeploymentRepository implements DeploymentRepository { public function __construct(EventStore $eventStore) { /** .. **/ } public function find(UuidInterface $uuid) : Deployment { return Deployment::fromEvents( $this->eventStore->findByDeploymentUuid($uuid) ); } }
  • 37. SimpleBus 4 Written by Matthias Noback http://simplebus.github.io/SymfonyBridge/ # app/config/config.yml event_bus: logging: ~ command_bus: logging: ~
  • 38. Our HTTP interface (without commands) final class DeploymentController { private $eventBus; public function __construct(MessageBus $eventBus) { /* ... */ } public function createAction(Request $request) { $deployment = Deployment::create( Uuid::uuid4(), $request->request->get('docker-images') ); foreach ($deployment->raisedEvents() as $event) { $this->eventBus->handle($event); } return new Response(Response::HTTP_CREATED); } }
  • 39. Our HTTP interface (with commands) final class DeploymentController { private $commandBus; public function __construct(MessageBus $commandBus) { /* ... */ } public function createAction(Request $request) { $uuid = Uuid::uuid4(); $this->commandBus->handle(new CreateDeployment( $uuid, $request->request->get('docker-images') )); return new Response(Response::HTTP_CREATED); } }
  • 40. Command Handler final class CreateDeploymentHandler { private $eventBus; public function __construct(MessageBus $eventBus) { /* ... */ } public function handle(CreateDeployment $command) { $deployment = Deployment::create( $command->getUuid(), $command->getImages() ); foreach ($deployment->raisedEvents() as $event) { $this->eventBus->handle($event); } } }
  • 41. The plumbing <service id="app.controller.deployment" class="AppBundleControllerDeploymentController"> <argument type="service" id="command_bus" /> </service> <service id="app.handler.create_deployment" class="AppDeploymentHandlerCreateDeploymentHandler"> <argument type="service" id="event_bus" /> <tag name="command_handler" handles="AppCommandCreateDeployment" /> </service>
  • 42. What do we have right now? 1. Send a command from an HTTP API 2. The command handler talks to our domain 3. Domain raise an event 4. The event is dispatched to the event bus
  • 43. Storing our events final class DeploymentEventStoreMiddleware implements MessageBusMiddleware { private $eventStore; public function __construct(EventStore $eventStore) { $this->eventStore = $eventStore; } public function handle($message, callable $next) { if ($message instanceof DeploymentEvent) { $this->eventStore->add($message); } $next($message); } }
  • 44. We <3 XML <service id="app.event_bus.middleware.store_events" class="AppEventBusMiddlewareStoreEvents"> <argument type="service" id="event_store" /> <tag name="event_bus_middleware" /> </service>
  • 45. Our events are stored! ...so we can get our Deployment from the repository
  • 46. Let's start our deployment! final class StartDeploymentWhenCreated { private $commandBus; public function __construct(MessageBus $commandBus) { /* ... */ } public function notify(DeploymentCreated $event) { // There will be conditions here... $this->commandBus->handle(new StartDeployment( $event->getDeploymentUuid() )); } }
  • 47. The handler final class StartDeploymentHandler { public function __construct(DeploymentRepository $repository, MessageBus $eventBus) { /* ... */ } public function handle(StartDeployment $command) { $deployment = $this->repository->find($command->getDeploymentUuid()); $deployment->start(); foreach ($deployment->raisedEvents() as $event) { $this->eventBus->handle($event); } } }
  • 48. The plumbing <service id="app.deployment.auto_start.starts_when_created" class="AppDeploymentAutoStartStartsWhenCreated"> <argument type="service" id="command_bus" /> <tag name="event_subscriber" subscribes_to="AppEventDeploymentCreated" /> </service> <service id="app.deployment.handler.start_deployment" class="AppDeploymentHandlerStartDeploymentHandler"> <argument type="service" id="app.deployment_repository" /> <argument type="service" id="event_bus" /> <tag name="command_handler" handles="AppCommandStartDeployment" /> </service>
  • 49. What happened? [...] 4. A dispatched DeploymentCreated event 5. A listener created a StartDeployment command 6. The command handler called the start method on the Deployment 7. The domain validated and raised a DeploymentStarted event 8. The DeploymentStarted was dispatched on the event-
  • 51. final class Deployment { // ... public function finishedBuild(Build $build) { if ($build->isFailure()) { return $this->raise(new DeploymentFailed($this->uuid)); } $this->builtImages[] = $build->getImage(); if (count($this->builtImages) == count($this->images)) { $this->raise(new DeploymentSuccessful($this->uuid)); } } }
  • 52. Dependencies... the wrong way final class Deployment { private $notifier; public function __construct(NotifierInterface $notifier) { /* .. */ } public function notify() { $this->notifier->notify($this); } }
  • 53. Dependencies... the right way final class Deployment { public function notify(NotifierInterface $notifier) { $notifier->notify($this); } }
  • 55. final class DeploymentStatusProjector { public function __construct( DeploymentRepository $repository, DeploymentStatusProjectionStorage $storage ) { /* ... */ } public function notify(DeploymentEvent $event) { $uuid = $event->getDeploymentUuid(); $deployment = $this->repository->find($uuid); $percentage = count($deployment->getBuiltImages()) / count($deployment->getImages()); $this->storage->store($uuid, [ 'started' => $deployment->isStarted(), 'percentage' => $percentage, ]); } }
  • 56. You can have many projections and storage backends for just one aggregate.
  • 57. Testing! (layers) 1. Use your domain objects 2. Create commands and read your event store 3. Uses your API and projections
  • 58. What we just achieved 1. Incoming HTTP requests 2. Commands to the command bus 3. Handlers talk to your domain 4. Domain produces events 5. Events are stored and dispatched 6. Projections built for fast query