Apr 17 2010

Dependency Injection Flavors - The Good, The Bad and the Ugly

Category: Software DevelopmentJeff @ 08:19

UPDATE: check out the code at http://codepaste.net/t4z78p if the stuff below isn't formatted well for your browser.

using System;
using NUnit.Framework;

namespace DependencyInjectionApproaches
{    
    /// <summary>
    /// There are multiple flavors of Dependency Injection - 
    /// 
    /// 1) Constructor injection - dependencies are passed in as constructor parameters
    /// 2) Setter injection - dependencies are passed in to setter methods
    /// 3) Service locator - dependencies are registered with a static gateway which serves up 
    ///     the dependencies (not really dependency *injection* per se, but let's not be sticklers)
    /// 4) PMDI - Poor Man's Dependency Injection (more to come on this one later)
    /// 
    /// I've found that Setter Injection (SI) and Service Locator (SL) have an inherent problem 
    /// because they hide dependencies.  If my class-under-test uses SI or SL, then I often have 
    /// to inspect the source of the class to understand which dependencies must be wired up for 
    /// testing the class.  With Constructor Injection (CI), all of the dependencies are right 
    /// there in the constructor.  There are quite a few other issues related to this matter
    /// which I will discuss below in the context of demonstrating each flavor of dependency 
    /// injection.
    /// 
    /// I've defined an interface "IService" and with multiple implementations.  Each concrete
    /// implementation depends on "ILogger". This dependency is resolved in different ways
    /// for each concrete implementation of IService.
    /// </summary>
    [TestFixture]
    public class DependencyInjectionSpecs
    {
        [Test]
        public void using_constructor_injection()
        {
            var testLogger = new TestLogger(); // Moq would be better, but trying to focus on DI here
            var sut = new ConstructorDependentService(testLogger); 
            
            sut.DoWork();
                        
            testLogger.LastMessage().ShouldEqual("ConstructorDependentService did work");
            
            // (*) when I build the System-Under-Test (sut) see how I can't even call the constructor 
            // without the dependency?  that's good.
            // also notice that I'm only testing my System-Under-Test (SUT) in its interaction with
            // a single dependency.  Good unit tests usually look like this. They only test one class 
            // in its interactions with one abstraction of a dependency.
        }
        
        [Test]
        public void forgetting_to_wire_up_dependency_in_setter()
        {
            var sut = new SetterDependentService();
            
            sut.DoWork(); // throws a NullReferenceException        
            
            // Oops.  I'm able to create my SUT, but I can't execute the DoWork method on it.  Why not?
        }
        
        [Test]
        public void remembering_to_wire_up_dependency_in_setter()
        {
            var testLogger = new TestLogger();            
            var sut = new SetterDependentService();
            sut.Logger = testLogger; // Note this extra requirement for the test to work (*)
            
            sut.DoWork(); 
            
            testLogger.LastMessage().ShouldEqual("SetterDependentService did work");
            
            // (*) That's better than the last test, but more work than the first one.
            // I can still make this test pass, but there's an extra setup cost.  Note that the 
            // dependency is hard to see immediately (we missed it in the previous test).
            // With constructor injection I get a compliation error if I don't set up the dependency.  
            // With setter injection, the missed dependency is not discovered until run time.  Oops.
        }
        
        [Test]
        public void forgetting_to_wire_up_dependency_in_service_locator()
        {
            var sut = new ServiceLocatorDependentService(); // Crud! - KeyNotFoundException (*)
            
            // (*) The test throws immediately due to lack of service registration. Even worse, the 
            // dependency is completely opaque. At least with setter injection I could inspect the 
            // SUT with intellisense, now I'm totally hosed.  I have to look at the implementation 
            // of ServiceLocatorDependentService to figure out how to test it (or if I can at all).
        }
        
        [Test]
        public void remembering_to_wire_up_dependency_in_service_locator()
        {
            var testLogger = new TestLogger();
            ServiceLocator.Configure<ILogger>(() => testLogger); // additional complexity - see below (*)
            var sut = new ServiceLocatorDependentService();
            
            sut.DoWork(); 
            
            testLogger.LastMessage().ShouldEqual("ServiceLocatorDependentService did work");
                        
            // (*)  I'm no longer testing my System-Under-Test in isolation with its dependency, 
            // but I'm also testing my ability to register and resolve dependencies with my Service Locator.
            // This gets worse when my SUT has multiple dependencies which must be resolved from the 
            // Service Locator.
        }        
        
        // PMDI = "Poor Man's Dependency Injection"
        // PMDI may seem like a nice compromise.  You get a default parameterless constructor which uses
        // default implementations of dependencies.  The trouble arises when you want to modify the 
        // constructor of one of *those* dependencies.  What if DefaultLogger changes to require a 
        // constructor parameter? Now changes ripple to all classes that use PMDI with DefaultLogger.
        
        [Test]
        public void PMDI_is_tempting_you()
        {
            var sut = new PoorMansDependencyInjectedService();
            
            sut.DoWork();                
            
            // There is nothing to assert, nothing I can test.  I could visually check my console output, 
            // but doesn't that defeat the whole purpose of an *automated* unit test?
        }
        
        [Test]
        public void PMDI_wants_you_to_come_to_the_dark_side()
        {
            var testLogger = new TestLogger();
            var sut = new PoorMansDependencyInjectedService(testLogger);
            
            sut.DoWork();
                
            testLogger.LastMessage().ShouldEqual("PoorMansDependencyInjectedService did work");

            // yes, it passes.  and with the *same* number of lines as the first example with 
            // constructor injection.  but consider the costs.
            
            // 1) DefaultLogger might change to have constructor arguments (rippling problems, 
            //    but at least caught at compile time)
            // 2) DefaultLogger might change to have setter injected dependencies (now you're 
            //    really hosed! runtime exception!)
            // 3) this is really an SRP violation (Single Responsibility Principle).  Your class 
            //    is not only doing its own job, but is also tasked with determining its appropriate 
            //    defaults.  I suggest you centralize this code in an application bootstrapper and 
            //    find a better way to resolve default implementations (incidendally, StructureMap
            //     has some spectacular abilities in this regard).

            // my last word on PMDI:
            // it seems to me that PMDI is just an attempt to avoid teaching Dependency Injection 
            // to people. I think we're better off showing people all the options and explaining 
            // the pros/cons of each. Perhaps even PMDI has it's place, but it shouldn't just be 
            // the knee-jerk default.  For a shining example of the abuse of PMDI, check out 
            // many of the samples Microsoft released for ASP.NET MVC 1
        }
                    
        #region test support code not relevant to the discussion of dependency injection
        
        [TearDown]
        public void after_each_test_method_executes()
        {
            // make sure we have a clean service locator after each test run.
            // otherwise a registered service from a previous test run can affect
            // the results of other tests, causing false positives or false failures
            // (both really, really bad).
            ServiceLocator.Reset(); 
        }        
        
        #endregion        
    }    
    
    #region more test support code not relevant to the discussion of dependency injection

    public static class SpecificationExtensions
    {
        public static void ShouldEqual(this string actual, string expected)
        {
            Assert.AreEqual(expected, actual);
        }
        
        // check out the NUnit.Specs project for a library of fluent wrappers
        // for NUnit assertions:
        // http://nunitspecs.codeplex.com/
        // I think they are much more clear and concise than the out-of-the-box
        // assertion syntax of NUnit.
    }
    

    #endregion        

    public interface ILogger
    {
        void Log(string message);
    }
    
    public class DefaultLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }
    
    /// <summary>
    /// Yes!!! - I would much rather use a Mocking framework like Moq, but I
    /// don't want to muddy the waters since the focus here is on learning
    /// dependency injection.  
    /// </summary>
    public class TestLogger : ILogger
    {
        string _lastMessage;
        
        public void Log(string message)
        {
            _lastMessage = message;
        }
        
        public string LastMessage()
        {
            return _lastMessage;
        }
    }
    
    /// <summary>
    /// I hate xml comments, but in this context they just might help you.  :)
    /// </summary>
    public interface IService
    {
        void DoWork();
    }
    
    /// <summary>
    /// Implementation of IService using Constructor Injection
    /// </summary>
    public class ConstructorDependentService : IService
    {
        ILogger _logger;
        
        public ConstructorDependentService(ILogger logger)
        {
            _logger = logger;
        }
        
        public void DoWork()
        {
            _logger.Log("ConstructorDependentService did work");
        }
    }

    /// <summary>
    /// Implementation of IService using Setter Injection
    /// </summary>
    public class SetterDependentService : IService
    {
        ILogger _logger;
        
        public SetterDependentService() { }
        
        public ILogger Logger { internal get { return _logger; } set { _logger = value; } }
        
        public void DoWork()
        {
            _logger.Log("SetterDependentService did work");
        }
    }
    
    /// <summary>
    /// Implementation of IService using Service Locator
    /// </summary>
    public class ServiceLocatorDependentService : IService
    {
        ILogger _logger;
        
        public ServiceLocatorDependentService()
        {        
            _logger = ServiceLocator.GetInstance<ILogger>();
        }        
        
        public void DoWork()
        {
            _logger.Log("ServiceLocatorDependentService did work");
        }
    }
    
    /// <summary>
    /// Bonus! Implementation of IService using Poor Man's Dependency Injection
    /// </summary>
    public class PoorMansDependencyInjectedService : IService
    {
        ILogger _logger;
        
        public PoorMansDependencyInjectedService() : this(new DefaultLogger())    { }
        
        public PoorMansDependencyInjectedService(ILogger logger)
        {
            _logger = logger;
        }
        
        public void DoWork()
        {
            _logger.Log("PoorMansDependencyInjectedService did work");
        }
    }
    
    /// <summary>
    /// NOTE: this is a terrible implementation of a service locator the only purpose of which
    /// is to demonstrate the pitfalls of overreliance on ServiceLocation in your classes.
    /// *IF* you are going to do service location, you're better off using the implementation
    /// from a Dependency Injection framework (like StructureMap's "ObjectFactory").
    /// You might also consider the CommonServiceLocator project:
    /// http://commonservicelocator.codeplex.com/
    /// In general, however, I recommend you steer clear of service locator until you grok
    /// dependency injection enough that you are using constructor injection the majority of 
    /// the time.
    /// </summary>    
    public static class ServiceLocator
    {
        static readonly System.Collections.Generic.Dictionary<Type, Func<object>> Configuration
            = new System.Collections.Generic.Dictionary<Type, Func<object>>();
        
        public static void Configure<TService>(Func<TService> factory)
        {
            Configuration.Add(typeof(TService), () => factory());
        }
        
        public static TService GetInstance<TService>()
        {
            var factory = Configuration[typeof(TService)];
            return (TService)factory();
        }
        
        public static void Reset()
        {
            Configuration.Clear();
        }
    }
}

Tags: , , , ,

Nov 22 2008

Dependency Injection for the Compact Framework, Part 2

Category: Software DevelopmentJeff @ 00:18

Last week I wrote about my need for Dependency Injection for the Compact Framework.  Many DI frameworks do not support the Compact Framework, so I identified four options:

  1. Don't use IoC and dependency injection
  2. Continue to "roll-my-own" dependency injection tool
  3. Pray that Jeremy Miller ports StructureMap to the Compact Framework
  4. Use Ninject

Well, there is a fifth option that I hadn't considered: finding an open source project that does what I need.  Germán Schuager pointed me to a project he created on Google Code called Compact Container. It is lightweight, clean and simple.  After reviewing Germán's code, I found that it would support most of my needs, short of a couple of nice-to-have features.

  1. Marking the injectable constructor with an attribute
  2. Setting a default object life cycle for a container (rather than having to pass in the life cycle every time a type is registered with the container)
  3. Ability to resolve concrete types that have not yet been registered with the container

In the spirit of open-source, I created the necessary code and sent it back to Germán for inclusion in the project.  As of this posting, he's included the constructor attribution feature in the source repository, and hopefully he'll also add the other features as well.

My struggle with Ninject came down to the fact that it operates in a fashion quite distinct from other DI frameworks. I must admit that the Ninject DSL is quite appealing, but certain things that I'm comfortable doing with StructureMap are difficult if not impossible to attain using Ninject without creating a bunch of direct dependencies on the Ninject assemblies.  So it looks like Compact Container is the winner at this point.  We'll see if it sticks.

 

Tags: , , , ,

Nov 12 2008

Dependency Injection for the Compact Framework

Category: Software DevelopmentJeff @ 08:03

I've spent the last 2 years developing WinForms and ASP.Net MVC applications, so it's been a while since I've worked on a .NET Compact Framework project.  In case you haven't heard of it, the .NET Compact Framework is a compact sub-set of the .NET framework that can run on devices with limited resources (like phones, blackberries, barcode scanners, etc.).  As I began working on the project, I decided up front that I would use the same SOLID principles I use on any other application.  I almost hit a roadblock, however, when it came to choosing a Dependency Injection framework.

I began with just a simple roll-your-own dependency resolver.  But this solution doesn't work well once you need to inject multiple layers of dependencies and build up your object hierarchies in a more complex fashion.  So I did some research into which (if any) DI frameworks support the Compact Framework.  It turns out that only one DI framework supports the Compact Framework: Ninject.  Typically StructureMap has been my DI container of choice, and recently I've spent some time evaluating the Unity container from Microsoft, but neither of these support the Compact Framework.  So I guess I've got a limited number of choices:

  1. Don't use IoC and dependency injection
  2. Continue to "roll-my-own" dependency injection tool
  3. Pray that Jeremy Miller ports StructureMap to the Compact Framework
  4. Use Ninject

Option 1 is not even a real option.  There are too many benefits to be gained from loose coupling and depending on abstractions rather than details.  Option 2 seemed feasible at first, until I needed more complicated object hierarchies.  I can't hold my breath and wait on something that may never happen, so Option 3 isn't a real option either.  So, it looks like I'm going to be heading to Ninja school!  I'll let you know how it goes and what I discover along the way.

Tags: , , , ,