Wednesday, April 29, 2015

On Demand Dependencies in C#

Imagine a situation where you have a business engine that requires different dependencies, such as repositories and services. An easy solution for decoupling these dependencies from the business engine (although a 'business engine' referrers to a class that contains business logic, you could substitute it for any class that requires dependencies for the purpose of this post) would be to employ dependency injection via the constructor. When your business engine only requires a few dependencies, this method works well. However, what about where six dependencies are being injected as illustrated below?

public class MyBusinessEngine
{
    private readonly IDependencyA DependencyA;
    private readonly IDependencyB DependencyB;
    private readonly IDependencyC DependencyC;
    private readonly IDependencyD DependencyD;
    private readonly IDependencyE DependencyE;
    private readonly IDependencyF DependencyF;

    //Constructor injection from IoC
    public MyBusinessEngine(IDependencyA dependencyA, IDependencyB dependencyB, IDependencyC dependencyC,
        IDependencyD dependencyD, IDependencyE dependencyE, IDependencyF dependencyF)
    {
        DependencyA = dependencyA;
        DependencyB = dependencyB;
        DependencyC = dependencyC;
        DependencyD = dependencyD;
        DependencyE = dependencyE;
        DependencyF = dependencyF;
    }

    //Multiple functions that use the above dependencies. 
    //Note that everyone of the methods does not use all of the dependencies; some might use
    //all of them when others might only use a few.
}

With this long list of dependencies, the constructor looks cluttered. In addition, as not all the methods on MyBusinessEngine use all the injected dependencies, resources are being wasted creating them when they aren't going to be needed. There has to be a better way for when you don’t need to inject the kitchen sink!

Resolve dependencies on demand:

public class MyBusinessEngine
{
    public MyBusinessEngine()
    {
    }

    public void SomeMethod()
    {
        var dependencyA = MyContainer.Container.GetInstance<IDependencyA>();
        var dependencyD = MyContainer.Container.GetInstance<IDependencyD>();

        //...
    }
}

Dependencies are no longer injected, but are resolved as they are needed by requesting them from the IoC container directly. Resources are saved by no longer supplying everything the class could need and only creating when is required by the method being invoked. This is an easy solution to implement, however, it has a serious drawback: the class is now coupled to the IoC container. In order to unit test this class, mocks of the dependencies will need to be registered with the IoC container so they can be resolved at runtime by the class under test. Once again, there must be a better way.

Abstracting the IoC container:

public class MyDependencyResolver : IDependencyResolver
{
    public T GetInstance<T>()
    {
        return MyContainer.Container.GetInstance<T>();
    }
}

public class BusinessEngine
{
    private readonly IDependencyResolver DependencyResolver;

    //Constructor injection of MyDependencyResolver from IoC
    public BusinessEngine(IDependencyResolver dependencyResolver)
    {
        DependencyResolver = dependencyResolver;
    }

    public void SomeMethod()
    {
        var dependencyA = DependencyResolver.GetInstance<IDependencyA>();
        var dependencyD = DependencyResolver.GetInstance<IDependencyD>();

        //...
    }
}

In order to remove the coupling of the business engine with the IoC container, the mechanism for which dependencies are resolved needs to be abstracted. This is achieved by creating a dependency resolver that handles the resolution of dependencies. The business engine no longer cares about how the dependency is created, all it cares about is getting the one it requested. You could move from using StructureMap to Castle Windsor or even newing-up instances directly (which is not recommended) and the business engine would not be affected.

Unit testing for the business engine is now possible though mocking the dependency resolver to return mocks of the dependency that are required for the code under test. This makes testing a lot easier and removes the need for your tests to reference the IoC container.

To limit the objects that can be retrieved from your dependency resolver, you can add a constraint upon the GetInstance method. For example, if you created a resolver for repositories, you could apply this constraint: T GetInstance<T>() where T : class, IRepository. This would ensure that only classes that implement IRepository can be served by this dependency resolver. The only issue with this is that as you are now creating multiple dependency resolvers, you will also be adding additional references to your IoC container throughout your project. Fortunately, there's a better way for doing this too.

Decoupling the IoC Container:

public class MyDependencyResolver : IDependencyResolver
{
    public T GetInstance<T>()
    {
        return ServiceLocator.Current.GetInstance<T>();
    }
}

Through using the Common Service Locator (see my blog post from January), the IoC container is no longer coupled to the dependency resolver. If you wanted to change your IoC container, you can now achieve this by making the change in a single location (MyContainer) and rather than having to update all the dependency resolvers as well. Whereas before your project was heavily coupled to your IoC container, by shifting that coupling to the Common Service Locator, you are now free to change your container with minimal impact.

There's the option to only inject dependency resolvers and then to resolve all other instances upon demand. This would certainly help to keep your constructors short and concise. It would also help with the maintenance of unit tests as you would no longer break your tests the moment you add a new dependency to the constructor. As that new dependency would be supplied from the dependency resolver that's already being injected, no change to the constructors signature would be required.

One last thing to consider is that the service locator pattern hides the context from the IoC container when supplying requested dependencies. In other words, it has no idea about what other dependencies have been requested or what is requesting the dependency. If context is important to your application, then it would be wise to inject directly from the IoC container.

The code for the OnDemandDependencies project can be found on GitHub. I've included an example of how you can unit test a business engine with MSTest and Rhino Mocks and how you can create dependencies that live for the lifetime of a dependency resolver.

No comments:

Post a Comment