Wednesday, April 20, 2016

On Read Models

I learned something new about read models today.

Most discussions I have found so far emphasize the separation of the read model(s) from the write model. 

For example, in an event sourced solution, the write model will update the event history in the book of record.  Asynchronously, new events are read out of the book of record, and published.  Event handlers process these new events, and produce updated projections.  The read model answers queries using the most recently published projection.  Because we are freed from the constraints of the write model, we can store the data we need in whatever format gives us the best performance; reads are "fast".

But by the time the read models can access the data; the data is old -- there's always going to be some latency between "then" and "now".   In this example, we've had to wait for the events to be published (separately from the write), and then for the event handlers to consume them, construct the new projections, and store them.

What if we need the data to be younger?

A possible answer; have the read models pull the events from the book of record directly, then consume the events directly.  It's not free to do this -- the read model has to do its own processing, which adds to its own latency.  There record book is doing more work (answering more queries), which may make your writes slower, and so on.

But it's a choice you can make; selecting which parts of the system get the freshest data, and which parts of the system can trade off freshness for other benefits.

Example

In some use cases, after handling a command, you will want to refresh the client with a projection that includes the effect of the command that just completed; think Post/Redirect/Get.

In an event sourced solution, one option is to return, from the command, the version number of the aggregate that was just updated.  This version number becomes part of the query used to refresh the view.

In handling the query, you compare the version number in the query with that used to generate the projection.  If the projection was assembled from a history that includes that version, you're done -- just return the existing projection.

But when the query is describing a version that is still in the future (from the perspective of the projection), one option is to suspend the query until history "catches up", and a new projection is assembled.  An alternative approach is to query the book of record directly, to retrieve the missing history and update a copy of the projection "by hand".  More work, more contention, less latency.

If the client is sophisticated enough to be carrying a local copy of the model, it can apply its own changes to that model, provisionally; reconciling the model when the next updates arrive from the server.  That supports the illusion of low latency without creating additional work for the book of record (but might involve later reconciliation).

No comments:

Post a Comment