I'm using the IPostUpdateEventListener
interface to do update audit logging now, grabbing the old and new values, then storing each updated field in an "Audit" table and all that jive. Works swell, but there's two last requirements I'm having a hard time fullfilling:
- Display which employee the update was for.
- Display the "friendly" name of the field updated.
For #1, my first instinct was to use reflection and look for & grab the "Employee" property on the given entity to find out which Employee it was for, but that quickly falls apart when you're a few objects deep in the graph with no automatic way to get back to the given Employee object.
Ideas to solve #1 ranged from requiring a "Parent" property on every object so I can traverse the graph looking for the Employee type (which, to me, would pollute our domain too much for a simple persistance concern) to using a separate SQL job to tranverse the foriegn keys and fill in the Employee ID after the fact (I'd rather not maintain a separate SQL job as everthing is code based thus far - and that SQL job will get quite nasty very quick).
As for the second requirement, I can get the actual property name that changed just fine. For a good 80% - 90% of our fields, the (properly formatted) property name is what we display, so I can just space the name based on the Pascal casing. The rest of the fields, however, don't match up for various reasons. We're using ASP.NET MVC and Fluent HTML builders from MvcContrib, but even if we modified the setup to the point of having an attribute on the view model that overridess what the field name should be (and therefor having it in code instead of just the view), there's no real way to match those attributes from the view models to the domain objects being saved.
A final pragmatic solution to both problems would just be to call an audit logging service after each update operation in another service, passing in the field names and employee information as needed, but, well, I really don't want to go there for obvious reasons.
Ideas for either problem would be greatly appreciated. Searching and racking my brain for a couple of days has turned up nothing of use - most people seem to stop at simple old/new vale recording or just a "created/updated" timestamp on the record itself.
This seems like a more complicated scenario than a trivial audit trail, and for that reason I would lean towards an application service to handle this.
We use an audit service in our application, even though our scenario is much simpler than yours because its a domain concern. Our domain knows about history and works with history, its not created just for some reporting front end.
Are you recording who (Employee) is making the update, or is Employee some sort of aggregate root?
To create a log you can use IPostInsertEventListener or IPostUpdateEventListener. If you are using fluent configuration you has that configuring as in the example bellow.
For #1, I designed the following: In MyCommon assembly I declared
which gets created per request using Ninject. The implementation of this class, simply stores the UserSession object in the ASPNET MVC Session.
With this, I can request the IUserSessionStore throughout all layers and get the UserSession for this class to use and to insert as audit info in every object (below class would be in MyModel project):
So you can adapt this to obtain your Employee information that you need.
Gladly I'd take more suggestions!
I have a requirement similar to yours. In my case, it's a health care application and the audit log needs to identify the patient to which the insert/update applies.
My solution is to define an interface, which all audited classes need to implement :
The audited classes then implement this interface in whatever way is required. For example:
The
IPostInsertEventListener
andIPostUpdateEventListener
then fetch theOwningPatient
property in order to populate the audit record.The solution has the advantages of keeping the auditing logic in the event listeners, which is the only place where one is sure that an insert/update will take place, as well as allowing the traversal of indirect links between an object and its owning patient.
The downside is that the audited classes have to derive from a specific interface. I think the benefits outweigh this small cost.