views:

499

answers:

2

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:

  1. Display which employee the update was for.
  2. 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.

A: 

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.

  1. Its much more testable.
  2. Its explicit.
  3. You don't have to resort to reflection or common properties.

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?

Sneal
Employee is an aggregate root. We have a simple security related "user" making the change. Auditing is simply for record keeping and reporting in our system, though, and we never do anything with it, which is why I was hesitant to move it into a service. I had a feeling from the start of this that's where it'd have to end up due to technical reasons, unfortunately.
Darrell Mozingo
if you are using NHibernate as ORM, i am interested in how you are populating your history table unless you are using trigger.cheers
ashik_gurung
+3  A: 

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 :

public interface IAuditedRecord
{
    IPatient OwningPatient { get; }

    ...
    // Other audit-related properties (user, timestamp)
}

The audited classes then implement this interface in whatever way is required. For example:

public class Medication : IAuditedRecord
{
    // One end of a bidirectional association. Populated by NHibernate.
    private IPatient _patient;

    IPatient OwningPatient { get { return _patient; } }
}

public class MedicationNote : IAuditedRecord
{
    // One end of a bidirectional association. Populated by NHibernate.
    private Medication _medication;

    IPatient OwningPatient { get { return _medication.OwningPatient; } }
}

The IPostInsertEventListener and IPostUpdateEventListener then fetch the OwningPatient 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.

Paul Lalonde
Interesting. I used a similar approach not long ago, except instead of a getter I had a setter on the IEntity interface, which each POCO implemented. I injected User on the constructor of the repository base class, and when a persist(IEntity poco) action was called on any repository class, it would call the setter on the persistent entity (we stored the last user to touch the record on the record itself). This allowed us to ignore event listeners altogether.The nice thing about your approach, however, is that you can pick and choose which classes should be audited.
Nelson
Funny, that's pretty much what I ended up doing. I defined an "EmployeeIdForAuditing" property (only getter) on our IDomainEntity, then had it return null in the base DomainEntity class. We also had a requirement to know whether each entry was an Employee/Company level "edit", so we created an audit attribute with the information. Any object that was an IDomainEntity and tagged with the Employee level audit attribute is required to implement the property. This also allowed me to write a test to check all the objects in our project for those requirements. Thanks for posting this!
Darrell Mozingo