Founder and CEO of prooph software GmbH
Founder and core contributor prooph components
contact@prooph.de
Supported Web Frameworks
or any other framework/codebase supporting container-interop & interop-config
CQRS and Event Sourcing Basics
time to grab a beer
Event Sourcing case study
Networking
Complex
Distributed
Chaotic
Informational message
Communicate facts that have happened
Relevant to the business
OrderPlaced
OrderPayed
OrderShipped
<?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);
}
}
<?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);
}
}
# 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
UserRegistered
UsernameChanged
ChangeEmailFailed
{
E1 + E2
Change Email
UserRegistered
UsernameChanged
5 min
7 min
Who has changed username within 5 min after registration?
UserRegistered
UsernameChanged
Gateway
Write Model
Read Model
Gateway
Write Model
Read Model
Event Store
Projection
Cache
CQRS and Event Sourcing Basics
time to grab a beer
Event Sourcing case study
Networking
Simplified version of a true story
Bob runs a successful fashion business
New Strategy: Simplify and modernize the sales process
Bettina is one of the many account managers 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
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
Strategy: Simplify and modernize the sales process
requires negotiation of new contracts
Contract App
Symfony + prooph =
Bettina needs to find Petra out of 50.000 customers in the Contract App
Customer data is in a legacy system
Some Customer Events ...
Customer Import Stream
Some Customer Events ...
Some Customer Events ...
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 ...
Contract Stream
Appointment was made
Some Contract Events ...
Special conditions were requested
Some Contract Events ...
Send mail to John
Contract Stream
Special conditions were requested
Some Contract Events ...
Special conditions were accepted
Some Contract Events ...
Send to external signing service
Contract Stream
Contract signing was started
Some Contract Events ...
Contract was internally signed
Some Contract Events ...
Contract was externally signed
Event Sourcing for enterprise application integration (EAI)
Manage business processes with event sourcing
Monitor long-running business processes
//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);
}
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'
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
}
}
# 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