SlideShare una empresa de Scribd logo
1 de 59
Descargar para leer sin conexión
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

Más contenido relacionado

La actualidad más candente

Kubernetes in Docker
Kubernetes in DockerKubernetes in Docker
Kubernetes in DockerDocker, Inc.
 
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)GOG.com dev team
 
Développement d'un site web jee de e commerce basé sur spring (m.youssfi)
Développement d'un site web jee de e commerce basé sur spring (m.youssfi)Développement d'un site web jee de e commerce basé sur spring (m.youssfi)
Développement d'un site web jee de e commerce basé sur spring (m.youssfi)ENSET, Université Hassan II Casablanca
 
Symfony in microservice architecture
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architectureDaniele D'Angeli
 
Android-Tp5 : web services
Android-Tp5 : web servicesAndroid-Tp5 : web services
Android-Tp5 : web servicesLilia Sfaxi
 
이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정Arawn Park
 
Building .NET Microservices
Building .NET MicroservicesBuilding .NET Microservices
Building .NET MicroservicesVMware Tanzu
 
Design patterns avec Symfony
Design patterns avec SymfonyDesign patterns avec Symfony
Design patterns avec SymfonyMohammed Rhamnia
 
kubernetes : From beginner to Advanced
kubernetes : From beginner to Advancedkubernetes : From beginner to Advanced
kubernetes : From beginner to AdvancedInho Kang
 
왕초보를 위한 도커 사용법
왕초보를 위한 도커 사용법왕초보를 위한 도커 사용법
왕초보를 위한 도커 사용법GeunCheolYeom
 
Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...
Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...
Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...Simplilearn
 
Dependency injection - the right way
Dependency injection - the right wayDependency injection - the right way
Dependency injection - the right wayThibaud Desodt
 
New features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptx
New features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptxNew features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptx
New features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptxMuralidharan Deenathayalan
 
Easy tests with Selenide and Easyb
Easy tests with Selenide and EasybEasy tests with Selenide and Easyb
Easy tests with Selenide and EasybIakiv Kramarenko
 

La actualidad más candente (20)

Kubernetes in Docker
Kubernetes in DockerKubernetes in Docker
Kubernetes in Docker
 
Spring boot jpa
Spring boot jpaSpring boot jpa
Spring boot jpa
 
Angular Schematics
Angular SchematicsAngular Schematics
Angular Schematics
 
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)
 
Développement d'un site web jee de e commerce basé sur spring (m.youssfi)
Développement d'un site web jee de e commerce basé sur spring (m.youssfi)Développement d'un site web jee de e commerce basé sur spring (m.youssfi)
Développement d'un site web jee de e commerce basé sur spring (m.youssfi)
 
Rapport Sockets en Java
Rapport Sockets en JavaRapport Sockets en Java
Rapport Sockets en Java
 
Symfony in microservice architecture
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architecture
 
Android-Tp5 : web services
Android-Tp5 : web servicesAndroid-Tp5 : web services
Android-Tp5 : web services
 
이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정
 
Node.js Express Framework
Node.js Express FrameworkNode.js Express Framework
Node.js Express Framework
 
Building .NET Microservices
Building .NET MicroservicesBuilding .NET Microservices
Building .NET Microservices
 
Design patterns avec Symfony
Design patterns avec SymfonyDesign patterns avec Symfony
Design patterns avec Symfony
 
kubernetes : From beginner to Advanced
kubernetes : From beginner to Advancedkubernetes : From beginner to Advanced
kubernetes : From beginner to Advanced
 
왕초보를 위한 도커 사용법
왕초보를 위한 도커 사용법왕초보를 위한 도커 사용법
왕초보를 위한 도커 사용법
 
Domain Driven Design 101
Domain Driven Design 101Domain Driven Design 101
Domain Driven Design 101
 
Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...
Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...
Docker Compose | Docker Compose Tutorial | Docker Tutorial For Beginners | De...
 
Dependency injection - the right way
Dependency injection - the right wayDependency injection - the right way
Dependency injection - the right way
 
New features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptx
New features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptxNew features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptx
New features of Minimal APIs in .NET 7 -Muralidharan Deenathayalan.pptx
 
Easy tests with Selenide and Easyb
Easy tests with Selenide and EasybEasy tests with Selenide and Easyb
Easy tests with Selenide and Easyb
 
Express node js
Express node jsExpress node js
Express node js
 

Destacado

When cqrs meets event sourcing
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcingManel Sellés
 
Symfony 2 : Performances et Optimisations
Symfony 2 : Performances et OptimisationsSymfony 2 : Performances et Optimisations
Symfony 2 : Performances et OptimisationsLes-Tilleuls.coop
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingSamuel ROZE
 
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...Alexander Lisachenko
 
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...Chris Richardson
 
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...Ryan Weaver
 
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 CQRSSteve Pember
 
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 illustratedLorenzo Nicora
 
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 SourcingBen Wilcock
 
Refactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con SymfonyRefactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con SymfonyMario Marín
 
PHP 7 et Symfony 3
PHP 7 et Symfony 3PHP 7 et Symfony 3
PHP 7 et Symfony 3Eddy RICHARD
 
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 webJean-Pierre Vincent
 
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 StreamsDavey Shafik
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phingRajat Pandit
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 

Destacado (20)

When cqrs meets event sourcing
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcing
 
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
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 

Similar a CQRS and Event Sourcing in a Symfony application

Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
 
Leaving Flatland: getting started with WebGL
Leaving Flatland: getting started with WebGLLeaving Flatland: getting started with WebGL
Leaving Flatland: getting started with WebGLgerbille
 
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 AiresRobert Nyman
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ EtsyNishan Subedi
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
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 PauloRobert 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
 
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...mharkus
 
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 AiresRobert 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, ChileRobert Nyman
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsMichael Peacock
 
ZF2 for the ZF1 Developer
ZF2 for the ZF1 DeveloperZF2 for the ZF1 Developer
ZF2 for the ZF1 DeveloperGary Hockin
 
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 2014Matthias Noback
 
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, MontevideoRobert Nyman
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScriptAndrew Dupont
 

Similar a 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
 

Más de 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)
Event streaming: what will go wrong? (Symfony World 2020)Samuel ROZE
 
Living documentation
Living documentationLiving documentation
Living documentationSamuel ROZE
 
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 patternsSamuel ROZE
 
Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Samuel ROZE
 
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 ideaSamuel ROZE
 
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 patternsSamuel ROZE
 
Take care of our micro services
Take care of our micro servicesTake care of our micro services
Take care of our micro servicesSamuel ROZE
 
(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et ToleranceSamuel ROZE
 
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 workflowsSamuel ROZE
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form componentSamuel ROZE
 
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 thatSamuel ROZE
 
Docker orchestration with Kubernetes
Docker orchestration with KubernetesDocker orchestration with Kubernetes
Docker orchestration with KubernetesSamuel ROZE
 
Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Samuel ROZE
 

Más 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
 

Último

TechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor Catchers
TechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor CatchersTechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor Catchers
TechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor Catcherssdickerson1
 
Correctly Loading Incremental Data at Scale
Correctly Loading Incremental Data at ScaleCorrectly Loading Incremental Data at Scale
Correctly Loading Incremental Data at ScaleAlluxio, Inc.
 
CCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdf
CCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdfCCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdf
CCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdfAsst.prof M.Gokilavani
 
Call Girls Narol 7397865700 Independent Call Girls
Call Girls Narol 7397865700 Independent Call GirlsCall Girls Narol 7397865700 Independent Call Girls
Call Girls Narol 7397865700 Independent Call Girlsssuser7cb4ff
 
Vishratwadi & Ghorpadi Bridge Tender documents
Vishratwadi & Ghorpadi Bridge Tender documentsVishratwadi & Ghorpadi Bridge Tender documents
Vishratwadi & Ghorpadi Bridge Tender documentsSachinPawar510423
 
computer application and construction management
computer application and construction managementcomputer application and construction management
computer application and construction managementMariconPadriquez1
 
IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024Mark Billinghurst
 
Sachpazis Costas: Geotechnical Engineering: A student's Perspective Introduction
Sachpazis Costas: Geotechnical Engineering: A student's Perspective IntroductionSachpazis Costas: Geotechnical Engineering: A student's Perspective Introduction
Sachpazis Costas: Geotechnical Engineering: A student's Perspective IntroductionDr.Costas Sachpazis
 
Solving The Right Triangles PowerPoint 2.ppt
Solving The Right Triangles PowerPoint 2.pptSolving The Right Triangles PowerPoint 2.ppt
Solving The Right Triangles PowerPoint 2.pptJasonTagapanGulla
 
Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)
Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)
Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)dollysharma2066
 
INFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETE
INFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETEINFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETE
INFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETEroselinkalist12
 
Transport layer issues and challenges - Guide
Transport layer issues and challenges - GuideTransport layer issues and challenges - Guide
Transport layer issues and challenges - GuideGOPINATHS437943
 
Call Girls Delhi {Jodhpur} 9711199012 high profile service
Call Girls Delhi {Jodhpur} 9711199012 high profile serviceCall Girls Delhi {Jodhpur} 9711199012 high profile service
Call Girls Delhi {Jodhpur} 9711199012 high profile servicerehmti665
 
UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)
UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)
UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)Dr SOUNDIRARAJ N
 
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube ExchangerStudy on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube ExchangerAnamika Sarkar
 
Concrete Mix Design - IS 10262-2019 - .pptx
Concrete Mix Design - IS 10262-2019 - .pptxConcrete Mix Design - IS 10262-2019 - .pptx
Concrete Mix Design - IS 10262-2019 - .pptxKartikeyaDwivedi3
 
Architect Hassan Khalil Portfolio for 2024
Architect Hassan Khalil Portfolio for 2024Architect Hassan Khalil Portfolio for 2024
Architect Hassan Khalil Portfolio for 2024hassan khalil
 
Past, Present and Future of Generative AI
Past, Present and Future of Generative AIPast, Present and Future of Generative AI
Past, Present and Future of Generative AIabhishek36461
 
US Department of Education FAFSA Week of Action
US Department of Education FAFSA Week of ActionUS Department of Education FAFSA Week of Action
US Department of Education FAFSA Week of ActionMebane Rash
 

Último (20)

TechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor Catchers
TechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor CatchersTechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor Catchers
TechTAC® CFD Report Summary: A Comparison of Two Types of Tubing Anchor Catchers
 
Correctly Loading Incremental Data at Scale
Correctly Loading Incremental Data at ScaleCorrectly Loading Incremental Data at Scale
Correctly Loading Incremental Data at Scale
 
CCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdf
CCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdfCCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdf
CCS355 Neural Network & Deep Learning Unit II Notes with Question bank .pdf
 
Design and analysis of solar grass cutter.pdf
Design and analysis of solar grass cutter.pdfDesign and analysis of solar grass cutter.pdf
Design and analysis of solar grass cutter.pdf
 
Call Girls Narol 7397865700 Independent Call Girls
Call Girls Narol 7397865700 Independent Call GirlsCall Girls Narol 7397865700 Independent Call Girls
Call Girls Narol 7397865700 Independent Call Girls
 
Vishratwadi & Ghorpadi Bridge Tender documents
Vishratwadi & Ghorpadi Bridge Tender documentsVishratwadi & Ghorpadi Bridge Tender documents
Vishratwadi & Ghorpadi Bridge Tender documents
 
computer application and construction management
computer application and construction managementcomputer application and construction management
computer application and construction management
 
IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024
 
Sachpazis Costas: Geotechnical Engineering: A student's Perspective Introduction
Sachpazis Costas: Geotechnical Engineering: A student's Perspective IntroductionSachpazis Costas: Geotechnical Engineering: A student's Perspective Introduction
Sachpazis Costas: Geotechnical Engineering: A student's Perspective Introduction
 
Solving The Right Triangles PowerPoint 2.ppt
Solving The Right Triangles PowerPoint 2.pptSolving The Right Triangles PowerPoint 2.ppt
Solving The Right Triangles PowerPoint 2.ppt
 
Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)
Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)
Call Us ≽ 8377877756 ≼ Call Girls In Shastri Nagar (Delhi)
 
INFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETE
INFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETEINFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETE
INFLUENCE OF NANOSILICA ON THE PROPERTIES OF CONCRETE
 
Transport layer issues and challenges - Guide
Transport layer issues and challenges - GuideTransport layer issues and challenges - Guide
Transport layer issues and challenges - Guide
 
Call Girls Delhi {Jodhpur} 9711199012 high profile service
Call Girls Delhi {Jodhpur} 9711199012 high profile serviceCall Girls Delhi {Jodhpur} 9711199012 high profile service
Call Girls Delhi {Jodhpur} 9711199012 high profile service
 
UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)
UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)
UNIT III ANALOG ELECTRONICS (BASIC ELECTRONICS)
 
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube ExchangerStudy on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
 
Concrete Mix Design - IS 10262-2019 - .pptx
Concrete Mix Design - IS 10262-2019 - .pptxConcrete Mix Design - IS 10262-2019 - .pptx
Concrete Mix Design - IS 10262-2019 - .pptx
 
Architect Hassan Khalil Portfolio for 2024
Architect Hassan Khalil Portfolio for 2024Architect Hassan Khalil Portfolio for 2024
Architect Hassan Khalil Portfolio for 2024
 
Past, Present and Future of Generative AI
Past, Present and Future of Generative AIPast, Present and Future of Generative AI
Past, Present and Future of Generative AI
 
US Department of Education FAFSA Week of Action
US Department of Education FAFSA Week of ActionUS Department of Education FAFSA Week of Action
US Department of Education FAFSA Week of Action
 

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