'Event Sourcing - Replaying Events
Imagine an event sourced system where there exists a consuming service that is subscribed to a certain Event A. Once this consumer detects Event A has been emitted in the network, it handles it somehow and dispatches its own Event B.
How would someone replay such a system. Before the replay, both Event A and Event B exist in the event store/database. If we replay Event A and Event B, would this not double count the dispatch of Event B (once being deduced from A and the other being replayed from our event store)? How do you go about replaying events in general when 1 event may cause a cascading chain of other dispatched events.
Solution 1:[1]
It is not really a form of replaying the events in the system so that each event is published again and triggers actions. It is more like rehydrating (reconstituting) aggregates from events which are stored in the event store.
The implementation could for instance involve a specific constructor (or factory method) of an aggregate that takes a list of the stored domain events related to the specific aggregate. The aggregate than simply applies those events to mutate it's own state until the current state of the aggregate is reached.
You can take a look at such an implementation in Vaughn Vernons sample Event Sourcing and CQRS project iddd_collaboration. I directly referenced the implementation of a Forum Aggregate which is derived from Vaughn Vernon's implementation of an EventSourcedRootEntity.
You can look into the Forum constructor
public Forum(List<DomainEvent> anEventStream, int aStreamVersion) {
super(anEventStream, aStreamVersion);
}
and the related implementations of the different when() methods and the base class functionalities of EventSourcedRootEntity.
Note: If there is a huge amount of events and performance issues are a concern during aggregate rehydration looking into the concepts of snapshots might also be of your interest.
Solution 2:[2]
Events "replay" can easily be handled within the aggregate pattern because applying events does not cause new transactions, but rather the state is rehydrated. It's important to have only event appliers in the aggregate constructor when it's instantiated out of a list of ordered events.
That's pretty much event sourcing. But there are potential problems when expanding this into event driven architecture (EDA) where an entity/aggregate/microservice/module reacts to an event by initiating another transaction.
In your example, an entity A produces an event A. The entity B reacts to event A by sending a new command, or starting a new transaction that ends up producing an event B.
So right now the event store has event A and event B.
How to ensure a replay or a new read of that stream or all streams doesn't cause a write amplify? Because as soon as event A handlers reads the event won't know if it's the first time it has handled it (and has to initiate the next transaction, command B --> event B, or if it's a replay and doesn't have to do anything about it because it already happened and there is an event B already in the stream.
I am assuming this is your concern, and it's a big one if the event reaction implies making a payment, for example. We wouldn't want to make a new payment each time the event A is handled.
There are a few options:
- Never replay events in systems that react to events by creating new transactions. Never replay events unless it's for aggregate instantiation (event sourcing) which uses events just to re-hydrate state, or unless it's for projection/read models that are idempotent or when the projections are being recreated (because DB was dropped for example)
- Another option is to react to an event A by appending a command B to a "command stream" (i.e: queue) and have the command handler receive it asynchronously and create the transaction that produces event B. This way, you can rely on the Event store duplicate check and prevent the append of a command if it already exist. The scenario would be similar to this:
A. Transaction A produces event A which is appended to an event store stream B. Event Handler A reacts to event A and adds a command B to a command stream C. Command handler B receives the command B and executes transaction that produces an event B appended to the stream.
Now the first time this would work as expected.
If the projections that use event A and event B to write in DB a read model replay events, all is good. It reads event A and then event B.
If the "reactive" event handlers receive event A again, it attempts to append a command B to the command stream. The event/command store detects that command B is a duplicate (optimistic concurrency control using some versioning) and doesn't add it. Command handler B never gets the old command again.
It's important to notice that the processed commands should result in a checkpoint that is never deleted, so that commands are never ever replayed. That's the key.
Probably there are also other mechanisms out there.
Solution 3:[3]
You're referring to what's called a "Saga Pattern" and in order to resolve it you need to make your commands explicit. This example helps to illustrate the difference between Commands and Events.
Events are the record of what happened. The are immutable, connected with an entity and describe the original intention of the user.
Commands are a request to do something, which may cause an event to be recorded. They can also cause 'real world' state changes outside of the event-sourced system, but in doing so they should cause an event that records the external change happened.
A few rules will resolve your conundrum:
- You cannot record an event without a corresponding command having executed. Every event was caused by a command.
- You cannot process commands until the event stream has 'caught up' to the present. Otherwise you are taking action on a partial replay of history.
Back to the Saga Pattern: In the Saga Pattern, events can lead to more commands. In this way, the system can progress based on a cascade of events and commands and execute a distributed workflow, choreographed by the relations between system state, commands generated, and further events generated.
In your case, as long as you wait for the full event stream to be replayed before issuing the next command, you can then prevent the duplicate cascading event by checking that the action has not already been done.
If event B already exists, there's no need to issue another command to generate event B again.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | afh |
Solution 2 | diegosasw |
Solution 3 | user18820619 |