SlideShare une entreprise Scribd logo
1  sur  59
Télécharger pour lire hors ligne
CQRS and Event Sourcing
in a Symfony application
Samuel Roze
Software Enginner @ Inviqa
4 twitter.com/samuelroze
4 github.com/sroze
4 sroze.io
The heart of software is its
ability to solve domain-
related problems for its
user
1
Eric Evans
Command Query
Responsibility Segregation
CQRS & Event Sourcing
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();
}
}
}
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

Contenu connexe

Tendances

Tendances (20)

Cours design pattern m youssfi partie 6 proxy
Cours design pattern m youssfi partie 6 proxyCours design pattern m youssfi partie 6 proxy
Cours design pattern m youssfi partie 6 proxy
 
Springboot Microservices
Springboot MicroservicesSpringboot Microservices
Springboot Microservices
 
NGINX Ingress Controller for Kubernetes
NGINX Ingress Controller for KubernetesNGINX Ingress Controller for Kubernetes
NGINX Ingress Controller for Kubernetes
 
Clean architecture with ddd layering in php
Clean architecture with ddd layering in phpClean architecture with ddd layering in php
Clean architecture with ddd layering in php
 
Docker & kubernetes
Docker & kubernetesDocker & kubernetes
Docker & kubernetes
 
Spring Framework Petclinic sample application
Spring Framework Petclinic sample applicationSpring Framework Petclinic sample application
Spring Framework Petclinic sample application
 
Dependency injection in Java, from naive to functional
Dependency injection in Java, from naive to functionalDependency injection in Java, from naive to functional
Dependency injection in Java, from naive to functional
 
Spring boot introduction
Spring boot introductionSpring boot introduction
Spring boot introduction
 
Docker Introduction
Docker IntroductionDocker Introduction
Docker Introduction
 
Manipulating Android tasks and back stack
Manipulating Android tasks and back stackManipulating Android tasks and back stack
Manipulating Android tasks and back stack
 
Java 8 Stream API. A different way to process collections.
Java 8 Stream API. A different way to process collections.Java 8 Stream API. A different way to process collections.
Java 8 Stream API. A different way to process collections.
 
Android Toast.pdf
Android Toast.pdfAndroid Toast.pdf
Android Toast.pdf
 
Introduction à spring boot
Introduction à spring bootIntroduction à spring boot
Introduction à spring boot
 
Building a REST Service in minutes with Spring Boot
Building a REST Service in minutes with Spring BootBuilding a REST Service in minutes with Spring Boot
Building a REST Service in minutes with Spring Boot
 
Docker containers
Docker containersDocker containers
Docker containers
 
Docker Introduction
Docker IntroductionDocker Introduction
Docker Introduction
 
[Android] Services and Broadcast Receivers
[Android] Services and Broadcast Receivers[Android] Services and Broadcast Receivers
[Android] Services and Broadcast Receivers
 
Nodejs functions & modules
Nodejs functions & modulesNodejs functions & modules
Nodejs functions & modules
 
Spring boot
Spring bootSpring boot
Spring boot
 
Symfony in microservice architecture
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architecture
 

En vedette

A year with event sourcing and CQRS
A year with event sourcing and CQRSA year with event sourcing and CQRS
A year with event sourcing and CQRS
Steve Pember
 
Get Soaked - An In Depth Look At PHP Streams
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 Shafik
 

En vedette (20)

When cqrs meets event sourcing
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcing
 
Event sourcing w PHP (by Piotr Kacała)
Event sourcing w PHP (by Piotr Kacała)Event sourcing w PHP (by Piotr Kacała)
Event sourcing w PHP (by Piotr Kacała)
 
Symfony 2 : Performances et Optimisations
Symfony 2 : Performances et OptimisationsSymfony 2 : Performances et Optimisations
Symfony 2 : Performances et Optimisations
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
 
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...
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
 
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...
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...
 
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...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
 
Transactions redefined
Transactions redefinedTransactions redefined
Transactions redefined
 
A year with event sourcing and CQRS
A year with event sourcing and CQRSA year with event sourcing and CQRS
A year with event sourcing and CQRS
 
From C to Q one event at a time: Event Sourcing illustrated
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
 
Microservice Architecture with CQRS and Event Sourcing
Microservice Architecture with CQRS and Event SourcingMicroservice Architecture with CQRS and Event Sourcing
Microservice Architecture with CQRS and Event Sourcing
 
Refactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con SymfonyRefactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con Symfony
 
PHP 7 et Symfony 3
PHP 7 et Symfony 3PHP 7 et Symfony 3
PHP 7 et Symfony 3
 
CQRS
CQRSCQRS
CQRS
 
Elastic Searching With PHP
Elastic Searching With PHPElastic Searching With PHP
Elastic Searching With PHP
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages web
 
Diving deep into twig
Diving deep into twigDiving deep into twig
Diving deep into twig
 
Get Soaked - An In Depth Look At PHP Streams
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
 
PHP5.5 is Here
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phing
 

Similaire à CQRS and Event Sourcing in a Symfony application

JavaScript APIs - The Web is the Platform - MDN Hack Day - Buenos Aires
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 Nyman
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Sao Paulo
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 Nyman
 
Mobile Software Engineering Crash Course - C04 Android Cont.
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 Shaker
 
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
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 Nyman
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
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 Nyman
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
Michael Peacock
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
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 Nyman
 

Similaire à CQRS and Event Sourcing in a Symfony application (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
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Android workshop
Android workshopAndroid workshop
Android workshop
 
Leaving Flatland: getting started with WebGL
Leaving Flatland: getting started with WebGLLeaving Flatland: getting started with WebGL
Leaving Flatland: getting started with WebGL
 
Prototype UI
Prototype UIPrototype UI
Prototype UI
 
JavaScript APIs - The Web is the Platform - MDN Hack Day - Buenos Aires
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
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
Sbaw091117
Sbaw091117Sbaw091117
Sbaw091117
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Sao Paulo
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
 
Mobile Software Engineering Crash Course - C04 Android Cont.
Mobile Software Engineering Crash Course - C04 Android Cont.Mobile Software Engineering Crash Course - C04 Android Cont.
Mobile Software Engineering Crash Course - C04 Android Cont.
 
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...
GDG GeorgeTown Devfest 2014 Presentation: Android Wear: A Developer's Perspec...
 
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
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
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
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
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
ZF2 for the ZF1 Developer
ZF2 for the ZF1 DeveloperZF2 for the ZF1 Developer
ZF2 for the ZF1 Developer
 
A Series of Fortunate Events - Symfony Camp Sweden 2014
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
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
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
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
 

Plus de Samuel ROZE

Plus de Samuel ROZE (13)

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)
Event streaming: what will go wrong? (Symfony World 2020)
 
Living documentation
Living documentationLiving documentation
Living documentation
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)
 
Micro services may not be the best idea
Micro services may not be the best ideaMicro services may not be the best idea
Micro services may not be the best idea
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Take care of our micro services
Take care of our micro servicesTake care of our micro services
Take care of our micro services
 
(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance
 
Using continuouspipe to speed up our workflows
Using continuouspipe to speed up our workflowsUsing continuouspipe to speed up our workflows
Using continuouspipe to speed up our workflows
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
Behat c'est plus que ça | Behat is more than that
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
 
Docker orchestration with Kubernetes
Docker orchestration with KubernetesDocker orchestration with Kubernetes
Docker orchestration with Kubernetes
 
Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer
 

Dernier

VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
dharasingh5698
 
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Christo Ananth
 
VIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 Booking
dharasingh5698
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
notes on Evolution Of Analytic Scalability.ppt
notes on Evolution Of Analytic Scalability.pptnotes on Evolution Of Analytic Scalability.ppt
notes on Evolution Of Analytic Scalability.ppt
MsecMca
 

Dernier (20)

Call for Papers - International Journal of Intelligent Systems and Applicatio...
Call for Papers - International Journal of Intelligent Systems and Applicatio...Call for Papers - International Journal of Intelligent Systems and Applicatio...
Call for Papers - International Journal of Intelligent Systems and Applicatio...
 
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
 
Double rodded leveling 1 pdf activity 01
Double rodded leveling 1 pdf activity 01Double rodded leveling 1 pdf activity 01
Double rodded leveling 1 pdf activity 01
 
Intze Overhead Water Tank Design by Working Stress - IS Method.pdf
Intze Overhead Water Tank  Design by Working Stress - IS Method.pdfIntze Overhead Water Tank  Design by Working Stress - IS Method.pdf
Intze Overhead Water Tank Design by Working Stress - IS Method.pdf
 
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
 
Bhosari ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready For ...
Bhosari ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready For ...Bhosari ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready For ...
Bhosari ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready For ...
 
Thermal Engineering-R & A / C - unit - V
Thermal Engineering-R & A / C - unit - VThermal Engineering-R & A / C - unit - V
Thermal Engineering-R & A / C - unit - V
 
VIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Palanpur 7001035870 Whatsapp Number, 24/07 Booking
 
Java Programming :Event Handling(Types of Events)
Java Programming :Event Handling(Types of Events)Java Programming :Event Handling(Types of Events)
Java Programming :Event Handling(Types of Events)
 
Thermal Engineering -unit - III & IV.ppt
Thermal Engineering -unit - III & IV.pptThermal Engineering -unit - III & IV.ppt
Thermal Engineering -unit - III & IV.ppt
 
Vivazz, Mieres Social Housing Design Spain
Vivazz, Mieres Social Housing Design SpainVivazz, Mieres Social Housing Design Spain
Vivazz, Mieres Social Housing Design Spain
 
Online banking management system project.pdf
Online banking management system project.pdfOnline banking management system project.pdf
Online banking management system project.pdf
 
(INDIRA) Call Girl Meerut Call Now 8617697112 Meerut Escorts 24x7
(INDIRA) Call Girl Meerut Call Now 8617697112 Meerut Escorts 24x7(INDIRA) Call Girl Meerut Call Now 8617697112 Meerut Escorts 24x7
(INDIRA) Call Girl Meerut Call Now 8617697112 Meerut Escorts 24x7
 
KubeKraft presentation @CloudNativeHooghly
KubeKraft presentation @CloudNativeHooghlyKubeKraft presentation @CloudNativeHooghly
KubeKraft presentation @CloudNativeHooghly
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
 
data_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdfdata_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdf
 
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
 
Unleashing the Power of the SORA AI lastest leap
Unleashing the Power of the SORA AI lastest leapUnleashing the Power of the SORA AI lastest leap
Unleashing the Power of the SORA AI lastest leap
 
chapter 5.pptx: drainage and irrigation engineering
chapter 5.pptx: drainage and irrigation engineeringchapter 5.pptx: drainage and irrigation engineering
chapter 5.pptx: drainage and irrigation engineering
 
notes on Evolution Of Analytic Scalability.ppt
notes on Evolution Of Analytic Scalability.pptnotes on Evolution Of Analytic Scalability.ppt
notes on Evolution Of Analytic Scalability.ppt
 

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
  • 3.
  • 4.
  • 5. The heart of software is its ability to solve domain- related problems for its user 1 Eric Evans
  • 6.
  • 8.
  • 9. CQRS & Event Sourcing
  • 10.
  • 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(); } } }
  • 17.
  • 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