20

I realize the above question probably raises a few 'what??'s, but let me try to explain :

I'm trying to wrap my head on a couple of related concepts, basically the Saga-pattern ( http://www.rgoarchitects.com/Files/SOAPatterns/Saga.pdf) in combination with Event-sourcing (A DDD-concept: http://en.wikipedia.org/wiki/Domain-driven_design)

A good post that wraps it together: https://blog.jonathanoliver.com/cqrs-sagas-with-event-sourcing-part-ii-of-ii/

I'm getting to the question in a minute, but I think I have to try to summarize what I understand of it first (which might well be wrong, so please correct if that's the case) since this might well impact why I'm asking the question to begin with:

  1. The Saga pattern is a sort of broker that given an action (end-user, automated, etc. essentially anything that is going to change data) divides that action up in business activities and sends each of these activities as messages to a Message Bus which in turn sends it to the respective aggregate roots to be taken care of.
  2. These aggregate roots can operate completely autonomously (nice separation of concerns, great scalability, etc. )
  3. A Saga-instance itself doesn't contain any business logic, that is contained in the aggregate roots it sends activities to. The only 'logic' contained in the Saga is 'process'-logic, (often implemented as a Statemachine), which determines based on actions received (as well as follow-up events) what to do (i.e: what activities to send)
  4. The Saga-patterns implements a kind of distributed transaction pattern. I.e: when one of the aggregate roots (which again work autonomously, without knowing of eachothers existense) fails, the entire action might have to be rolled back.
  5. This is implemented by having all aggregate roots, upon completion of their activity report back to the Saga. (In case of success as well as error)
  6. In case all aggregate roots return a success, the internal statemachine if the Saga determines what to do next (or decides it's done)
  7. In case of failure, the Saga issues to all aggregate roots that took part in the last action a so called Compensation Action, i.e: an action to undo the last action each of the aggregate roots did.
  8. This might be simply doing a 'Minus 1 vote' if the action was "plus 1 vote' but it could be more complicated like restoring a blogpost to it's previous version.
  9. Event-sourcing (see the blogpost combining the two) aims to externalize saving the results of each of the activities that each of the aggregate roots undertake to a centralized Event Store (the changes are called 'events' in this context)
  10. This Event store is the 'single version of the truth' and can be use to replay the state of all entities simply by iterating the events stored (essentially like an event log)
  11. Combining the two (i.e: letting aggregate roots use Event-sourcing to outsource saving their changes before reporting back to the Saga) enables lots of nice possibilities, one of which concerns my question...

I felt I needed to get this off of my shoulder, since it's a lot to grasp in one go. Given this context/mindset (again, please correct if wrong)

the question: When an aggregate root receives a Compensate Action and if that aggregate root has outsourced it's state-changes using Event-sourcing, wouldn't the Compensate Action in all situations just be a delete of the last event in the Event Store for that given Aggregate Root? (Assuming the persist-implementation allows deletes)

That would make sense to me a lot (and would be another great benefit of this combination) but as I said I might be making these assumptions based on a incorrect/incomplete understanding of these concepts.

I hope this didn't get too long-winded.

Thanks.

5 Answers 5

10

You shouldn't delete the events from your event store. These represent something that happened. If something happens now, then you should know it too, therefore add an event that will force your aggregate to go back to some previous state. That's a pity to delete information from your database.

think about the example of the cart from greg young and the removed items. May be tomorrow the information of compensating action might be of any value..

As for the saga, every thing is correct, as far as I know myself. You should think of the saga as of a state machine. It does only that.

It is a Command the saga is issuing to tell the aggregate to do something. Of this action there will be an event. This event might be the corrective action you need, or might need to be denormalised in a view so that some user will see it to decide what to do.

It depends on your business rules. Either, there is an easy fix : the aggregate root knows that in such case , you do that, or the choice is too complex to be done programmatically ( development cost are too high) and therefore, you might propose this to some user, who might choose between different action. Everything depend on the context of the compensating action. This is pure business rules. What should we do in this case or that case. This is something to ask your domain expert, and then choose with him, if it is better to develop an automatic solution or if the solution is to ask the user Ronald R., because he is the one to answer this question usually.

8
  • How would the aggregate know to which state to revert? Would it be allowed to query against the event-store or something?
    – Geert-Jan
    Commented Jul 19, 2012 at 13:18
  • It should'nt revert but add another event. It is a Command the saga is issuing to tell the aggregate to do something. Of this action there will be an event.
    – Arthis
    Commented Jul 19, 2012 at 13:39
  • Yeah I understand. "Revert" was probably ill-chosen. Consider the following: I might want to use Event Sourcing with Sagas for modeling, among other things, Document publishing / approval flows. What if an editor has send an ChangeBlogBody-action that is ultimately persisted as a BlogBodyChanged-event to the Event Store. Afterwards, for some reason, a Compensate Action is issued. How would the Aggregate know what compensate event to fire that results in the contents of the Blog Body as it was just before the ChangeBlogBody-action was issued?
    – Geert-Jan
    Commented Jul 19, 2012 at 13:49
  • It depends on your business rules. Either, there is an easy fix : the aggregate root knows that in such case , you do that, or the choice is too complex to be done programmatically ( development cost are too high) and therefore, you might propose this to some user, who might choose between different action.
    – Arthis
    Commented Jul 19, 2012 at 14:03
  • Everything depend on the context of the compensating action. This is pure business rules. What should we do in this case or that case. This is something to ask your domain expert, and then choose with him, if it is better to develop an automatic solution or if the solution is to ask the user Ronald R., because he is the one to answer this question usually.
    – Arthis
    Commented Jul 19, 2012 at 14:11
6

For completeness I thought to include an relevant snippet from Martin Fowler about ways to revert state:

As well as events playing themselves forwards, it's also often useful for them to be able to reverse themselves.

Reversal is the most straightforward when the event is cast in the form of a difference. An example of this would be "add $10 to Martin's account" as opposed to "set Martin's account to $110". In the former case I can reverse by just subtracting $10, but in the latter case I don't have enough information to recreate the past value of the account.

If the input events don't follow the difference approach, then the event should ensure it stores everything needed for reversal during processing. You can do this by storing the previous values on any value that is changed, or by calculating and storing differences on the event.

From: http://martinfowler.com/eaaDev/EventSourcing.html

1

Conceptually:

  • Event is something what happened. The past cannot be changed.
  • Command is something to do. Command may not happen (may be rejected).

We can only change our future state by executing another command (Compensate Action) which should result in Events changing state of the application.

To "de-confuse" the question, think about this phrase "delete of the last event":

  • What if the last event is not the one which has to be deleted? What if other events happened after the one to be deleted? These events have to be changed too (as their base state would have changed by deleting event before them).
  • What if event was sent to external system? You may have no access to correct its state other than sending another event.

In short, you have no option to delete events within CQRS pattern.

You may shrink your Event Store by making a snapshot state (which is based on all events leading to that state), but this physical aspect has nothing to do with the concept of event.

0

Geert-Jan, I too think the compensation action can simply delete the corresponding event(s). It makes sense and it shows another benefit of Event Sourcing design pattern: easier implementation of the Compensating Transaction design pattern.

Some say deleting the event violates the "principles" of event sourcing or CQRS. I think that's a limiting generalization. I think it's ok to delete an event if it was created within the scope of a global transaction that is being cancelled. Consider the pseudocode:

try {
    newEvents = processMycommand(myCommand)
    for (Event newEvent : newEvents)
        EventStore.insertEvent(newEvent);
} catch (Exception e) {
    EventStore.rollback();
}

Suppose your event store is a transactional database. In the pseudocode, you can imagine the situation where you inserted the first event, but when trying to insert the second event an Exception was thrown. The rollback command would naturally revert the insertion of the first event. Is that reasonable?

If it's reasonable for an ACID database transaction (transaction with commit/rollback), why wouldn't it be reasonable for a compensating transaction?

When executing the global Saga transaction, data changes can be undone (by compensation). There's no need to keep the event that was created during the transaction because the transaction did not complete.

Now, if the compensation tries to delete an event and that event is not the newest event on an object, then the deletion should not occur. But in general, that's unlikely to happen, especially in read-intensive solutions.

0

Let's play with the thought that the event storage is just data you want to save. For example we can have a hard drive, we can start at the beginning and write data on it. Every time we got an event we append our previous data, so we just continue writing on the disk where we stopped last time. When we want to remove an event we go back, remove that part from the disk and leave a gap. Moving back on its own slows down our event storage, but we can live with that. If we use a filesystem, that will try to fill the gap with latter events, so we will end up having a slow fragmented storage or we can do defragmentation. Neither of them is something we like. If we are talking about databases you get this if you use a relational database instead of an append only database for storing your events:

enter image description here ref: https://cambridge-intelligence.com/bringing-time-series-data-to-life-with-keylines/

Ofc. by a normal site this does not matter, but these solutions are designed for huge websites like facebook, google, etc... So really the question does not make sense, because how would you delete an event in an append only database and how would you call compensation something like building a time machine and moving back in time to change or prevent an event???

Afaik. the only way to solve this is creating a new event storage where you exclude the events you don't want to have and after that removing the old event storage. But this an extreme solution to remove a single event. If we are talking about GDPR, then the only relative good solution I know of is storing personal data encrypted in the event storage and removing the encryption key from a different database.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.