SlideShare a Scribd company logo
1 of 45
Download to read offline
When CQRS meets Event Sourcing
A warehouse management system done in PHP
When CQRS meets Event Sourcing / Ulabox
ULABOX
About me
● @manelselles
● Backend at Ulabox
● Symfony Expert
Certified by Sensiolabs
● DDD-TDD fan
When CQRS meets Event Sourcing / Warehouse
Warehouse management system
● PHP and framework agnostic
○ (almost) all of us love Symfony
● Independent of other systems
○ Ulabox ecosystem is complex -> Microservices
● Extensible and maintainable
○ Testing
● The system must log every action
○ Event driven architecture
When CQRS meets Event Sourcing / Warehouse
Please test!
Good practices
When CQRS meets Event Sourcing / Good practices
Outside-in TDD
● Behat features
● Describe behaviour with PhpSpec
● Testing integration with database of repository methods with Phpunit
When CQRS meets Event Sourcing / Good practices
Continuous integration
When CQRS meets Event Sourcing / Good practices
Other good practices
● SOLID
● Coding Style
● Pair programming
● Refactor
DDD-Hexagonal architecture
When CQRS meets Event Sourcing / DDD-Hexagonal
DDD Basics
● Strategic
○ Ubiquitous language
○ Bounded contexts
● Tactical
○ Value objects
○ Aggregates and entities
○ Repositories
○ Domain events
○ Domain and application services
When CQRS meets Event Sourcing / DDD-Hexagonal
Aggregate
When CQRS meets Event Sourcing / DDD-Hexagonal
Hexagonal architecture
namespace UlaboxChangoInfrastructureUiHttpController;
class ReceptionController
{
public function addContainerAction(JsonApiRequest $request, $receptionId)
{
$containerPayload = $this->jsonApiTransformer->fromPayload($request->jsonData(), 'container');
$this->receptionService->addContainer(ReceptionId::fromString($receptionId), $containerPayload);
return JsonApiResponse::createJsonApiData(200, null, []);
}
}
namespace UlaboxChangoInfrastructureUiAmqpConsumer;
class ContainerAddedToReceptionConsumer extends Consumer
{
public function execute(AMQPMessage $rabbitMessage)
{
$message = $this->messageBody($rabbitMessage);
$containerPayload = $this->amqpTransformer->fromPayload($message, 'container');
$this->receptionService->addContainer(ReceptionId::fromString($message['reception_id']), $containerPayload);
return ConsumerInterface::MSG_ACK;
}
}
namespace UlaboxChangoApplicationService;
class ReceptionService
{
public function addContainer(ReceptionId $receptionId, ContainerPayload $payload)
{
$reception = $this->receptionRepository->get($receptionId);
$reception->addContainer($payload->temperature(), $payload->lines());
$this->receptionRepository->save($reception);
$this->eventBus->dispatch($reception->recordedEvents());
}
}
When CQRS meets Event Sourcing / DDD-Hexagonal
Why application service?
● Same entry point
● Coordinate tasks on model
● Early checks
● User authentication
namespace UlaboxChangoDomainModelReception;
class Reception extends Aggregate
{
public function addContainer(Temperature $temperature, array $containerLines)
{
Assertion::allIsInstanceOf($containerLines, ContainerLinePayload::class);
$containerId = ContainerId::create($this->id(), $temperature, count($this->containers));
$this->containers->set((string) $containerId, new Container($containerId, $temperature));
$this->recordThat(new ContainerWasAdded($this->id, $containerId, $temperature));
foreach ($containerLines as $line) {
$this->addLine($containerId, $line->label(), $line->quantity(), $line->type());
}
}
public function addLine(ContainerId $containerId, Label $label, LineQuantity $quantity, ItemType $type)
{
if (!$container = $this->containers->get((string) $containerId)) {
throw new EntityNotFoundException("Container not found");
}
$container->addLine(ContainerLine::create($label, $quantity, $type));
$this->recordThat(new ContainerLineWasAdded($this->id, $containerId, $label, $quantity, $type));
}
}
namespace UlaboxChangoDomainModelReceptionContainer;
class Container
{
public function __construct(ContainerId $id, Temperature $temperature)
{
$this->id = $id;
$this->temperature = $temperature;
$this->lines = new ArrayCollection();
$this->status = ContainerStatus::PENDING();
}
public function addLine(ContainerLine $line)
{
if ($this->containsLine($line->label())) {
throw new AlreadyRegisteredException("Line already exists");
}
$this->lines->set((string) $line->label(), $line);
}
}
namespace UlaboxChangoInfrastructurePersistenceDoctrineReception;
class DoctrineReceptionRepository implements ReceptionRepository
{
public function get(ReceptionId $id)
{
return $this->find($id);
}
public function save(Reception $reception)
{
$this->_em->persist($reception);
}
}
Let’s apply Command and Query
Responsibility Segregation
When CQRS meets Event Sourcing / CQRS
CQRS
Separate:
● Command: do something
● Query: ask for something
Different source of data for read and write:
● Write model with DDD tactical patterns
● Read model with listeners to events
When CQRS meets Event Sourcing / CQRS
Command bus
● Finds handler for each action
● Decoupled command creator and handler
● Middlewares
○ Transactional
○ Logging
● Asynchronous actions
● Separation of concerns
When CQRS meets Event Sourcing / CQRS
Event bus
● Posted events are delivered to matching event handlers
● Decouples event producers and reactors
● Middlewares
○ Rabbit
○ Add correlation id
● Asynchronous actions
● Separation of concerns
When CQRS meets Event Sourcing / CQRS
namespace UlaboxChangoApplicationService;
class ReceptionService
{
public function addContainer(ReceptionId $receptionId, ContainerPayload $payload)
{
$command = new AddContainer($receptionId, $payload->temperature(), $payload->containerLines());
$this->commandBus->handle($command);
}
}
namespace UlaboxChangoDomainCommandReception;
class ReceptionCommandHandler extends CommandHandler
{
public function handleAddContainer(AddContainer $command)
{
$reception = $this->receptionRepository->get($command->aggregateId());
$reception->addContainer($command->temperature(), $command->lines());
$this->receptionRepository->save($reception);
$this->eventBus->dispatch($reception->recordedEvents());
}
}
namespace UlaboxChangoDomainReadModelReception;
class ReceptionProjector extends ReadModelProcessor
{
public function applyContainerWasAdded(ContainerWasAdded $event)
{
$reception = $this->receptionInfoView->receptionOfId($event->aggregateId());
$container = new ContainerProjection($event->containerId(), $event->temperature());
$this->receptionInfoView->save($reception->addContainer($container));
}
public function applyContainerLineWasAdded(ContainerLineWasAdded $event)
{
$reception = $this->receptionInfoView->receptionOfId($event->aggregateId());
$line = ContainerLineProjection($event->label(), $event->quantity(), $event->itemType());
$this->receptionInfoView->save($reception->addContainerLine($event->containerId(), $line));
}
}
namespace UlaboxChangoDomainReadModelReception;
interface ReceptionView
{
public function save(ReceptionProjection $reception);
public function receptionOfId(ReceptionId $receptionId);
public function find(Query $query);
}
namespace UlaboxChangoApplicationService;
class ReceptionQueryService
{
public function byId(ReceptionId $receptionId)
{
return $this->receptionView->receptionOfId($receptionId);
}
public function byContainer(ContainerId $containerId)
{
return $this->receptionView->find(new byContainer($containerId));
}
public function search($filters, Paging $paging = null, Sorting $sorting = null)
{
return $this->receptionView->find(new ByFilters($filters, $sorting, $paging));
}
}
Let’s get crazy: event sourcing
When CQRS meets Event Sourcing / Event sourcing
Event sourcing
● Entities are reconstructed with events
● No state
● No database to update manually
● No joins
When CQRS meets Event Sourcing / Event sourcing
Why event sourcing?
● Get state of an aggregate at any moment in time
● Append-only model storing events is easier to scale
● Forces to log because everything is an event
● No coupling between current state in the domain and in storage
● Simulate business suppositions
○ Change picking algorithm
When CQRS meets Event Sourcing / Event sourcing
Event Store
● PostgreSQL
● jsonb
● DBAL
namespace UlaboxChangoInfrastructurePersistenceEventStore;
class PDOEventStore implements EventStore
{
public function append(AggregateId $id, EventStream $eventStream)
{
$stmt = $this->connection->prepare("INSERT INTO event_store (data) VALUES (:message)");
$this->connection->beginTransaction();
foreach ($eventStream as $event) {
if (!$stmt->execute(['message' => $this->eventSerializer->serialize($event)])) {
$this->connection->rollBack();
}
}
$this->connection->commit();
}
public function load(AggregateId $id)
{
$stmt = $this->connection->prepare("SELECT data FROM event_store WHERE data->'payload'->>'aggregate_id' = :id");
$stmt->execute(['id' => (string) $id]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$events = [];
foreach ($rows as $row) {
$events[] = $this->eventSerializer->deserialize($row['data']);
}
return new EventStream($events);
}
}
When CQRS meets Event Sourcing / Event sourcing
namespace UlaboxChangoInfrastructurePersistenceModelReception;
class EventSourcingReceptionRepository implements ReceptionRepository
{
public function save(Reception $reception)
{
$events = $reception->recordedEvents();
$this->eventStore->append($reception->id(), $events);
foreach ($events as $event) {
$this->eventBus->dispatch($event);
}
}
public function load(ReceptionId $id)
{
$eventStream = $this->eventStore->load($id);
return Reception::reconstituteFromEvents(
new AggregateHistory($id, $eventStream)
);
}
}
namespace UlaboxChangoDomainModelReception;
class Reception extends EventSourcedAggregate
{
public static function create(
ReceptionId $id, DateTime $receptionDate, SupplierId $supplierId
) {
$instance = new self($id);
$instance->recordThat(
new ReceptionWasScheduled($id, $receptionDate, $supplierId)
);
return $instance;
}
protected function applyReceptionWasScheduled(ReceptionWasScheduled $event)
{
$this->receptionDate = $event->receptionDate();
$this->supplierId = $event->supplierId();
$this->status = ReceptionStatus::PENDING();
$this->containers = new ArrayCollection();
}
}
namespace UlaboxChangoDomainModel;
abstract class EventSourcedAggregate implements AggregateRoot, EventRecorder
{
protected function __construct()
{
$this->version = 0;
$this->eventStream = new EventStream();
}
protected function recordThat(Event $event)
{
$this->apply($event);
$this->eventStream->append($event);
}
protected function apply(Event $event)
{
$classParts = explode('', get_class($event));
$methodName = 'apply'.end($classParts);
if (method_exists($this, $methodName)) {
$this->$methodName($event);
}
$this->version++;
}
}
namespace UlaboxChangoDomainModelReception;
class Reception extends EventSourcedAggregate
{
public static function reconstituteFromEvents(AggregateHistory $history)
{
$instance = new self($history->aggregateId());
foreach ($history->events() as $event) {
$instance->apply($event);
}
return $instance;
}
public function addContainer(Temperature $temperature, array $containerLines)
{
$containerId = ContainerId::create(
$this->id(), $temperature, count($this->containers)
);
$this->recordThat(
new ContainerWasAdded($this->id, $containerId, $temperature)
);
foreach ($containerLines as $line) {
$this->addLine(
$containerId, $line->label(), $line->quantity(), $line->type()
);
}
}
protected function applyContainerWasAdded(ContainerWasAdded $event)
{
$container = new Container($event->containerId(), $event->temperature());
$this->containers->set((string) $event->containerId(), $container);
}
}
Conclusions
When CQRS meets Event Sourcing / Conclusions
Benefits
● Decoupling
● Performance in Read Model
● Scalability
● No joins
● Async with internal events and consumers
● Communicate other bounded contexts with events
When CQRS meets Event Sourcing / Conclusions
Problems found
● With DDD
○ Decide aggregates => talk a LOT with the domain experts
○ Boilerplate => generate as much boilerplate as possible
● With CQRS
○ Forgetting listeners in read model
○ Repeated code structure
● With event sourcing
○ Adapting your mindset
○ Forgetting applying the event to the entity
○ Retro compatibility with old events
● Concurrency/eventual consistency
Work with us!
When CQRS meets Event Sourcing / Work with us
Work with us
Thanks to...
When CQRS meets Event Sourcing / Conclusions
Thank you!
Questions?
@manelselles
manelselles@gmail.com

More Related Content

What's hot

Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
Bill Chang
 

What's hot (20)

Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
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
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mocking
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
 
Things I Believe Now That I'm Old
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Symfony World - Symfony components and design patterns
Symfony World - Symfony components and design patternsSymfony World - Symfony components and design patterns
Symfony World - Symfony components and design patterns
 
CQRS & Event Sourcing in the wild (ScotlandPHP 2016)
CQRS & Event Sourcing in the wild (ScotlandPHP 2016)CQRS & Event Sourcing in the wild (ScotlandPHP 2016)
CQRS & Event Sourcing in the wild (ScotlandPHP 2016)
 

Viewers also liked

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
 
Greg young’s simple cqrs sample
Greg young’s simple cqrs sampleGreg young’s simple cqrs sample
Greg young’s simple cqrs sample
Leonardo Rosales
 

Viewers also liked (20)

Refactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con SymfonyRefactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con Symfony
 
Introduction to hexagonal architecture
Introduction to hexagonal architectureIntroduction to hexagonal architecture
Introduction to hexagonal architecture
 
Introduction to testing
Introduction to testingIntroduction to testing
Introduction to testing
 
Symfony 2 : Performances et Optimisations
Symfony 2 : Performances et OptimisationsSymfony 2 : Performances et Optimisations
Symfony 2 : Performances et Optimisations
 
CQRS & event sourcing in the wild
CQRS & event sourcing in the wildCQRS & event sourcing in the wild
CQRS & event sourcing in the wild
 
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Integrando React.js en aplicaciones Symfony (deSymfony 2016)Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
 
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
 
CQRS + Event Sourcing
CQRS + Event SourcingCQRS + Event Sourcing
CQRS + Event Sourcing
 
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
 
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...
 
Learning from the Ulabox stack
Learning from the Ulabox stackLearning from the Ulabox stack
Learning from the Ulabox stack
 
Microservices
MicroservicesMicroservices
Microservices
 
Cqrs from the trenches
Cqrs from the trenchesCqrs from the trenches
Cqrs from the trenches
 
Greg young’s simple cqrs sample
Greg young’s simple cqrs sampleGreg young’s simple cqrs sample
Greg young’s simple cqrs sample
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
 
JPHP - О проекте на простом языке
JPHP - О проекте на простом языкеJPHP - О проекте на простом языке
JPHP - О проекте на простом языке
 
Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)
Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)
Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)
 
Matters of State
Matters of StateMatters of State
Matters of State
 
Webinar - What's new in Axon 3
Webinar - What's new in Axon 3 Webinar - What's new in Axon 3
Webinar - What's new in Axon 3
 
DDD 준비 서문래
DDD 준비 서문래DDD 준비 서문래
DDD 준비 서문래
 

Similar to When cqrs meets event sourcing

Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
unodelostrece
 

Similar to When cqrs meets event sourcing (20)

Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
Modularity and Layered Data Model
Modularity and Layered Data ModelModularity and Layered Data Model
Modularity and Layered Data Model
 
Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium Apps
 
#pugMi - DDD - Value objects
#pugMi - DDD - Value objects#pugMi - DDD - Value objects
#pugMi - DDD - Value objects
 
Drupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency InjectionDrupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency Injection
 
Practical Event Sourcing
Practical Event SourcingPractical Event Sourcing
Practical Event Sourcing
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
 
My Development Story
My Development StoryMy Development Story
My Development Story
 
The command dispatcher pattern
The command dispatcher patternThe command dispatcher pattern
The command dispatcher pattern
 
Lazy evaluation drupal camp moscow 2014
Lazy evaluation drupal camp moscow 2014Lazy evaluation drupal camp moscow 2014
Lazy evaluation drupal camp moscow 2014
 
Design Patterns
Design PatternsDesign Patterns
Design Patterns
 
Ioc container | Hannes Van De Vreken | CODEiD
Ioc container | Hannes Van De Vreken | CODEiDIoc container | Hannes Van De Vreken | CODEiD
Ioc container | Hannes Van De Vreken | CODEiD
 
LJC Conference 2014 Cassandra for Java Developers
LJC Conference 2014 Cassandra for Java DevelopersLJC Conference 2014 Cassandra for Java Developers
LJC Conference 2014 Cassandra for Java Developers
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
 
Custom entities in d8
Custom entities in d8Custom entities in d8
Custom entities in d8
 
Event Sourcing with php
Event Sourcing with phpEvent Sourcing with php
Event Sourcing with php
 
Intro to advanced caching in WordPress
Intro to advanced caching in WordPressIntro to advanced caching in WordPress
Intro to advanced caching in WordPress
 

Recently uploaded

+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
shinachiaurasa2
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
VishalKumarJha10
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 

Recently uploaded (20)

+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
 
%in Durban+277-882-255-28 abortion pills for sale in Durban
%in Durban+277-882-255-28 abortion pills for sale in Durban%in Durban+277-882-255-28 abortion pills for sale in Durban
%in Durban+277-882-255-28 abortion pills for sale in Durban
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
The Top App Development Trends Shaping the Industry in 2024-25 .pdf
The Top App Development Trends Shaping the Industry in 2024-25 .pdfThe Top App Development Trends Shaping the Industry in 2024-25 .pdf
The Top App Development Trends Shaping the Industry in 2024-25 .pdf
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
SHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions PresentationSHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions Presentation
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdf
 
10 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 202410 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 2024
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 

When cqrs meets event sourcing

  • 1. When CQRS meets Event Sourcing A warehouse management system done in PHP
  • 2. When CQRS meets Event Sourcing / Ulabox ULABOX
  • 3. About me ● @manelselles ● Backend at Ulabox ● Symfony Expert Certified by Sensiolabs ● DDD-TDD fan
  • 4. When CQRS meets Event Sourcing / Warehouse Warehouse management system ● PHP and framework agnostic ○ (almost) all of us love Symfony ● Independent of other systems ○ Ulabox ecosystem is complex -> Microservices ● Extensible and maintainable ○ Testing ● The system must log every action ○ Event driven architecture
  • 5. When CQRS meets Event Sourcing / Warehouse
  • 7. When CQRS meets Event Sourcing / Good practices Outside-in TDD ● Behat features ● Describe behaviour with PhpSpec ● Testing integration with database of repository methods with Phpunit
  • 8. When CQRS meets Event Sourcing / Good practices Continuous integration
  • 9. When CQRS meets Event Sourcing / Good practices Other good practices ● SOLID ● Coding Style ● Pair programming ● Refactor
  • 11. When CQRS meets Event Sourcing / DDD-Hexagonal DDD Basics ● Strategic ○ Ubiquitous language ○ Bounded contexts ● Tactical ○ Value objects ○ Aggregates and entities ○ Repositories ○ Domain events ○ Domain and application services
  • 12. When CQRS meets Event Sourcing / DDD-Hexagonal Aggregate
  • 13. When CQRS meets Event Sourcing / DDD-Hexagonal Hexagonal architecture
  • 14. namespace UlaboxChangoInfrastructureUiHttpController; class ReceptionController { public function addContainerAction(JsonApiRequest $request, $receptionId) { $containerPayload = $this->jsonApiTransformer->fromPayload($request->jsonData(), 'container'); $this->receptionService->addContainer(ReceptionId::fromString($receptionId), $containerPayload); return JsonApiResponse::createJsonApiData(200, null, []); } } namespace UlaboxChangoInfrastructureUiAmqpConsumer; class ContainerAddedToReceptionConsumer extends Consumer { public function execute(AMQPMessage $rabbitMessage) { $message = $this->messageBody($rabbitMessage); $containerPayload = $this->amqpTransformer->fromPayload($message, 'container'); $this->receptionService->addContainer(ReceptionId::fromString($message['reception_id']), $containerPayload); return ConsumerInterface::MSG_ACK; } }
  • 15. namespace UlaboxChangoApplicationService; class ReceptionService { public function addContainer(ReceptionId $receptionId, ContainerPayload $payload) { $reception = $this->receptionRepository->get($receptionId); $reception->addContainer($payload->temperature(), $payload->lines()); $this->receptionRepository->save($reception); $this->eventBus->dispatch($reception->recordedEvents()); } }
  • 16. When CQRS meets Event Sourcing / DDD-Hexagonal Why application service? ● Same entry point ● Coordinate tasks on model ● Early checks ● User authentication
  • 17. namespace UlaboxChangoDomainModelReception; class Reception extends Aggregate { public function addContainer(Temperature $temperature, array $containerLines) { Assertion::allIsInstanceOf($containerLines, ContainerLinePayload::class); $containerId = ContainerId::create($this->id(), $temperature, count($this->containers)); $this->containers->set((string) $containerId, new Container($containerId, $temperature)); $this->recordThat(new ContainerWasAdded($this->id, $containerId, $temperature)); foreach ($containerLines as $line) { $this->addLine($containerId, $line->label(), $line->quantity(), $line->type()); } } public function addLine(ContainerId $containerId, Label $label, LineQuantity $quantity, ItemType $type) { if (!$container = $this->containers->get((string) $containerId)) { throw new EntityNotFoundException("Container not found"); } $container->addLine(ContainerLine::create($label, $quantity, $type)); $this->recordThat(new ContainerLineWasAdded($this->id, $containerId, $label, $quantity, $type)); } }
  • 18. namespace UlaboxChangoDomainModelReceptionContainer; class Container { public function __construct(ContainerId $id, Temperature $temperature) { $this->id = $id; $this->temperature = $temperature; $this->lines = new ArrayCollection(); $this->status = ContainerStatus::PENDING(); } public function addLine(ContainerLine $line) { if ($this->containsLine($line->label())) { throw new AlreadyRegisteredException("Line already exists"); } $this->lines->set((string) $line->label(), $line); } }
  • 19. namespace UlaboxChangoInfrastructurePersistenceDoctrineReception; class DoctrineReceptionRepository implements ReceptionRepository { public function get(ReceptionId $id) { return $this->find($id); } public function save(Reception $reception) { $this->_em->persist($reception); } }
  • 20. Let’s apply Command and Query Responsibility Segregation
  • 21. When CQRS meets Event Sourcing / CQRS CQRS Separate: ● Command: do something ● Query: ask for something Different source of data for read and write: ● Write model with DDD tactical patterns ● Read model with listeners to events
  • 22. When CQRS meets Event Sourcing / CQRS Command bus ● Finds handler for each action ● Decoupled command creator and handler ● Middlewares ○ Transactional ○ Logging ● Asynchronous actions ● Separation of concerns
  • 23. When CQRS meets Event Sourcing / CQRS Event bus ● Posted events are delivered to matching event handlers ● Decouples event producers and reactors ● Middlewares ○ Rabbit ○ Add correlation id ● Asynchronous actions ● Separation of concerns
  • 24. When CQRS meets Event Sourcing / CQRS
  • 25. namespace UlaboxChangoApplicationService; class ReceptionService { public function addContainer(ReceptionId $receptionId, ContainerPayload $payload) { $command = new AddContainer($receptionId, $payload->temperature(), $payload->containerLines()); $this->commandBus->handle($command); } } namespace UlaboxChangoDomainCommandReception; class ReceptionCommandHandler extends CommandHandler { public function handleAddContainer(AddContainer $command) { $reception = $this->receptionRepository->get($command->aggregateId()); $reception->addContainer($command->temperature(), $command->lines()); $this->receptionRepository->save($reception); $this->eventBus->dispatch($reception->recordedEvents()); } }
  • 26. namespace UlaboxChangoDomainReadModelReception; class ReceptionProjector extends ReadModelProcessor { public function applyContainerWasAdded(ContainerWasAdded $event) { $reception = $this->receptionInfoView->receptionOfId($event->aggregateId()); $container = new ContainerProjection($event->containerId(), $event->temperature()); $this->receptionInfoView->save($reception->addContainer($container)); } public function applyContainerLineWasAdded(ContainerLineWasAdded $event) { $reception = $this->receptionInfoView->receptionOfId($event->aggregateId()); $line = ContainerLineProjection($event->label(), $event->quantity(), $event->itemType()); $this->receptionInfoView->save($reception->addContainerLine($event->containerId(), $line)); } } namespace UlaboxChangoDomainReadModelReception; interface ReceptionView { public function save(ReceptionProjection $reception); public function receptionOfId(ReceptionId $receptionId); public function find(Query $query); }
  • 27. namespace UlaboxChangoApplicationService; class ReceptionQueryService { public function byId(ReceptionId $receptionId) { return $this->receptionView->receptionOfId($receptionId); } public function byContainer(ContainerId $containerId) { return $this->receptionView->find(new byContainer($containerId)); } public function search($filters, Paging $paging = null, Sorting $sorting = null) { return $this->receptionView->find(new ByFilters($filters, $sorting, $paging)); } }
  • 28. Let’s get crazy: event sourcing
  • 29. When CQRS meets Event Sourcing / Event sourcing Event sourcing ● Entities are reconstructed with events ● No state ● No database to update manually ● No joins
  • 30. When CQRS meets Event Sourcing / Event sourcing Why event sourcing? ● Get state of an aggregate at any moment in time ● Append-only model storing events is easier to scale ● Forces to log because everything is an event ● No coupling between current state in the domain and in storage ● Simulate business suppositions ○ Change picking algorithm
  • 31. When CQRS meets Event Sourcing / Event sourcing Event Store ● PostgreSQL ● jsonb ● DBAL
  • 32. namespace UlaboxChangoInfrastructurePersistenceEventStore; class PDOEventStore implements EventStore { public function append(AggregateId $id, EventStream $eventStream) { $stmt = $this->connection->prepare("INSERT INTO event_store (data) VALUES (:message)"); $this->connection->beginTransaction(); foreach ($eventStream as $event) { if (!$stmt->execute(['message' => $this->eventSerializer->serialize($event)])) { $this->connection->rollBack(); } } $this->connection->commit(); } public function load(AggregateId $id) { $stmt = $this->connection->prepare("SELECT data FROM event_store WHERE data->'payload'->>'aggregate_id' = :id"); $stmt->execute(['id' => (string) $id]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $events = []; foreach ($rows as $row) { $events[] = $this->eventSerializer->deserialize($row['data']); } return new EventStream($events); } }
  • 33. When CQRS meets Event Sourcing / Event sourcing
  • 34. namespace UlaboxChangoInfrastructurePersistenceModelReception; class EventSourcingReceptionRepository implements ReceptionRepository { public function save(Reception $reception) { $events = $reception->recordedEvents(); $this->eventStore->append($reception->id(), $events); foreach ($events as $event) { $this->eventBus->dispatch($event); } } public function load(ReceptionId $id) { $eventStream = $this->eventStore->load($id); return Reception::reconstituteFromEvents( new AggregateHistory($id, $eventStream) ); } }
  • 35. namespace UlaboxChangoDomainModelReception; class Reception extends EventSourcedAggregate { public static function create( ReceptionId $id, DateTime $receptionDate, SupplierId $supplierId ) { $instance = new self($id); $instance->recordThat( new ReceptionWasScheduled($id, $receptionDate, $supplierId) ); return $instance; } protected function applyReceptionWasScheduled(ReceptionWasScheduled $event) { $this->receptionDate = $event->receptionDate(); $this->supplierId = $event->supplierId(); $this->status = ReceptionStatus::PENDING(); $this->containers = new ArrayCollection(); } }
  • 36. namespace UlaboxChangoDomainModel; abstract class EventSourcedAggregate implements AggregateRoot, EventRecorder { protected function __construct() { $this->version = 0; $this->eventStream = new EventStream(); } protected function recordThat(Event $event) { $this->apply($event); $this->eventStream->append($event); } protected function apply(Event $event) { $classParts = explode('', get_class($event)); $methodName = 'apply'.end($classParts); if (method_exists($this, $methodName)) { $this->$methodName($event); } $this->version++; } }
  • 37. namespace UlaboxChangoDomainModelReception; class Reception extends EventSourcedAggregate { public static function reconstituteFromEvents(AggregateHistory $history) { $instance = new self($history->aggregateId()); foreach ($history->events() as $event) { $instance->apply($event); } return $instance; } public function addContainer(Temperature $temperature, array $containerLines) { $containerId = ContainerId::create( $this->id(), $temperature, count($this->containers) ); $this->recordThat( new ContainerWasAdded($this->id, $containerId, $temperature) ); foreach ($containerLines as $line) { $this->addLine( $containerId, $line->label(), $line->quantity(), $line->type() ); } } protected function applyContainerWasAdded(ContainerWasAdded $event) { $container = new Container($event->containerId(), $event->temperature()); $this->containers->set((string) $event->containerId(), $container); } }
  • 39. When CQRS meets Event Sourcing / Conclusions Benefits ● Decoupling ● Performance in Read Model ● Scalability ● No joins ● Async with internal events and consumers ● Communicate other bounded contexts with events
  • 40. When CQRS meets Event Sourcing / Conclusions Problems found ● With DDD ○ Decide aggregates => talk a LOT with the domain experts ○ Boilerplate => generate as much boilerplate as possible ● With CQRS ○ Forgetting listeners in read model ○ Repeated code structure ● With event sourcing ○ Adapting your mindset ○ Forgetting applying the event to the entity ○ Retro compatibility with old events ● Concurrency/eventual consistency
  • 42. When CQRS meets Event Sourcing / Work with us Work with us
  • 44. When CQRS meets Event Sourcing / Conclusions