We started using event sourcing over ten years ago. One of the hardest lessons was that there is a kind of events that is not obvious at first but have a big impact on your system design. I call this kind secondary domain events.
In this post, I’ll explain what secondary domain events are, and how they impact the design.
Primary domain events
Primary events are events that actually happen in the real world. In the context of our application – a time tracking application – such events are, for example:
- A new employee started working in a company.
- The contract of an employee has changed.
- An employee’s name has changed because they got married.
Secondary Domain Events
Secondary domain events are events that do, however, not model events that actually happened, but are caused by users (or systems) making mistakes:
- A typo in the name of the employee.
- Enterred the wrong birthdate of the employee.
- Selected the wrong entry date of the employee.
- Changed data for the wrong employee.
- etc.
The Difference between Primary and Secondary Domain Events
Primary events carry meaning. In our application, users are often interested in the history of that data. They want to see the history of an employee’s contracts, or when the employee started working in the company.
However, the users are not interested in secondary domain events. Secondary events correct wrongly created primary events. When looking at the history of an employee’s name, the user wants to see that they got married and took on a new name, but doesn’t want to see that 3 days later, the name was corrected. The name change due to the marriage should directly lead to the correct(ed) name.
In our system, most event streams are even bi-temporal – an event has a timestamp when it entered the application and a timestamp when it takes effect. For example, I entered today that an employee gets married next month – the name change will be effective next month, not today. This makes things even worse. By the way, if you’d like a deep dive into bi-temporal event sourcing, take a look at Our Experience with Bi-temporal Event Sourcing.
Consequences of secondary domain events
The main feature of our attendance tracking system, in simple terms, is that users enter data on how they have worked; the system compares that to how they should work and presents a result showing how much overtime they have accrued and how many days of vacation they have left this year. Now, many of these secondary correction events lead to different results. Correcting the length of my Christmas vacation changes the number of days still available for vacation. So, we always need to apply the effects, but whether these events should be shown in the history depends on the use case: do we want to see events with a real-world impact, or events as an audit timeline?
A second consequence is that there simply are much more events to be modelled than the real-world primary events.
While this is not dramatic from a technical standpoint, the additional events and the more complicated handling of secondary domain events increase overall code complexity.
Sometimes a single primary event can have several secondary events as companions. Changing a contract for an employee, for example, has the following secondary domain events:
- corrected the effective date of the change,
- corrected the kind of the contract,
- corrected the target times in the contract,
- and many more.
All of these have different effects on the calculation. So, it is better to model them individually than with a single CorrectedContract event. Also, debugging would become harder if one of these corrections resulted in incorrect data. We would have to diff the data to see what actually changed.
Conclusions
Being aware of primary and secondary domain events helps design better systems. Systems that can express what actually happened, real-world changes (primary) or correction changes (secondary).
[…] The Problem with Secondary Domain Events in Event Sourcing (Urs Enzler) […]