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


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

prooph components
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
CQRS in PHP

prooph components
Just Try It


Service Bus for Symfony

prooph_service_bus:
command_buses:
todo_command_bus:
router:
type: 'prooph_service_bus.command_bus_router'
event_buses:
todo_event_bus:
plugins:
- 'prooph_service_bus.on_event_invoke_strategy'
router:
type: 'prooph_service_bus.event_bus_router'
routes:
'Prooph\ProophessorDo\Model\Todo\Event\TodoAssigneeWasReminded':
- 'Prooph\ProophessorDo\ProcessManager\SendTodoReminderMailSubscriber'
'Prooph\ProophessorDo\Model\Todo\Event\TodoWasMarkedAsExpired':
- 'Prooph\ProophessorDo\ProcessManager\SendTodoDeadlineExpiredMailSubscriber'
Tagged Command Handler

Prooph\ProophessorDo\Model\Todo\Handler\MarkTodoAsDoneHandler:
arguments: ['@todo_list']
public: true
tags:
- {
name: 'prooph_service_bus.todo_command_bus.route_target',
message_detection: true
}
Handle Commands

class MarkTodoAsDoneHandler
{
/**
* @var TodoList
*/
private $todoList;
public function __construct(TodoList $todoList)
{
$this->todoList = $todoList;
}
public function __invoke(MarkTodoAsDone $command): void
{
$todo = $this->todoList->get($command->todoId());
if (! $todo) {
throw TodoNotFound::withTodoId($command->todoId());
}
$todo->markAsDone();
$this->todoList->save($todo);
}
}
Event Store for Symfony

services:
Prooph\EventStore\Pdo\MySqlEventStore:
arguments:
- '@prooph_event_store.message_factory',
- '@doctrine.pdo.connection',
- '@prooph_event_store.single_stream_strategy'
doctrine.pdo.connection:
class: PDO
factory: ['@database_connection', getWrappedConnection]
prooph_event_store.single_stream_strategy:
class: Prooph\EventStore\Pdo\PersistenceStrategy\MySqlSingleStreamStrategy
prooph_event_sourcing.aggregate_translator:
class: Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator
Event Store for Symfony

prooph_event_store:
stores:
todo_store:
event_store: Prooph\EventStore\Pdo\MySqlEventStore
repositories:
todo_list:
repository_class: Infrastructure\Repository\EventStoreTodoList
aggregate_type: Model\Todo\Todo
aggregate_translator: prooph_event_sourcing.aggregate_translator
Event Store for Symfony

final class Todo extends AggregateRoot implements Entity
{
//...
/**
* @throws Exception\TodoNotOpen
*/
public function markAsDone(): void
{
$status = TodoStatus::DONE();
if (! $this->status->is(TodoStatus::OPEN())) {
throw Exception\TodoNotOpen::triedStatus($status, $this);
}
$this->recordThat(
TodoWasMarkedAsDone::fromStatus(
$this->todoId,
$this->status,
$status,
$this->assigneeId
)
);
}
//...
}
Event Store for Symfony


Event Sourcing Tutorial


Thanks for listening
Questions?
Symfony Live Berlin 2017 CQRS and ES Basics
By prooph