Auditing Part 2 - From one to many, in the blink of an eye!

Before I start, I'd like to think my colleagues Alex Robson and Craig Israel for helping with the design. It'd have ended up containing 200% more suck without their assistance. Also, the finished product is part of Nvigorate, so whenever Alex updates that project, it'll be there. What is currently up there however is very out of date. It's improved greatly.

Edit: Craig Israel dropped a good point to me - by using the new keyword in my derived classes, classes using the base class won't actually get the sub-classed version of my function! So, I've made the modifications needed below. If you hadn't read the blog yet, then just ignore this line.

So, hey, I actually am writing part 2! I'm as shocked as you are! If you ended up here first, feel free to check out the first part, where I introduced generics to this solution.

When we left off there, we had several things non-optimal about our solution. In fact, it was damn near unusable if you ask me. Here our current list of things to unsuckify:

  • Handling collections - right now, everything is running against a single instance. This is a limitation we'll remove.
  • What if we want multiple auditors for a single type? We'll have to add more and more TrackAction calls.
  • Firing our auditor - it's in the middle of our functions. That adds a bit of code noise, and can make it harder to track down.

So, let's start at the top!

Gotta collect 'em all!

Why do we care about collections? Many swanky ORM solutions allow you to have a whole buncha objects, and save them at once, instead of looping through them. So, we need to account for that. Lets refresh our head goo and see what our interface looks like again:

   1: public interface IAuditor<T>
   2: {
   3:     void TrackAction(AuditAction action, string user, T information, DateTime? when);
   4: }
   5:  

 

So, what needs to change? Let's think about what actually IS changing here. Do we still the action, user, and time stamp? Yes. Do we still need the data? Yes. Is our data type still unknown to us? Hmmm...not so much. We know we're going to be getting a collection, which is important to us, but we still don't care what it' a collection of. Well, what do all collections (Lists, Array, etc.) have in common? That's right! IEnumerable. Congratulations, I'm not going to berate you in the face with mean words today. So, we want a collection of generic objects. That's easy! IEnumerable<T>.

Since all that's changed is our information, and the rest of the function is the same, I think it's time for our old pal polymorphism to help us out! He's like grandfather time, except his beard is way more gnarly. So let's add another function to our interface:

   1: public interface IAuditor<T>
   2: {
   3:     void TrackAction(AuditAction action, string user, T information, DateTime? when);
   4:     
   5:     void TrackAction(AuditAction action, string user, IEnumerable<T> information, DateTime? when);
   6: }
   7:  

 

Well....that kinda sucks. Now, our interface requires two functions to be implemented, wether or not the consumer actually cares about auditing collections as a whole. You know, we should be nice, and give a default implementation for the collection based one. But, you can't give function bodies in an interface. Looks like we're going to have to upgrade to an abstract class. Then, we can make our collection based function loop through and call the instance based one over and over and over. That way, if someone WANTS to handle collections separately, they can, by simply overriding it. And since we're switching from an interface to an abstract class, we'll change our naming a bit too, to match convention. How's this look?

   1: public abstract class AuditorBase<T>
   2: {
   3:     public abstract void TrackAction(AuditAction action, string user, T information, DateTime? when);
   4:  
   5:     public virtual void TrackAction(AuditAction action, string user, IEnumerable<T> information, DateTime? when)
   6:     {
   7:         foreach (T info in information)
   8:         {
   9:             TrackAction(action, user, info, when);
  10:         }
  11:     }
  12: }

 

Why, I think that looks good! We're gonna mark the collection-based TrackAction as virtual so we can override it in subclasses.

Come together now

So, let's see how this changes our implementation? Good news - barely! All we have to do is change where it inherits from, and add the override keyword for our single-instance version of TrackAction. Then, optionally, we can override the collection based TrackAction if we want. Since we defined a body for the collection based one, however, we'll need to use the "override" keyword on the function declaration. Let's do that too, just for fun.

   1: public class MyFirstTypeAuditor : AuditorBase<MyFirstType>
   2: {
   3:     public void TrackAction(AuditAction action, string user, MyFirstType information, DateTime? when)
   4:     {
   5:         MyDataLayer.WriteData(new AuditLog()
   6:             {
   7:                 Action = action,User = user,
   8:                 Information = string.Format("<fields><field1>{0}</field1><field2>{1}</field2></fields>", 
   9:                     information.Field1,
  10:                     information.Field2),
  11:                 Date = when ?? DateTime.Now
  12:             });
  13:     }
  14:  
  15:     public override void TrackAction(AuditAction action, string user, IEnumerable<MyFirstType> information, DateTime? when)
  16:     {
  17:         MyDataLayer.WriteData(new AuditLog()
  18:             {
  19:                 Action = action,User = user,
  20:                 Information = "<message>Beginning auditing a collection</message>",
  21:                 Date = when ?? DateTime.Now
  22:             });
  23:  
  24:         base.TrackAction(action, user, information, when);
  25:     }
  26: }

You can see we still let the base class do our actual looping over each instance, but if we wanted to do something special, we'd just omit the call to base.TrackAction(.....). And calling TrackAction from our business code won't change at all! Whether it's an instance or collection, we're good.

It stinks!

Shut up. I know. Today's post was pretty simple, but that's because the next step is a doozy! It's going to wash the stink right out of your mouth! What's it going to be? Well, let's take a look at our Bulleted List O' Crap:

  • What if we want multiple auditors for a single type? We'll have to add more and more TrackAction calls.
  • Firing our auditor - it's in the middle of our functions. That adds a bit of code noise, and can make it harder to track down.

    That's right, it's going to be that first one there. We're going to go for a config-driven approach to defining how our Auditors are loaded. We're going to make something we'll call our AuditManager to read the config to find out what auditors (yes, multiple!) need to be loaded per type of object. It's gonna be a good ole time down on the farm!

  • 0 comments:

    Designed by Posicionamiento Web | Bloggerized by GosuBlogger