Change Tracking

Change Tracking

Old forum URL: forums.lhotka.net/forums/t/2176.aspx


SonOfPirate posted on Friday, January 19, 2007

I need to implement a change log/history for some of the BO's in my application and am wondering about the different approaches I could take.

First, a little background.  At this time I have 3 BO's in my application that need change tracking implemented.  This may increase as additional features are added to the application in subsequent versions.  At a minimum, we want to track object creation, deletion and state changes (the app has a workflow engine that the BO's pass through).  We may also want to track changes to key properties in the object.  For instance, if a Customer's Address is changed, for tracability and accountability purposes we need to record the change (with the date and the user that made the change).

I know this sounds like a straight-forward audit trail, but I think we are looking for more flexibility than would by typical for an audit trail.  I may be wrong, but you set up an audit trail to log specific events on a more global level while here I need to be able to vary what events (changes) are logged and may even have to implement logic to determine if the change warrants logging.  For example, we may decide that price changes to a Quote less than $50 do not merit logging but any that are higher do get recorded.

So, my original approach was going to utilize triggers in the SQL Server database that would add the appropriate record into the associated history table.  But, I have never had real good luck with triggers in the past and have only been reading reasons why not to go that route - data portability being a huge point. (We may find ourselves switching to Oracle - or some other DB - in the near future.)

Next, I considered hard-coding directly into the Insert, Delete and Update sprocs.  But, I don't see an easy way to implement the flexibility I need such as turning on and off what properties we track and changes to the threshold values.  Plus, detecting what properties changed would require a lookup and compare routine within the sproc - more overhead than I'm willing to impose.

I then started considering the Observer pattern and having a CustomerAuditor (or some other name) object that listened for changes to the Customer objects and added the appropriate records.  To do this, I would have to implement a set of events for the Auditor to trap such as CustomerAdded, CustomerUpdated and CustomerDeleted.  Afterall, we don't want to record the changes until they are committed.

Having the Observer listen to a parent collection would be the cleanest way to go, but we won't always have a collection to work with - e.g. when we instantiate the BO directly.  So, there's some logistics that have to be worked out as far as getting the Observer and BO "talking" to each other.  I figure having a factory to instantiate the object would provide me the easiest way to "attach" the Observer.

Finally, I started thinking that this may be better suited as a part of the BO itself.  Much like the AuthorizationRules and ValidationRules properties/collections/operations, I could extend the objects to have an AuditRules collection and associated methods.  Every time a property is changed we would use the AuditRules collection to determine if we are to track that property and, if so, add the change to the list of pending changes.  Then, when the BO is committed, we would also commit the audit record.

My issues with the last approach, which seems like the best at this point, is that this will work great for property change tracking (which would include state tracking) but I would still need a way to track object creation and deletion - of course, maybe these could simply be rules associated with the IsNew and IsDeleted properties...

In addition, committing the audit record in the same trip to the database as the BO's operation would be preferred, so I need some approach to wrapping the event information into the SQL call.  I am thinking it could be done as a string parameter with all of the changes concatenated together, I guess.

In fact, perhaps a combination of approaches would work the best.  Since I have separate procedures for the Insert, Delete and Update operations, I could embed the necessary SQL within those procedures to add the necessary audit record without involving the BO.  In other words, I would add a record indicating the object was created in the Insert sproc and likewise for the Delete sproc.  Then, add the additional parameter to the Update sproc to record the properties that had been changed by the update.  But is it wise to mix approaches like this?

I know this is a lot to throw out there and only hope that this instigates a healthy discussion on how some of you have already approach this and the pro's & con's of each.

Thanks in advance.

 

ajj3085 replied on Friday, January 19, 2007

Pirate,

I would do full row level auditing on the tables your interested in.  I know that you don't necessarly want to log everything, but building complex rules to audit only certain pieces of data or only at certain times will just make your development more complex and harder to maintain.

To generate the data, I would use the update, delete and insert procs to copy row state into the audit table (so Person would have a PersonAudit, Phone would have PhoneAudit).  Maybe create a central Audit table so that the various xxxAudit tables can be liked to a single transaction.

Instead, I would focus on building business objects that can get the audit details in which the users are interested.  Anything else can be filtered by the object automatically and just present only the data requested. 

The problem is that a BO shouldn't be responsbile for doing its main job AND generating an audit trail.  I think that violates the SRP, and creating copies of data is something databases are really good at, so let it do it.

Your other options are much more complex, and will likely require you to make alot of changes as the audit data needed by users changes.

SonOfPirate replied on Monday, January 22, 2007

ajj3085:
I would do full row level auditing on the tables your interested in.  I know that you don't necessarly want to log everything, but building complex rules to audit only certain pieces of data or only at certain times will just make your development more complex and harder to maintain.

To generate the data, I would use the update, delete and insert procs to copy row state into the audit table (so Person would have a PersonAudit, Phone would have PhoneAudit).  Maybe create a central Audit table so that the various xxxAudit tables can be liked to a single transaction.

Instead, I would focus on building business objects that can get the audit details in which the users are interested.  Anything else can be filtered by the object automatically and just present only the data requested. 

So, you are saying that I should log everything and apply a filter on the viewing end of things?  I understand the concept, but isn't the idea of defining audit levels and criteria to reduce the impact logging has on performance, space, etc.?

 

ajj3085 replied on Monday, January 22, 2007

Performance shouldn't be an issue, espeically if you define indexes properly. 

As for space, that's true, my suggestion will cause space to be filled as the database is modified.  You can limit this though by limiting the auditing to only certain tables. 

If you feel the need for more specific audits, then you would likely need the help of a BO though.  But auditing to me is different than logging; an audit in my mind is reliable and detailed, a log is not and is usually configurable.  Maybe that's the disconnect I'm having with my suggestion for you...

Brian Criswell replied on Friday, January 19, 2007

I would generally stay away from triggers as they you would struggle with the impedence mismatch problem.  Personally, I would lean toward just putting your checks in the DataPortal_XYZ methods.  Check your rules (which could be dynamic) to see whether the change needs to be audited, and then make a second stored procedure call which contains the audit information  and occurs within the same transaction as your actual changes.

Jimbo replied on Friday, January 19, 2007

Within a transaction, it is often prefered to log the audit data first and if all is happy then apply the actual changes. This allows you to do data reads and  inserts that may not be as easy after the update.. particularty if there are physical deletes and you are not  accessing the  updated and deleted tables  as you could in an after trigger.

DesNolan replied on Monday, January 22, 2007

Nothing wrong with triggers at all.

I currently work on a custom ERP system where they are used for just about every table. Data is copied to local shadow tables which contain no indexes, user rules, RI, or anything like that, and where all fields may be null. Data from those tables is copied out to the permanent Shadow Archive database every night. This all works, because the ShadowArchive is only used for investigative inquiries and does not need to be as performant as the as the main system. All in all, we get a complete history of changes made to each table. (Each trigger adds a few fields to the shadow tables to record who's making a change, the type of change, and when it was made.)

Cheers,

Des Nolan


SonOfPirate replied on Tuesday, January 23, 2007

I guess the knock (at least imo) on triggers has to do with portability of the system.  You can obviously use triggers with SQL Server and Oracle, but beyond that there is no guarantee that the approach will work.

Andy, I guess you are right that there is not only a semantical difference between auditing and logging but there really is a functional difference as well.  But, I think my definition is backwards from yours.

To me, logging is something that is either enabled or not and is used for diagnostic, maintenance or administrative-type purposes.  A good example of what I consider a log is a web site that tracks a visitor as they navigate from page to page.  Each page hit is recorded in the log.  The same information is recorded each time and the data can be later massaged and manipulated as desired for analysis.  It is automatic, consistent and is either on or off.

Auditing is something that is event-driven by nature (as is logging, I guess) but is based on the occurrance of a specific event - or set of conditions - and can be triggered by one or the other boolean state of those conditions.  For instance, recording each time there is a failed login attempt in the application.  I see auditing as more of a security, accountability, tracability (quality) tool.

Even as I explain it, the lines are blurred.  Maybe it is just semantics, I don't know.  But I still haven't completely solved my situation yet athough I have implemented a short-term solution to keep my efforts moving forward.

At this point, I am using a BO because I think that may be the direction I take this as I implement a more comprehensive model.  The BO is a child of the object being audited and is created in response to a particular event.  When the parent is persisted, the audit/log entry is as well.  Because the BO is a child object (part of a child collection), I am able to easily retrieve the entries from the data store and display the parent's "history" on the page.

Down the line I am thinking of extracting this from the BO and having a collaborative Auditor object using the Observer pattern.  I would "register" the object(s) to be audited which would allow the auditor to "hook into" the object's events, such as PropertyChanged.  Somewhere/somehow there'd be a list of what is being audited so the Auditor could filter the events...........

Anyway, my thought process is relatively new and pretty rough.  My short-term fix gives me what I need for now, so I suppose that's good enough, eh?

 

ajj3085 replied on Wednesday, January 24, 2007

Pirate, yes, I agree logging is usually used for diagnostic purposes as well.  The lines are blurry.

Since you do need to have these rules, perhaps what you can do is use the Proxy pattern to wrap each business object you need to audit.  The proxy object can handle the auditng aspect by building up a change list, and your original BO will not need to be modified at all. 

Just a thought.  I think that'd be a good seperation of responsiblities.

SonOfPirate replied on Wednesday, January 24, 2007

I'm not as familiar with the Proxy pattern, but I will certainly look into it as it sounds promising.  Thanks.

ajj3085 replied on Wednesday, January 24, 2007

The basic premise is that you create a class which looks just like the BO your are creating a proxy for and the proxy keeps a reference to the BO, much like the Decorator pattern.  In the end the proxy just routes calls to the BO, but it may also do other tasks, such as security checks, RPC, or in your case it create audit data.

Check out the Head First Design Patterns book, its a really good read.  It uses Java as the language of choice, but the concepts applies nicely to .Net as well.

SonOfPirate replied on Wednesday, January 24, 2007

Too funny, I just started reading it a short time ago!

ajj3085 replied on Wednesday, January 24, 2007

Well I found it to be well worth the time.  Maybe you should skip a few chapters ahead. Smile [:)]

glenntoy replied on Wednesday, January 24, 2007

You might want to consider to read this thread http://forums.lhotka.net/forums/thread/10206.aspx.

Although the thread was more on object level audit trail, maybe it could give you an idea.

SonOfPirate replied on Wednesday, January 24, 2007

Thanks for the reference.  I'm pretty much on the same page with the exception that we also track "state" changes, meaning an entry when the object itself is created and/or deleted.  Other than that, the example for tracking property changes pretty much reflects what I am trying to accomplish.  Having started down this path, however, I am leaning towards something a bit more powerful, flexible and... "generic".  I haven't rules out the AuditRules idea yet, but I like the Observer or Proxy pattern ideas as well.  I would like something that is there if I want it and doesn't consume memory or processor time if not - much like the way ValidationRules are setup.  So, we'll see.

 

ajj3085 replied on Wednesday, January 24, 2007

I think either pattern will give you what you want; the proxy or observer could listen for a flag and just not do anything (well, the proxy would still route the call) if the auditing is set to off.

Copyright (c) Marimer LLC