Event Sourcing: "Should I Use One Eventstore per Service?"
The problem with having more than one event store: Ensuring events are in the right order.
To reliably determine the current state of a system, the events which led to the state should be in the order in which they occurred.
This is straightforward if there is only one event store which ensures that this is the case. If there is more than one event DB it gets more complicated: Determining event order by timestamp depends on system timers being in sync, e.g. by using an atomic clock. Even then it’s not impossible to have two events which were time stamped with the exact same time.
Given that “one event store to rule them all” is a rather harsh form of coupling and often impractical at scale, DDD an d Bounded Contexts can help to simplify the necessary trade-offs and cut down on workarounds:
One event store per Bounded Context
This can help to limit the number of events which need to be sourced from external logs because what belongs together is more likely to “talk” to each other than to the “outside”. In practice, using Ubiquitous Language (a.k.a plain English, Swahili, German, etc.) to determine what belongs to a system and what doesn’t is a surprisingly powerful tool for managing coupling and cohesion. For example, the user with ID DQIIBA0OBwkEBA4EBAMAAg in the “Identity & Access” Bounded Context may be the customer with ID DQIIBA0OBwkEBA4EBAMAAg in the “Subscription Orders” context and the building manager with ID DQIIBA0OBwkEBA4EBAMAAg in the “Rental Property Management” context:
- In order to purchase a subscription to the “Rental Property Management” system, a user registers with “Identity & Access” which has its own event store (or may not even be an event sourced system). The resulting “User Registered” event gets published in order to notify other bounded contexts such as “Subscription Orders”.
- The user proceeds to subscribe. “Subscription Orders” also has its own DB and knows that the user is registered because it received the relevant event from “Identity & Access”. Upon subscription, a “Subscription Purchased” event occurs.
- Because it subscribes to events from “Identity & Access”, the “Rental Property Management System” nows that the building manager DQIIBA0OBwkEBA4EBAMAAg is a legitimate, registered user. From events received from “Subscription Orders”, it knows that the building manager is a subscriber in good standing.
Compared to having multiple event stores in the same context/system, this approach limits the number of events which are needed from separate event logs and makes it less likely that their order matters. Conversely, where the order does matter it is easier to write anti-corruption layers which prevent problems from out-of-sequence events, e.g.:
“Even if there is a “Subscription Purchased” event, a building manager is not considered to be a subscriber unless there is also a “User Registered” event.”
Start with one event store to rule them all, refactor later
If the contexts are “separate” enough, it may be possible to start out with running with one global event store in the beginning and start using multiple ones later. This is a whole lot easier to do if the individual Bounded Contexts/systems/applications were implemented with clean separation in mind from the beginning, e.g.:
- One code base/source code repo per bounded context.
- DevOps: The context can (usually) be deployed without requiring deployment and/or changes of others.
- Project management: The individal contexts/systems can be owned by their own team, with minimal need for coordination with other teams, regression testing other systems and/or release schedule dependencies.
With that, and given that event stores are add-only (and therefore side effect free), sharing a global event store is much less likely to cause problems than a shared current-state-only traditional relational database and splitting it up into separate instances is potentially less daunting.