Thursday, August 13, 2009

IoC Doesn't Have to Break your Build!

If you're not familiar with dependency injection or inversion of control, check out this older post by Jeremy Miller.

There are a million reasons why these patterns make sense and help you build better code. Many of the new guidance and patterns depend on, well, dependency injection (take the Composite Application Guidance AKA PRISM for example). One major object I've seen and heard is "it's great, but I can't use it ... it's going to break my build!"

The main reason for this is the fact that inversion of control depends to a large extent on constructor injection. Take for example your customer data access class that used to look like this:

public class CustomerDataAccess
{
   public void SaveCustomer(CustomerEntity customer) 
   {
      ConsoleLogger logger = new ConsoleLogger();
      SQLDataAccessLayer layer = new SQLDataAccessLayer(); 
      layer.Save(customer); 
      logger.Log("Saved the customer."); 
   }
}

After kicking it around for awhile you realized your logger, which used tracing, stopped working in the web project and one of your customers wanted the backend to be MySQL instead of SQL. Your class evolved to something like this:

public class CustomerDataAccess
{
   public void SaveCustomer(CustomerEntity customer) 
   {
      ILogger logger = LoggerFactory.GetLogger();
      IDataAccessLayer layer = DataAccessFactory.GetDataAccess();
      layer.Save(customer); 
      logger.Log("Saved the customer."); 
   }
}

It was easy because in your factory, you simply checked a few app.config settings and returned the appropriate instance.

Now DI and IoC come along and you want to plug in the Unit Framework. Only that means you are going to something like this:

public class CustomerDataAccess
{
   private ILogger _logger;
   private IDataAccessLayer _layer; 

   public CustomerDataAccess(ILogger logger, IDataAccessLayer _layer)
   {
      _layer = layer;
      _logger = logger;
   }

   public void SaveCustomer(CustomerEntity customer) 
   {
      _layer.Save(customer); 
      _logger.Log("Saved the customer."); 
   }
}

Suddenly, the 5000 places you make a data access layer break. You know you are going to go through and start getting an ICustomerDataAccess by called the inversion of control container, but do you really have to hold open the release and do it all at once?

The answer is, of course, no. While this may seem painfully obvious to some people, sometimes developers can't see the forest to the trees. They get too close to the code and thing it's all or nothing. Instead, you can simply do this:

...
public CustomerDataAccess() : this(LoggerFactory.GetLogger(), DataAccessFactory.GetDataAccess()) 
...

Now you've got your backwards-compatible behavior. You can then go into your factories and mark your methods as obsolete, and you'll receive compiler warnings where the old method is being used. Use this as your "to do" list and start wiring them up using the container instead!

Jeremy Likness