Handling out of order events in CQRS read side
If you have a sequence number, then you can detect a situation where current event is out of order, e.g. currentEventNumber != lastReceivedEventNumber + 1
Once you've detected that, you just throw an exception. If your subscriber has a mechanism for 'retries' it will try to process this event again in a second or so. There is a pretty good chance that during this time earlier events will be processed and sequence will be correct. This is a solution if out-of-order events are happening rarely.
If you are facing with this situation regularly, you need to implement global locking mechanism, which will allow certain events be processed sequentially. For example, we were using sp_getapplock in MSSQL to achieve global "critical section" behaviour in certain situations. Apache ZooKeeper offers a framework to deal with even more complicated scenarios when multiple parts of the distributed application require something more than a just simple lock.
Timestamp based solution:
The incoming messages are:
{
id: 1,
timestamp: T2,
name: Samuel
}
{
id: 1,
timestamp: T1,
name: Sam,
age: 26
}
{
id: 1,
timestamp: T3,
name: Marlon Samuels,
contact: 123
}
And what we expect to see irrespective of the ORDER in the database is:
{
id: 1,
timestamp: T3,
name: Marlon Samuels,
age: 26,
contact: 123
}
For every incoming message, do the following:
- Get the persisted record and evaluate the timestamp.
- Whichever's timestamp is greater that's the target.
Now let's go through the messages:
- T2 arrives first: Store it in the database as it's the first one.
- T1 arrives next: Persistent one (T2) & incoming (T1), so T2 is the target.
- T3 arrives: Persistent one (T2) & incoming (T1), so T3 is target.
The following deepMerge(src, target) should be able to give us the resultant:
public static JsonObject deepMerge(JsonObject source, JsonObject target) {
for (String key: source.keySet()) {
JsonElement srcValue = source.get(key);
if (!target.has(key)) { // add only when target doesn't have it already
target.add(key, srcValue);
} else {
// handle recursively according to the requirement
}
}
return target;
}
Let me know in the comment if you need full version of deepMerge()