In my last article, I wrote about how to get ActiveRecord to handle reading and writing data to the database.  Now that we know how to handle that, it’s time to clean up the example.  One problem with my business entity classes is that they had specific knowledge about how they were loaded and saved.  For example, I had methods like this one in my Contact class:

   1:  public static Contact LoadContactById(int contactID)
   2:  {
   3:      return FindOne(Expression.Eq("ContactID", contactID));
   4:  }

The problem with this is that this couples our business objects with our persistence strategy, namely ActiveRecord.  This makes it really difficult to get rid of this down the road if we choose to, violating a design goal of making our software loosely coupled.  If you’ve written software for any period of time, the one thing you can count on is that requirements change.   What seems locked in stone today might need to be completely replaced tomorrow, which is why we need to make design decisions that make changes like this easier.

Another reason to separate our persistence strategy from our business objects is that it just looks cleaner.  You have less code in your objects dealing with how they are loaded and stored, and the code that is left deals with the actual business rules.  How can we accomplish this goal?

The first thing I think of is some sort of repository to do the job.  I’ll make this an interface, that way our options for a persistence strategy are left open.  We’ll start out small, and just give the interface one method to load the business object from it’s primary key.  At this point we have a choice.  We could make a repository for each business object to specialize them, or we could make a generic repository to try and handle all cases.   I’ll go with the latter method and just create a single repository.

   1:  namespace ActiveRecordSample
   2:  {
   3:     public interface IRepository
   4:     {
   5:        T LoadById<T>(int key) where T : class;
   6:     }
   7:  }

For my repository definition, I will create an ActiveRecord repository:

   1:  namespace ActiveRecordSample
   2:  {
   3:     public class ActiveRecordRepository : IRepository 
   4:     {
   5:        #region IRepository Members
   6:        public T LoadById<T>(int key) where T : class
   7:        {
   8:           return ActiveRecordMediator<T>.FindByPrimaryKey(key, true);
   9:        }
  10:        #endregion
  11:     }
  12:  }

I had to add the type constraint to my method in the interface because ActiveRecordMediator<T> requires T to be a reference type.   This methodology allows me to pull out all of the persistence methods from my business objects, and I will be able to load any of my business objects by their ID without any additional code. 

   1:  // Instantiate the repository.
   2:  ActiveRecordRepository repository = new ActiveRecordRepository();
   3:   
   4:  Order ord = repository.LoadById<Order>(43659);
   5:  Product prod = repository.LoadById<Product>(1);
   6:  Employee emp = repository.LoadById<Employee>(1001);

 

In the above example, I’m loading Orders, Products and Employees using the same repository method.  (for a refresher on the definitions of my business objects, see my previous article)   OK, that’s great…but you might be saying to yourself that we don’t really have much of a repository here.  What if we want to find any order in July of 2001?  Or any user with a last name that starts with A?  How can the repository possibly handle these situations?

Well, this is sort of a trick question.  This can be done fairly easily by adding a method to our interface, making our interface and implementation look like this:

   1:  public interface IRepository
   2:  {
   3:     T LoadById<T>(int key) where T : class;
   4:     IList<T> FindResults<T>(ICriterion[] criteria) where T : class;
   5:  }
   6:   
   7:  public class ActiveRecordRepository : IRepository 
   8:  {
   9:     #region IRepository Members
  10:     public T LoadById<T>(int key) 
  11:              where T : class
  12:     {
  13:        return 
  14:           ActiveRecordMediator<T>.FindByPrimaryKey(key, true);
  15:     }
  16:   
  17:     public IList<T> FindResults<T>(ICriterion[] criteria) 
  18:              where T : class
  19:     {
  20:        return ActiveRecordMediator<T>.FindAll(criteria);
  21:     }
  22:     #endregion
  23:  }

We could then answer the questions I posed above fairly easily like this:

   1:  // Find all employees whose last name starts with 'A'
   2:  expr.ICriterion[] criteria = new expr.ICriterion[]
   3:  {
   4:     expr.Expression.Like("LastName", "A%")
   5:  };
   6:   
   7:  IList<Employee> emps = 
   8:       repository.FindResults<Employee>(criteria);
   9:   
  10:  // Find all the orders in July 2001
  11:  DateTime start = new DateTime(2001, 7, 1);
  12:  DateTime end = new DateTime(2001, 7, 31);
  13:   
  14:  criteria = new expr.ICriterion[]
  15:  {
  16:     expr.Expression.Between("OrderDate", start, end)
  17:  };
  18:   
  19:  IList<Order> ords = 
  20:       repository.FindResults<Order>(criteria);

 

A little note about the above code…..I created an alias to the NHibernate.Expression namespace called expr to avoid namespace naming conflicts.  The problem with this solution is that we’ve just created a leaky abstraction.  We now made our repository depend on the NHibernate .dll because we used the interface NHibernate.Expression.ICriterion as a parameter to our method.  This makes it hard to ever replace NHibernate if the need arises.

There are a few ways we can fix the leaky abstraction.  We could either create a repository for each business object, or we could write a wrapper for the ICriterion parameter that would allow us to abstract the dependency on NHibernate away.  If we choose to create a repository for each business object, the example above would look something like this:

 

   1:  public interface IEmployeeRepository
   2:  {
   3:     Employee LoadById(int id);
   4:     IList<Employee> LoadByLastName(char letter);
   5:  }
   6:   
   7:  public interface IOrderRepository
   8:  {
   9:     Order LoadById(int id);
  10:     IList<Order> LoadByDateRange(DateTime start, DateTime end);
  11:  }
  12:   
  13:  public class AREmployeeRepository : IEmployeeRepository
  14:  {
  15:     #region IEmployeeRepository Members
  16:     public Employee LoadById(int id)
  17:     {
  18:        return 
  19:           ActiveRecordMediator<Employee>.FindByPrimaryKey(id, true);
  20:     }
  21:   
  22:     public IList<Employee> LoadByLastName(char letter)
  23:     {
  24:        ICriterion[] criteria = new ICriterion[]
  25:        {
  26:           Expression.Like("LastName", letter + "%")
  27:        };
  28:   
  29:        return ActiveRecordMediator<Employee>.FindAll(criteria);
  30:     }
  31:     #endregion
  32:  }
  33:   
  34:  public class AROrderRepository : IOrderRepository
  35:  {
  36:     #region IOrderRepository Members
  37:     public Order LoadById(int id)
  38:     {
  39:        return 
  40:           ActiveRecordMediator<Order>.FindByPrimaryKey(id, true);
  41:     }
  42:   
  43:     public IList<Order> LoadByDateRange(DateTime s, DateTime e)
  44:     {
  45:        ICriterion[] criteria = new ICriterion[]
  46:        {
  47:           Expression.Between("OrderDate", s, e)
  48:        };
  49:   
  50:        return ActiveRecordMediator<Order>.FindAll(criteria);
  51:     }
  52:     #endregion
  53:  }

Notice how our interfaces no longer depend on NHibernate, so loose coupling is achieved.  The downside to this approach is that there are more interfaces to maintain, and you need to create a method for every operation that you want to support.  This is an example of how these repositories could be used:

   1:  AREmployeeRepository employeeRepository =
   2:      new AREmployeeRepository();
   3:   
   4:  Employee emp = 
   5:     employeeRepository.LoadById(1001);
   6:   
   7:  IList<Employee> emps = 
   8:     employeeRepository.LoadByLastName('A');
   9:   
  10:  AROrderRepository orderRepository = 
  11:     new AROrderRepository();
  12:   
  13:  Order ord = orderRepository.LoadById(43659);
  14:   
  15:  DateTime start = new DateTime(2001, 7, 1);
  16:  DateTime end = new DateTime(2001, 7, 31);
  17:  IList<Order> orders = 
  18:     orderRepository.LoadByDateRange(start, end);

The benefit to this that we achieve loose coupling, which allows me to completely replace NHibernate and / or ActiveRecord if I ever want a different persistence solution.  I should point out, we would still need to modify our business entities we wanted to completely replace our persistence solution, since all of our classes are using ActiveRecord attribute decoration….but this would hopefully be a relatively painless task.  We could also introduce a dependency injection framework to further promote loose coupling. (Ninject comes immediately to mind as a great choice here)  This would allow us to bind our repository at run time and not have to instantiate a specific implementation of our repositories every time we want to use them.  

kick it on DotNetKicks.com