On monday we published a post with a lot of open questions about event sourcing. Today we will give some answers to these questions.

If you have not read the post yet, you can find it here. After posting the article we went to the Microservices-Meetup-Hamburg to get some answers to our questions and came home with a lot of new ideas and solutions thanks to the great people there. This is what we learned:

1. Command and Events

Let’s begin with a use case involving a list of entities where only one entity can be marked as default. To implement this functionality, we need to consider the following questions:

  • General answer: if business rules require an atomic solution, it is necessary to model an event that covers both changes: setting and unsetting the default entity. However, if there is no atomicity requirement, creating two events from a command is acceptable.
  • Thoughts in the context of Event Sourcing: to ensure atomicity, a single event can be created that captures the switch from the previous default entity (ID 1) to the new default entity (ID 2). This event would be added to the event stream of either entity ID 1 or ID 2, but not both, to maintain atomicity. Consequently, one of the streams would be unaware of this event. This is problematic and does not fulfill the requirements. In case of Event Sourcing we would need to have a new stream defaultServer and track the event separately (see alternatives futher down).
  • Solution in our specific case: we create two events (defaultUnset and defaultSet). The defaultUnset event is appended to the stream of the previous default entity, and the defaultSet event is appended to the stream of the new default entity. The order of events ensures that the unset operation occurs before setting a new default entity, handling the scenario of having no default entity instead of having two defaults.
  • Alternatives: to model an atomic event using Event Sourcing, a separate stream can be created specifically for managing the “Default AdServer” functionality. The isDefault property can be removed from the ad server model. In this new stream, events can be published that reference the ad server ID for the new default ad server. Other services can listen to these events and update their read models accordingly.

2. Referential Integrity

  • General thoughts: when referential integrity becomes an issue, it may indicate that the services are designed at a very granular level, resulting in split responsibilities that should be together. If changing the architecture is no option, we can see the referential integrity concerns as a form of cache invalidation. This involves having a service listen for deletion events, applying the necessary business logic, and generating new events. Other services then listen to these new events, ignore the deletion event, and update their read models or caches accordingly.
  • Solution in our specific case: the main concern was determining the appropriate place to implement the business logic for handling the deletion of an entity. In this case, the responsibility was assigned to the service that references the deleted entity. The service listens to all events as part of the Command Query Responsibility Segregation (CQRS) pattern to maintain its read model. Upon detecting a deletion event, the service fetches all relevant entities from the read model and triggers a separate validation process. Since eventual consistency may affect the read model’s accuracy, the validation process re-reads the entities from their corresponding event stream. After creating the current state from the stream, the service checks for references to the deleted entity and applies the necessary business logic to generate new events. These new events are then appended to the event stream of the respective entity.

3. Global Settings

  • General thoughts: when it comes to managing default values in global settings, there are different options to consider. One option is to duplicate the default values across services, which simplifies the implementation, but lead to redundancy. Another option is to create an extended event within the settings service, annotating it with the default values. The service consuming the original event can then listen to this annotated event having the default values at hand.
  • Solution in our specific case: an additional event named DefaultValuesSet was introduced, triggered upon OrganizationCreated. In case of replaying the event stream it may happen we create the DefaultValuesSet event several times. You have to consider preventing re-creation of the event (e.g. if the action has important side effects like sending a mail to the user). We decide not to check if the event has already been created. Instead, the services listen for new default value events and check if they already have stored default values. If they do, they skip the event; otherwise, they apply the default values.