Eventsourcing Patterns: Decision Tracking

Store the outcome of a decision to guard against rule changes.

By Mathias Verraes
Published on 29 May 2019



Decision Tracking

Store the outcome of a decision to guard against rule changes.

Problem

In an eventsourced system, a consumer listens to a stream of events. IT has some business logic that makes decisions, and acts upon those, for example in the form of a side effect or a new event. Because we’ve already stored the input events in the event store, we can recalculate that decision. Should our event store persist an event that represents that decision?

Solution

Often, the reasoning is that the event that represents the decision, is redundant. We could store it, but we can recalculate it, so we don’t have to store it.

The problem with this line of reasoning is that it doesn’t take into account model changes. The business rule that is applied by the consumer, could change over time, perhaps because of a new requirement, or a bug fix.

If we now recalculate the decisions, using the stored events, we get a different outcome than the one that was decided the first time. The old outcome may have been wrong, but it was the one that was calculated at the time, and that caused the side effect to happen. Our present system is therefore lying, as it shows us “facts” about the past that didn’t actually happen. This goes against the idea that an eventstore serves as a historical record of the system.

One solution is to track decisions as events, and store these just like other events. We can now inspect the event store, and not only see domain events for the things that happened, but also for the decisions our system has made, whether these are correct or not.

This is much more explicit. It can, for example, help us to mitigate the consequences of a bug, because we know exactly what happened. In fact, many problems arise from attempts to make a system look like a bug never happened.

Decision Tracking isn’t going to be worth the effort in all cases, for example if the we don’t expect anything to be affected by that decision long after it was made.

Example

A producer emits a stream of CustomerHasPurchasedTeddyBear events. A business rule states that “At every 1000th purchase, we give away a free teddy bear hat”. A consumer increments a counter for each event. Alice is the 1000th buyer, so the consumer stores FreeHatWasAwarded{customer=Alice} in the event store.

Later, after seeing how successful the campaign was, marketing wants every 750th customer to get a hat now. On top of that, they want to prevent people from getting more than one hat. Because we have a historical record of customers who received the hat, we are certain not to award hats twice.

Alternative

A different approach is Model Change Tracking. Here changes in the model are tracked in the event store, as regular events. When we want to recalculate decisions, we know exactly which version of the model was active at the time. We then need to recalculate using that specific version of the code, so that the outcome is identical with the original outcome (even if that outcome was wrong).

These could be generic model change events, such as ModelWasDeployed{version=7}, or domain-specific such as NewFreeHatRuleWasDeployed{version=7}, or, if possible, they could be configuration changes such as FreeHatTresholdWasChanged{treshold=750}.