Kenneth Truyers

How to use Pure DI

Introduction

Kenneth Truyers

Kenneth Truyers


best practices patterns simplicity

How to use Pure DI

Posted by Kenneth Truyers on .
Featured

best practices patterns simplicity

How to use Pure DI

Posted by Kenneth Truyers on .

In my previous posts I talked about how you can decrease dependency on external libraries and frameworks while making your code simpler (not easier, simpler). In this post I want to continue on the same thread and show some of the benefits of Pure DI (as opposed to DI with a container).

DI-containers are beneficial if you have a complex application where you can rely on convention over configuration. If your application is not complex (and you should strive for that) or does not rely on conventions, a simpler approach can be followed by using pure DI. Before I dive in on how we can do this, let’s first iterate over a few of the disadvantages a container brings with it:

  • Complexity: When you configure a container in your composition root, you will usually be relying on conventions to make sure that the right types are resolved at runtime. How simple or complex this is, is directly proportional to how simple or complex your conventions are. If conventions are very clear, it will be relatively easy to spot how a dependency will be resolved at runtime. If not, it will be difficult to see how the application is composed.
  • Compiler assistance: Since you’re resolving your dependencies at runtime, the compiler can’t assist you. If you forget to declare a service it will just fail at runtime. This does not only go for registration but also for lifetimes. You can introduce lifetime mistakes and the compiler won’t be able to help you. An example of this is captive dependency (more information see: Captive dependency by Mark Seemann)
  • **Learning curve: **Every container has a different API. In order to use it effectively, you need to have quite a good knowledge of that API. With so many containers available, it’s possible you’ll encounter different containers in different projects, which means you need to learn a new API once in a while.

The situation where you might want to choose a pure DI approach over a container approach is when you do explicit registration instead of convention over configuration or when your conventions are really complex.

Pure DI in ASP.NET Web API

In order to use pure DI, you need to build your dependency graph whenever you code is being called. In an ASP.NET Web API application, this is when your controller is constructed. To intercept at this point, you need to implement the IHttpControllerActivator interface:

public class CompositionRoot : IHttpControllerActivator 
{ 
    public IHttpController Create(HttpRequestMessage request, 
                                  HttpControllerDescriptor controllerDescriptor, 
                                  Type controllerType) 
    { 
        // Resolve controller of Type here 
    }
}

Inside this method, you will be passed the requested controller-type and you need to return an instance of that controller. A few things you need to know about this class:

  • You need to register it on application start up
    ( GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new CompositionRoot() );
  • This class will only be instantiated once and will be reused for the entire lifetime of the application
  • The create-method will be called once per request

With this information, we can now infer how should configure lifetime management:

  • Singleton instances need to be instantiated in the constructor
  • Per request instances need to be instantiated inside the method
  • Transient instances need to be instantiated whenever necessary
  • Any custom lifetime is easily configurable by instantiating it when necessary

Let’s see how this looks:

public class SomeCompositionRoot : IHttpControllerActivator 
{ 
    readonly SomeService singleton; 
    public SomeCompositionRoot() 
    { 
        singleton = new SomeService(new SomeOtherSingletonService());
    } 
    public IHttpController Create(HttpRequestMessage request, 
                                 HttpControllerDescriptor controllerDescriptor, 
                                 Type controllerType) 
    { 
        var customLifeTime = new SomeCustomLifeTimeService(); 
        var perRequestService = new SomePerRequestService(customLifeTime); 
        if(controllerType == typeof(MyController)) 
        { 
            return new MyController(singleton, 
                                    new TransientService(singleton, 
                                    new SomeCustomLifeTimeService()), 
                                    perRequestService, 
                                    new SomeOtherTransientService(
                                        singleton, 
                                        perRequestService));
        } 
        throw new ArgumentException("Unexpected type!", "controllerType"); 
    } 
}

Pure DI in a console application

In a console application, we don’t have the per-request lifetime, but the same principles apply. We need to construct the graph on application start, right inside void main (or extract it to a separate class):

public class Program 
{ 
    public static void main() 
    { 
        var customLifeTime = new SomeCustomLifeTimeService(); 
        var singleton = new SomeService(customLifeTime); 
        var entryPoint = new EntrypointClass(
            new TransientDependency(singleton), 
            singleton, 
            new OtherTransientDependency(customLifeTime), 
            new SomeCustomLifeTimeService()); 
        entryPoint.Run(); // or something similar 
    } 
}

This is obviously a bit more typing work than registering services with a container, but it has certain advantages:

  • Compile-time safety: You cannot forget to register a service, because the compiler will tell you.
  • Lifetime configuration: You cannot make a lifetime mistake such as captive dependency. Because you are declaring the singletons in the constructor you can see that the services that get passed in their constructors will also be singletons. Again this makes it easier to read your configuration.
  • Explicit: Since you have very clearly marked places to declare singletons, per request and transient services, it makes the configuration very explicit. All types are also explicitly constructed so you can easily spot how the application is composed.
  • Learning curve: This is just object composition, so you don’t need to learn the specific API of a container.

Conclusion

Pure DI leads to more explicit code. You’ll write more code, but again I apply the same mantra: it’s more code, but it’s simpler code, so I’m happy to type a bit more for the sake of simplicity. One part that this approach does not solve is another pet peeve of mine: the over usage of constructor injection. In a next post I’ll show an example of how to use partial application to tackle this problem.

Kenneth Truyers

Kenneth Truyers

View Comments...