Event sourcing is a powerful architectural pattern that enables developers to build scalable, resilient, and flexible systems. By capturing and persisting a series of events, it provides a historical record of state changes and allows for easy auditing, debugging, and system evolution. In this blog post, we will explore three common scenarios related to event sourcing: managing commands and events, ensuring referential integrity, and handling global settings

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:

  • Should there be two events? When setting an entity as default, we may need to unset the previously set default entity. In this case, it is advisable to have two events: defaultSet for the entity being set as default and defaultUnset for the entity being unset.
  • Where to create these events? Events are typically created within the domain layer of an application. The domain layer contains the business logic and rules that govern the behavior of entities.
  • Which one to fire first? It is important to fire the defaultSet event before the defaultUnset event to ensure consistency. This way, the new default entity is established before unsetting the previous default entity.
  • Handling non-atomic events: When firing two events that are not atomic, there is a possibility that the first event is successfully appended while the second event fails. To tackle this, it is recommended to use event sourcing frameworks or libraries that provide mechanisms for handling failures and compensating actions. For example, the framework may have a mechanism to roll back the first event if the second event fails.

2. Referential Integrity

The next use case involves maintaining referential integrity between entities. Let’s consider a scenario where Service A defines Entity A, Service B defines Entity B with references to Entity A, and Service C maintains a read model of Entity B. Now, if Entity A is deleted, we expect Services B and C to clean up any references to Entity A. Here are some considerations:

  • Where to implement the cleanup logic? The responsibility of cleaning up Entity B references to Entity A typically lies within Service B. Since Service B manages the state and relationships of Entity B, it is the most appropriate place to handle the cleanup logic.
  • Avoiding duplication of business logic: To avoid duplicating the cleanup logic in multiple services, such as Service C, it is advisable to propagate the cleanup result as a new event. By doing so, other services can subscribe to this event and perform their own actions based on it.
  • Preventing repetitive actions during event replay: When firing a new event, such as a cleanup event, it is crucial to ensure that the event replay mechanism does not trigger the action repeatedly. One approach is to include a flag or metadata in the event that indicates whether the action has already been performed. Services can check this flag before executing the action to prevent redundant operations.

3. Global Settings

Lastly, let’s consider a use case involving global settings that need to be applied across different services when creating a new organization. Here are some considerations:

  • Modeling default values: To manage default values for global settings, it is recommended to have a separate service, let’s call it Service A, responsible for managing these settings. The default values can be stored within Service A and accessed by other services when needed.
  • Triggering a SettingsCreated event: When creating a new organization, it is appropriate to trigger a SettingsCreated event with the initial values of the global settings. This event can be consumed by the relevant services to apply the default values and initialize their own settings accordingly.

In conclusion, event sourcing is a powerful approach for building robust and scalable systems. By carefully managing commands and events, ensuring referential integrity, and handling global settings, developers can leverage the benefits of event sourcing and create flexible, event-driven architectures.