Checksum handling with prooph components
While working on this prooph components powered API, I realized it would make sense for our mobile client to know when data changed on the server-side without constantly pulling all data from the API and comparing it locally. In a "traditional" application I would have used a checksum mechanism - e.g. creating a checksum of all the data in the datastore - but with an event sourced application this is no longer needed as we can simply rely on the timestamp of the last event happened to know if things have changed.
Settings things up was rather simple. The projection set-up looks like this:
/* @var ProjectionManager $projectionManager */
/* @var SyncReadModel $readModel */
$projection = $projectionManager->createReadModelProjection(
'sync_checksums',
$readModel
);
$projection->delete(true);
$projection
->fromStream('my_events')
->whenAny(function(array $state, Message $event) {
$this->readModel()->stack('updateChecksumFor', $event);
})
->run();
The projection listens on any event and passes each and every received event to the updateChecksumFor() method of our read model. In the updateChecksumFor() method of the read model we first map the incoming event classes to a unique type, e.g. all events residing in moduleA namespace get "grouped" as type moduleA. The easiest way of doing that was a simple string search in the event class name - "not the best solution"(tm) but works for now:
$type = get_class($event);
if (stripos($type, 'moduleA')) {
$type = 'moduleA';
} else if (stripos($type, 'moduleB')) {
$type = 'moduleB';
} else if (stripos($type, 'moduleC')) {
$type = 'moduleC';
} else {
// simply ignore all other events!
return;
}
We then insert the data in the PostgreSQL database by making use of PostgreSQL upsert feature:
$sql = <<<EOT
INSERT INTO "sync_checksums" ("type", "checksum")
VALUES (:type, :checksum)
ON CONFLICT ("type") DO
UPDATE SET "checksum" = :checksum WHERE "sync_checksums"."type" = :type;
EOT;
$statement = $this->pdo->prepare($sql);
$statement->bindValue(':type', $type);
$statement->bindValue(':checksum', $event->createdAt()->format('U'),
PDO::PARAM_INT);
$statement->execute();
The client can fetch the data via a seperate API endpoint and then figure out which API calls are needed to update the local datastore to retrieve the latest changes.