Taking advantage of Event Sourcing to implement undo/rollback functionality

1,423 views
Skip to first unread message

Alex Spurling

unread,
Jun 29, 2016, 9:35:43 AM6/29/16
to DDD/CQRS
Let's say you want to implement a blog platform with the following requirements:

Create a new draft article
Update a draft article (update text/add images/change author etc etc)
Publish a draft article (record a revision number for each published article)
Rollback state of draft to a previous revision

Does it make sense to take advantage of the fact that we have events for each version of our article to implement the Publish / Rollback functionality? For example:

ArticleCreated
ArticleBodyUpdated
ArticleImageUpdated
ArticleAuthorUpdated
ArticlePublished (Revision 1 created, view model is updated to reflect published article)
ArticleBodyUpdated
ArticleImageUpdated
ArticleAuthorUpdated
ArticlePublished (Revision 2 created)
ArticleBodyUpdated
ArticleImageUpdated
ArticleAuthorUpdated
ArticleRolledBack (Rollback to revision 1)
ArticleBodyUpdated
ArticleImageUpdated
ArticleAuthorUpdated
ArticlePublished (Revision 3 created)

When loading my Article aggregate from events, I would want it to contain the state represented by the most recent draft updates, minus any changes that were rolled back. Normally to implement undo functionality, I would use compensating events but in this case, there could be hundreds of different types of events that can modify the state of my aggregate (in this example, I've only shown 3 but imagine there are others). In seems wrong to create so many compensating events to implement a single feature.

It seems that I need to separate out the draft state from the published state. Here is an outline of how I believe the aggregate should look

class Article {
  ArticleState draftArticle;
  ArticleState publishedArticle;
  int publishedRevision;
}

However, I cannot figure out how to maintain these article states given the sequence of events above without rebuilding a new ArticleState from scratch every time I encounter an ArticleRolledBack event. For articles that contain lots of rollbacks and revisions, this would quickly become massively inefficient.

Is there a better pattern I can use for building the state of my aggregate from a subset of previous events? Is this approach completely wrong for implementing this requirement? What approach would you use?

Alex

David Reaver

unread,
Jun 29, 2016, 11:55:35 AM6/29/16
to ddd...@googlegroups.com
Would it be possible to just snapshot all published articles in some read model? If I wasn't worried about memory, I would store all previous article states in the Article class, and attach the revision number to each published article. Then, when you need to roll back, you can just pluck out the correct revision very quickly and set that to draftArticle.

The way I see it, the only way you are going to be able to quickly switch to a different revision is to store them somewhere. I wonder if you could get any further benefits from storing published articles in their entirety, like allowing editors to quickly diff the different revisions.

I'm not an Event Sourcing expert though, so I would hold out to see if someone has a better answer :)

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

urbanhusky

unread,
Jun 30, 2016, 1:45:25 AM6/30/16
to DDD/CQRS
How about:
  • Keep a memento of the previous state in the event
  • Put the events on an additional stack (in addition to storing them)
  • On single undo, rollback stack - or in your case, roll back until a published event was undone
  • Rollback of stack: pop, raise new event from memento (and don't put it back on the stack)
  • You could even have a redo stack where you put the events you undid...

That's how we're going to handle undo/redo in our open source application.

Peter Hageus

unread,
Jun 30, 2016, 7:57:53 AM6/30/16
to DDD/CQRS
For starters, are there any invariants in this aggregate? Does event sourcing really bring anything useful? 

From your description it sounds a lot like a document with stored history, and a pointer to active version.

/Peter
Reply all
Reply to author
Forward
0 new messages