In part nine of my event sourcing series, we revisit something I showed in part eight but didn’t explain: events in a bi-temporal event stream that modify the projected data independently of the effective time axis. We called this behaviour perpetual modification (you know, naming is hard).
It’s simpler than it sounds – at least a bit.
Examples of mixed uni- and bi-temporal events
When an organisational form* changes its name, we want that name to be used even when you look at a past moment in time, when the unit was still called something else. This was a trade-off decision between correctness and our users’ understanding of what they see – of course, the users won. 😊 Name changes are typically minor changes; if there is a big change in the organisational chart, new units are created (with new names).
* An organisational form defines an organisational chart, e.g. showing the company, its departments, and teams (the units inside the form). There are typically additional forms that specify locations or are used only to configure rules. Rules can be associated with people inside units.
An organisational form also includes settings that specify whether it is used when looking for a person in a given role, or for another person, e.g., when looking for an HR person responsible for a given person. These settings do not reflect a change of the real organisational form on a specific timestamp. There is also a setting that specifies whether each person in the company needs an assignment in this form or whether it is optional. Typically, the main organisational form requires an assignment for each person and is used to look up relationships (e.g., HR, supervisor).
Another example is a person’s name. A person may change their name. Although this happens on a specific date, we apply the name change perpetually (without an effective date) so that the new name is shown regardless of the date the data is viewed. We did this in response to feedback that users were confused by old names appearing.
Couldn’t you separate uni-temporal and bi-temporal events into dedicated event streams?
Yes, we could. Again, this is a trade-off decision that we sometimes make one way, sometimes another. Let me show you our reasoning.
Having dedicated streams for both kinds of events is conceptually simpler. Putting both kinds of events into the same event stream lets us read all events with a single database query and project them in one go. So, it depends on the application’s use cases, which approach is better. When we need both kinds of events most of the time, it is better to query and project them together. Better equals less code with slightly better performance. For persons and organisational forms, access is very frequent; therefore, we decided to put both types of events into a single event stream.
Just one more detail
As you have seen in the last post, the bi-temporal projector projects the events into a timeline:

The timeline consists of a sequence of phases. Each phase has a value holding the data at that point in time on the effective time axis. Ot it’s a phase without a value because the value was deleted.
When the projector encounters an event that must be applied perpetually. It applies the event to all phases that were or would be created by events with a smaller application. That is necessary because an event with a later application timestamp (with or without an effective timestamp) should be able to change the same “field” on the projected data.
If we could, for example, set the name of an organisational unit in both ways: either with an effective date (from then on, the unit is named differently), or without (change the name perpetually). The perpetual change should not affect newer events with an effective time and later application. Otherwise, the perpetual change would always override the other events.
I think this is enough brain-gymnastics for today. Unless you want to stay for a not-so-deep dive…
Not-So-Deep dive
Let’s take a look at the projection definition that we saw in the last post:

As you can see, there are four ModifyPerpetually events: events that rename a form or unit and events that change the configuration. When defining an event as ModifyPerpetually, we don’t need to specify an effective timestamp.
To make this behaviour hopefully clearer, here are the test cases for testing the modify-perpetually feature (I left out the test cases for other features):

- We use some simple string parsers (think: match text with “created” ->; not real parsers) to read the events and the expected timeline. The timestamp with date and time is the application; the date is the effective.
Themodifiedevents don’t have an effective because these are theModifyPerpetuallyevents. - We run the projection
- We assert that the result is as expected. The
testWithResultfunction is a function wrapper around Unquote so that we can see the description in the error message as well.
Have fun thinking through the test cases. I had mine when I wrote these tests – back in 2019, obviously.😅