PROOPH EVENT STORE
IN ACTION

Alexander Miertsch

Founder and CEO of prooph software GmbH

Founder and core contributor prooph components

 

 

contact@prooph.de

@codeliner

@prooph_software

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?

Made with Slides.com