CQRS AND EVENT SOURCING BASICS

Alexander Miertsch
Founder and CEO of prooph software GmbH
Founder and core contributor prooph components
contact@prooph.de

prooph components
CQRS and Event Sourcing packages for PHP
Supported Web Frameworks


or any other framework/codebase supporting container-interop & interop-config

prooph team
Community



Join prooph


Agenda
CQRS and Event Sourcing Basics
time to grab a beer
Event Sourcing case study
Networking

Problem Space
Complex
Distributed
Chaotic
When to use CQRS & ES ?

Domain Events
to the rescue

Informational message
Communicate facts that have happened
Relevant to the business
Event Storming
Deeper insights with events

Event Flow
record, publish, react

OrderPlaced
OrderPayed
OrderShipped
Change State, Then Publish
ORM - Publisher

<?php
final class OrderService
{
private $entityManager;
private $orderRepositoy;
private $eventPublisher;
public function placeOrder(string $orderId, OrderItem ...$items): void
{
$order = $this->orderRepostoy->get($orderId);
$this->entityManager->beginTransaction();
try {
$order->place($items);
$this->entityManager->commit();
} catch(\Throwable $error) {
$this->entityManager->rollBack();
throw $error;
}
$event = OrderPlaced::fromOrder($order);
$this->eventPublisher->publish($event);
}
}
Record Event, Then Publish
Event Store - Publisher

<?php
final class PlaceOrderHandler
{
private $eventStore;
private $eventPublisher;
public function handle(PlaceOrder $command): void
{
//Note: Loading/saving is normally done by event sourcing repository
$orderStream = new StreamName('order-'.$command->orderId());
$history = $this->eventStore->load($orderStream);
$order = Order::reconstituteFromHistory($history);
$newEvents = $order->place($command->items());
//Atomic operation
$this->eventStore->appendTo($orderStream, $newEvents);
$this->eventPublisher->publish($newEvents);
}
}
Event Sourcing
State is a left fold of past events

# E1 at t1: UserRegistered
userId: 1
name: John Doe
email: doe@fake.com
# E2 at t2: UsernameChanged
userId: 1
oldName: John Doe
newName: Alex
# E3 at t3: EmailChanged
userId: 1
oldEmail: doe@fake.com
newEmail: contact@prooph.de
_______________________________________________
# current state (t:now) = ((E1 + E2) + E3):
userId: 1
name: Alex
email: contact@prooph.de
version: 3
Time Travel
Debug like a boss

UserRegistered
UsernameChanged
ChangeEmailFailed
{
E1 + E2
Change Email
Temporal Queries
Use time interval as query param

UserRegistered
UsernameChanged
5 min
7 min
Who has changed username within 5 min after registration?
UserRegistered
UsernameChanged
Event Sourcing in PHP

CQRS
SRP on system level

Gateway
Write Model
Read Model
CQRS and Event Sourcing
Double Team

Gateway
Write Model
Read Model
Event Store
Projection
Cache
Break
Questions?
Time to grab a beer!
Agenda
CQRS and Event Sourcing Basics
time to grab a beer
Event Sourcing case study
Networking

The Problem Space
Simplified version of a true story


The Problem Space
Bob runs a successful fashion business
New Strategy: Simplify and modernize the sales process

The Problem Space
Bettina is one of the many account managers in Bob's company

The Problem Space
Petra is a shop owner and one of 50.000 business customers of Bob
Bettina is one of the many account managers in Bob's company

The Problem Space
John is part of the customer service team in Bob's company
Petra is a shop owner and one of 50.000 business customers of Bob
Bettina is one of the many account managers in Bob's company

The Problem Space
Strategy: Simplify and modernize the sales process
requires negotiation of new contracts

The Problem Space
Contract App

The Contract App
- Supports account manager during contract negotiation
- Provides customer search and filter capabilities
- Dynamic multistep data form
- In-browser/realtime validation of complex decision rules
- Offline functionality

Symfony + prooph =

EAI
Bettina needs to find Petra out of 50.000 customers in the Contract App

EAI with Event Sourcing
Customer data is in a legacy system

Some Customer Events ...
Customer Import Stream
Some Customer Events ...
Some Customer Events ...
EAI with Event Sourcing
Customer data is in a legacy system

Some Customer Events ...
Customer Import Stream
Petra's shop was added
Some Customer Events ...
Petra's shop was updated
Nightly projection into customer search
Nightly XML/CSV export
Some Customer Events ...
Customer Projections
- No heavy ORM required, customer data is read only
- Read model acts as a cache

Focus on Behavior

Contract Stream
Appointment was made
Some Contract Events ...
Special conditions were requested
Some Contract Events ...
Send mail to John
Focus on Behavior

Contract Stream
Special conditions were requested
Some Contract Events ...
Special conditions were accepted
Some Contract Events ...
Send to external signing service
Monitor External Service

Contract Stream
Contract signing was started
Some Contract Events ...
Contract was internally signed
Some Contract Events ...
Contract was externally signed
Event Sourcing
Event Sourcing for enterprise application integration (EAI)
Manage business processes with event sourcing
Monitor long-running business processes
A Case Study

Save The Date


Thanks for listening
Questions?
File Imports

//Symfony\Console command, reads CSV file -> row => domain command
private function processDeskwareDataCollection(OutputInterface $output): void
{
//Use of ProgressBar omitted
$failed = 0;
foreach ($this->tmpDeskwareDataCollection as $deskwareData) {
try {
$domainCommand = new ImportCustomerDeskwareData($deskwareData->toArray());
$this->commandBus->dispatch($domainCommand);
} catch (CommandDispatchException $ex) {
//print and log error for row ...
$failed++;
}
}
if ($failed > 0) {
$output->writeln(
sprintf(
'<error>%d entries failed. Please check the log for details</error>',
$failed
)
);
exit(1);
}
exit(0);
}
Service Bus for Symfony

prooph_service_bus:
command_buses:
customer_command_bus:
plugins:
- 'prooph_service_bus.handle_command_invoke_strategy'
router:
type: 'prooph_service_bus.command_bus_router'
routes:
'ImportBasicCustomerData': 'customer_domain.import_basic_customer_data_handler'
'ImportDigitalInvoiceData': 'customer_domain.import_customer_digital_invoice_data_handler'
'ImportCustomerDeskwareData': 'customer_domain.import_customer_deskware_data_handler'
contract_app_command_bus:
# put command bus config for contract_app bundle here
event_buses:
contract_event_bus:
plugins:
- 'prooph_service_bus.on_event_invoke_strategy'
router:
# Service ID of the async message producer e.g. for Amazon AWS SQS
async_switch: 'my_async_message_producer'
type: 'prooph_service_bus.event_bus_router'
routes:
'ContractNegotiationWasCompleted':
- 'contract_app.send_negotiation_completed_mail'
'ContractNegotiationWasRejectedBySupervisor':
- 'contract_app.send_negotiation_rejected_by_supervisor_mail'
- 'contract_app.notify_customer_domain_about_rejected_negotiation'
Handle Commands

final class RequestSpecialConditionssHandler extends AbstractHandler
{
public function handle(RequestSpecialConditions $command)
{
$acquisitionProcess = $this->getAcquisitionProcess($command->acquisitionProcessId());
$acquisitionProcess->requestSpecialConditions(
$command->accountManager(),
$command->requestedSalesAreaDimension(),
$command->requestedPlanedAnnualSales()
);
$this->acquisitionProcessRepository->save($acquisitionProcess);
//SpecialConditionsRequested event is recorded internally
//and published on event bus after save
}
}
Event Store for Symfony

# prooph/event-store v6 compatible version
# prooph/event-store v7 bundle is wip
prooph_event_store:
stores:
contract_store:
type: 'contract_app.event_store.mongo_db_adapter'
repositories:
# name of repository is registered as service id
acquistion_process_repository:
repository_class: 'Contract\Infrastructure\MongoD\AcquistionProcessRepository'
aggregate_type: 'Contract\Model\AcquistionProcess'
aggregate_translator: 'prooph_event_sourcing.aggregate_translator'
stream_name: 'contract_stream'
# snapshot_store: prooph_snapshot_store.mongo_db <-- not activated by default
# one_stream_per_aggregate: false <-- default
Sf UG Berlin Meetup CQRS and ES Basics
By prooph
Sf UG Berlin Meetup CQRS and ES Basics
- 1,794