The slides of the try out of my new talk "CQRS & event sourcing in the wild" - held at PHP Amersfoort in September 2016.
CQRS & event sourcing are currently very popular topics in the PHP community. However, most blogs and talks focus on the theory, simple applications or introductions to one of the CQRS / event sourcing frameworks currently available for PHP, not necessarily the challenges of a production deployment.
In this session we will try to bridge that gap and look at some of the issues that popped up during a real-world deployment of an event sourced application. We’ll discuss a few strategies to deal with these issues and how they would apply to current PHP event sourcing frameworks.
10. ' Event Sourcing ensures that all
changes to application state are stored
as a sequence of events.
-Martin Fowler
11. ACTIVE RECORD VS. EVENT SOURCING
Account
number
Balance
12345678 €€ 50,00
... ...
Money Withdrawn
Account number 12345678
Amount €€ 50,00
Money Deposited
Account number 12345678
Amount €€ 100,00
Account Created
Account number 12345678
12. COMMANDS TO EVENTS
Deposit Money
Account number 12345678
Amount €€ 100,00
Money Deposited
Account number 12345678
Amount €€ 100,00
class DepositMoney {
public $accountNumber;
public $amount;
}
class MoneyDeposited {
public $accountNumber;
public $amount;
public $timestamp;
}
function depositMoney(DepositMoney $command) {
$this->apply(new MoneyDeposited($command->accountNumber,
$command->amount, date()));
}
13. AGGREGATES
class BankAccount {
public $accountNumber;
public $balance;
// command handlers...
public function applyAccountCreated(AccountCreated $event) {
$this->accountNumber = $event->accountNumber;
$this->balance = 0;
}
public function applyMoneyDeposited(MoneyDeposited $event) {
$this->balance += $event->amount;
}
public function applyMoneyWithdrawn(MoneyWithdrawn $event) {
$this->balance -= $event->amount;
}
}
14. AGGREGATE STATE
Account
number
Balance
12345678 €€ 0,00
Money Withdrawn
Account number 12345678
Amount €€ 50,00
Money Deposited
Account number 12345678
Amount €€ 100,00
Account Created
Account number 56789012
Account
number
Balance
12345678 €€ 100,00
Account
number
Balance
12345678 €€ 50,00
15. CQRS
➤ Command Query Responsibility Segregation
➤ Separate writes (events) and reads (UI)
➤ Optimize for reads!
38. UPCASTING
class UserRegistered_V1 {
public $userId;
public $name;
public $timestamp;
}
class UserRegistered_V2 {
public $userId;
public $name;
public $date;
}
function upcast($event): array {
if (!$event instanceof UserRegistered_V1) {
return [];
}
return [
new UserRegistered_V2($event->userId, $event->name,
$event->timestamp->format("Y-m-d"))
];
}
40. THINGS TO BE AWARE OF
Upcasting
➤ Performance
➤ Complexity
➤ Existing projections not
automatically updated
Rewriting events
➤ Running code that
depends on old structure
➤ Breaking serialization
➤ Changing wrong events
50. ARCHIVING EVENTS
➤ Reduce working set
➤ Inactive / deleted aggregates
➤ Historic / irrelevant events
➤ Cheaper storage
51. FRAMEWORK COMPARISON
Framework Upcasting Snapshots Replaying
Broadway
(PHP)
No (PR) No (PR) Not in core
Prooph
(PHP)
MessageFactory
Yes, triggers on
event count
Example code,
off line
Axon
(Java/Scala)
Upcaster /
UpcasterChain
Yes, triggers on
event count
Yes,
ReplayingCluster
Akka
Persistence
(Java/Scala)
Event Adapter
Yes, decided by
actor
Yes
52. BROADWAY VS PROOPH
class TodoItem extends EventSourcedAggregateRoot {
private $itemId;
public function __construct(PostTodo $command) {
$this->apply(new TodoPosted($command->getItemId()));
}
public function getAggregateRootId()
{
return $this->itemId;
}
public function applyTodoPosted(TodoPosted $event) {
echo "Posted: " . $event->getItemId() . "n";
}
}
53. BROADWAY VS PROOPH
class TodoItem extends AggregateRoot {
private $itemId;
public function __construct(PostTodo $command) {
$this->recordThat(new TodoPosted($command->getItemId()));
}
protected function aggregateId() {
return $this->itemId;
}
public function whenTodoPosted(TodoPosted $event) {
echo "Posted: " . $event->getItemId() . "n";
}
}