Monday, September 7, 2009

A Rewind Button For Your Application Without the Temporal Object Pattern

In Ayende's style, I will blog as soon as a thought comes to me.

In the last year or so, I have been working on applying the Event Sourcing pattern. This led to some interesting thoughts. With the fact that the state of any object will be "rewindable", you can do a true audit. Audit logs don't cut it as Greg Young has put forth in his presentations.

That's great for the state of each of the objects. With a bit of work, you can work out anything that might have happened on any reporting that you may need or needed. This may be quite a task to merge your structure and lookup data changes in the reporting db and the actual events that update that data.

To add, the more difficult task is to have the reporting service changes merged as well. This would not be so bad if the Open Closed Principle (OCP) was adhered to - but only for when new events were added. If the reporting service started to act differently as of some arbitrary version, a careful replay of the system through time would be needed with binaries ready for each timespan that they were around for to receive those events.

There is the very explicit Temporal Object Pattern that seems tempting to use. However unless the domain is clearly responsible for knowing it's own contracts, this looks like a very complex solution to put forth as we will want all our objects to be temporal - and our reporting data as well. This is just a wish to have a rewind button on my app to have a truly representative audit.

So now that we've described the problem, let's take a look at a possible solution: Event Sourcing. As stated earlier, this allows us to capture events that the domain publishes and add the ability to reconstruct an objects state using just what has been published in the past. A very good implementation of this pattern was thought up by Greg Young. Martin Fowler writes about it here.

That solves the problem of the rewind button working on the objects themselves. How can we do the same for the rest: reporting services and the reporting schema? The trick is that the state of the objects is write-only via the events they have published so far. To have the same effect for the Reporting Service, we need to adhere to the OCP and publish an event called "CustomerReportService2 will now listen to all CustomerEvents from now on". We keep both versions of the service alive. We can also say that another message is now going to be consumed by the second version from now on at a later point in time.

This simple fact that we are closing the old service functionality from that point on in the event stream is key. It also gives us the same thing for the dB schema. When we add a table or column or lookup item, etc., we publish the schema change as an event. When rewinding the clock, we drop all the tables from the dB and simply replay all events from the beginning of time through the latest binaries. This may take some time, so some snap shot concept can be introduced as is with Greg's Aggregate Root object snapshot idea.

If we adhered to the OCP, the reporting service and schema will have been used as it has before. The slight overhead is the management of which version of the reporting logic is used when. It listens for when to switch directing of the messages to a particular version of the service. Sounds like a job for an Inversion of Control (IoC) Contianer?!

So.. It always comes back to this: GIT hooks that prevent you from changing certain files in certain ways! The db script can only be added to! The reporting service cannot be changed - just ammended! OCP insurance from your source code management is a cool concept.

That's the best accounting system for software I can think of.

Looking forward to critiques of this and questions for clarification since I offered no diagrams. I probably will update this with that info later.


Anonymous said...

Would love to see some example code backing all this theory...

I recently started implementing an example based on Greg's presentation and whatever I've read here and there, and I quickly run into a myriad problems that born a myriad more questions.

Adam said...

I would love to do that. I have about a million things on the go, but this is important to me. I've used Version Scripts for the data base before. This has the same idea and may do away with them - at least in their current incarnation.