PROOPH EVENT STORE
IN ACTION
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
Event Store?
E1
E2
E3
E4
E5
Event Log
Append-only storage
Events (messages) are stored in chronological order
When do I need one?
Event Sourcing
Audit Log
Scalability
When NOT to use it?
Short lived applications
Prooph Event Store
PDO - PostgreSQL, MySQL, MariaDB
ArangoDB (WIP)
Redis (WIP)
MongoDB (until ES v6, not supported ES v7)
Required Database Features in v7
Transactions
JSON (querying JSON data)
Autoincrement/Sequence
Unique (multikey) index
Transactions
COMMAND
AGGREGATE
EVENT
EVENT
EVENT
EVENT
EVENT
}
JSON Queries
if ($fieldType->is(FieldType::METADATA())) {
if (is_bool($value)) {
$where[] = "metadata->>'$field' $operatorString '"
. var_export($value, true) . "' $operatorStringEnd";
continue;
}
if (is_int($value)) {
$where[] = "CAST(metadata->>'$field' AS INT)"
. " $operatorString $parameterString $operatorStringEnd";
} else {
$where[] = "metadata->>'$field'"
. " $operatorString $parameterString $operatorStringEnd";
}
}
Autoincrement/Sequence
E1
E2
E3
E4
E5
Event Log
Document DB
Search Service
Projections
consume
Unique (multikey) index
COMMAND
AGGREGATE
v2
EVENT
V3
}
COMMAND
AGGREGATE
v2
EVENT
V3
}
Nice-To-Have Features
Horizontal Scalability
Reactive Streams
Horizontal Scalability
E1
E2
E3
E4
Node 1
AGGREGATE 1
E1
E2
E3
E4
Node 2
AGGREGATE 2
Reactive Streams
E1
E2
E3
E4
E5
Event Log
Document DB
Message Queue
Process Manager
Search Service
Services
subscribe
consume
Event Store In Action
$eventStore = new ActionEventEmitterEventStore(new InMemoryEventStore(), $eventEmitter);
$quickStartSucceeded = QuickStartSucceeded::withSuccessMessage('It works');
$streamName = new StreamName('event_stream');
$singleStream = new Stream($streamName, new ArrayIterator());
$eventStore->create($singleStream);
$eventStore->appendTo($streamName, new ArrayIterator([$quickStartSucceeded /*, ...*/]));
$persistedEventStream = $eventStore->load($streamName);
foreach ($persistedEventStream as $event) {
if ($event instanceof QuickStartSucceeded) {
echo $event->getText();
}
}
Extension Points
$eventEmitter = new ProophActionEventEmitter(
ActionEventEmitterEventStore::ALL_EVENTS
);
$eventStore = new ActionEventEmitterEventStore(new InMemoryEventStore(), $eventEmitter);
$eventStore->attach(
ActionEventEmitterEventStore::EVENT_APPEND_TO,
function (ActionEvent $actionEvent): void {
$recordedEvents = $actionEvent->getParam('streamEvents');
foreach ($recordedEvents as $recordedEvent) {
echo sprintf(
"Event with name %s was recorded. ///\n\n",
$recordedEvent->messageName()
);
}
},
-1000 // low priority, so after action happened
);
$eventStore->appendTo($streamName, new ArrayIterator([$quickStartSucceeded /*, ...*/]));
Advanced Query Features
//Stream Pagination: load 5 events starting at stream position 3
$loadedEvents = $this->eventStore->load($stream->streamName(), 3, 5);
//Load 5 events in reverse order starting with newest event in stream
$loadedEvents = $this->eventStore->loadReverse($stream->streamName(), null, 5);
//Filter using MetadataMatcher
$metadataMatcher = new MetadataMatcher();
foreach ($metadata as $field => $value) {
$metadataMatcher = $metadataMatcher
->withMetadataMatch($field, Operator::EQUALS(), $value);
}
$loadedEvents = $this->eventStore->load(
$stream->streamName(),
1,
null,
$metadataMatcher
);
Projections
$projectionManager = new InMemoryProjectionManager(new InMemoryEventStore());
$readModel = new UserReadModel(new InMemoryDocumentStore());
$projection = $projectionManager->createReadModelProjection('user', $readModel);
$projection
->fromStream('event_stream')
->when([
UserWasRegistered::class => function ($state, UserWasRegistered $event) {
$this->readModel()->stack('insert', [
'id' => $event->userId()->toString(),
'name' => $event->name()->toString(),
'email' => $event->emailAddress()->toString(),
]);
}
])
->run();
Event Store HTTP API
{
"title": "Event stream 'event_stream'",
"id": "http://localhost:8081/api/v1/stream/event_stream/1/forward/10",
"streamName": "event_stream",
"_links": [
{
"uri": "http://localhost:8081/api/v1/stream/event_stream/1/forward/10",
"relation": "self"
},
{
"uri": "http://localhost:8081/api/v1/stream/event_stream/1/forward/10",
"relation": "first"
},
{
"uri": "http://localhost:8081/api/v1/stream/event_stream/head/backward/10",
"relation": "last"
}
],
"entries": [
{
"message_name": "Prooph\\ProophessorDo\\Model\\User\\Event\\UserWasRegistered",
"uuid": "a8578a14-0dcd-48e8-b71f-b8cad2e78144",
"payload": {
"name": "prooph",
"email": "contact@prooph.de"
},
"metadata": {
"_aggregate_id": "3abb3ac6-467d-4bb1-8d06-607f7c43e49a",
"_aggregate_type": "Prooph\\ProophessorDo\\Model\\User\\User",
"_aggregate_version": 1,
"_position": "1"
},
"created_at": "2017-12-22T18:28:58.509690"
}
]
}
Event Schema Changes
$upcaster = new class() extends SingleEventUpcaster {
protected function canUpcast(Message $message): bool
{
return true;
}
protected function doUpcast(Message $message): array
{
return [$message->withAddedMetadata('key', 'value')];
}
};
$plugin = new UpcastingPlugin($upcaster);
$plugin->attachToEventStore($this->eventStore);
Join prooph
Thanks for listening
Questions?
PHPUGDD 2018 prooph event store
By prooph
PHPUGDD 2018 prooph event store
- 1,496