Revert the domain model to a previous state

Hi!

We want to support a kind of “RevertToPreviousState” event, with the purpose of
making corrections. The effect of this event should be to “undo” a number of
previous events, after which a new series of corrected events will be applied
and published.

We do not want to keep a history of states in our domain model, but instead
make use of Event Sourcing to replay events up to a specific point in history,
stopping just before the events to be corrected.

How do we do this in a clean way?

We could not find a way to process the “RevertToPreviousState” event in the
same way while applying the event and while loading the event from the event
store.

We hope this is clear enough to get an answer. We will be happy to explain if
it is not.

Regards.

For very simple domains it might be possible to look at the eventstream and derive compensating commands for all events you want to “undo”. Additions become removals (easy), removals become additions (can be easy if you’re able to store the complete object you removed in the event), changes can be reverted if you’re able to store the “before” state in the event.

If the corrective actions are really a core part of your domain (and not just a CTRL-Z thingy) you should model commands and events to accomodate this.

Hope this helps your thought process a bit :slight_smile:
Jorg

Thanks for your reply, Jorg.

We got something working:

  • We extended ‘EmbeddedEventStore’ in order to override the ‘readEvents’ method to read the whole event stream into a List. We can safely do that because the history per aggregate is never very long. We scan this list for ‘RevertCommand’ events, which contain an id of a command to be reverted. Then we filter out the events resulting from those commands (each event contains an id of the command from which it originated) and return the resulting list as a ‘DomainEventStream’.

  • We wrote our own ‘Aggregate’ that delegates to ‘LockAwareAggregate’ and is able to reload itself from the modified event stream when a ‘RevertCommand’ event is applied.

  • We wrote our own ‘Repository’ that delegates to an ‘EventSourcingRepository’ to wrap loaded aggregates in our own.

  • The downstream view model’s of course must and did implement their own mechanism to revert to a previous state, by storing a version of their state for every series of events that resulted from a particular command.
    My question is whether this is the best and cleanest way to implement this.

Regarding the other two options you suggested:

  • Earlier we did add extra information to some of our events in order to be able to revert them. It worked, but since our domain is not very simple (>100 commands, >60 events and counting), we think the amount of work to do this for every event will be prohibitive, besides being complex and error-prone. Also because some events contain a lot of information, even without the ‘before’ information.
  • The corrective actions are indeed a core part of our domain and for some simpler types of correction we intend to create dedicated events. We feel that in addition to that the capability to revert to a previous state is still needed for a number of situations that are too complicated to repair with a correction event, for example because the whole corrected command needs to be validated again to a number of (also complex) business rules in the context of the reverted state.

Thanks for helping our thought process!

It all depends, whether this is the cleanest way…

The cleanest way would be to create explicit counter actions for everything. But if your domain is anything more complex than a bank account (where you could compensate a withdrawal error with a deposit, and vice versa), it may not always be a feasible option.
Do note that using the “oh, now forget the last batch of events” approach may have a complexity impact on event consumers as well. If they process the entire stream (as opposed to only a single aggregate’s stream), reverting a specific segment of the stream may become quite complex.

You could also consider adding some contextual data in the “revert” event, which gives your event consumers information about what the state should be instead. Obviously, that has limitations as well.

Just wondering, instead of creating your own aggregate, couldn’t you wrap the domain event stream to read ahead and clear events as they are marked as reverted?

Cheers,

Allard

Another option: you copy the eventstream up until the revert point to a new aggregate id. You would then need a merging eventstream reader to reconcile the complete history for the AR if ever needed, using copied-to and copied-from events on both sides this should be doable. But then you also need to dean with the aggregate id changing each time…