Ronin Building Blocks - Dependency Injection
As much as I enjoy coding I must constantly decide whether there is more value in writing a custom piece of code or taking one off the shelf that is supported and has had more eyes on it. A great example of this can be seen in the next few building blocks starting with this one on Dependency Injection. I will say that I have always been a little hesitant on using the mainstream dependency injection frameworks as they often have too much reflection associated costs with them for the value I found they brought in a high scale system.
I recently started to reflect more on this and began to change my stance due to:
- The ease of substituting stubbed in classes during testing
- The logical extensibility points it represents
- Centralization of the instances lifetime control
Up until now I had not used the new Microsoft.Extensions.DependencyInjection framework but from my research I like its approach to allowing substitution other DI frameworks and what appears to be a lighter touch. Let's start by discussing the basics of what it is for anyone that has not used it in the past.
Dependency Injection is a technique used to resolve references to an instance of a type using factories, typically to enable the Inversion of Control (IoC) pattern.
A great time to use DI is with logging frameworks. In my experience this seems to be a common point of change within a solution due to developer, performance or operations requirements. If you did not use a DI style approach, custom or through a common framework, you would have to go back through most your code to make these changes. Through DI you can make the change easily in one or at most a handful of changes, saving cost and risk.
Let’s use look at a simple example of a message generator that will be created using the DI framework. The first thing we need to do once we create our application is to add the NUGET package. The Microsoft.Extensions.DependencyInjection package installs the entire DI framework while the Microsoft.Extensions.DependencyInjection.Abstractions package provides the minimal interfaces as seen here.
Next I will define the IMessageGenerator interface that defines the contract my instance will use regardless of the concrete implementation.
To transform this console application so it uses simple DI is done with a few simple steps.
- Add a reference to the framework
- Create an instance of the service collection
- Add the instances with the appropriate lifetime constructs (I will talk more about this shortly)
- Build the dependency injection frameworks service provider
- Request an instance of the object
As you can see here in the final transformation it appears to be a lot more work but it really only a few additional lines but the flexibility it provides is worth it.
using System;
using Microsoft.Extensions.DependencyInjection;
class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IMessageGenerator>(new SimpleMessageGenerator());
var serviceProvider = serviceCollection.BuildServiceProvider();
var generator = serviceProvider.GetService<IMessageGenerator>();
Console.WriteLine(generator.GetMessage());
}
}
Running this program again we see the DI version of the program produces the same output.
Now to show our dependency injection is working I will change the instance to a complex generator as follows and run it again.
serviceCollection.AddSingleton<IMessageGenerator>(new ComplexMessageGenerator("test"));
That sample demonstrates a simple singleton version of the service that will continue to hand the same instance out to all clients that ask for an instance. That would be great if all the instance of classes I will every use did not contain state but there are quite a few that will. The following changes modify this class to hand out a different instance each time requested.
using System;
using Microsoft.Extensions.DependencyInjection;
class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IMessageGenerator>(ImplementationFactory);
var serviceProvider = serviceCollection.BuildServiceProvider();
var generator1 = serviceProvider.GetService<IMessageGenerator>();
Console.WriteLine($"{ generator1.GetId():N}:{ generator1.GetMessage() }");
var generator2 = serviceProvider.GetService<IMessageGenerator>();
Console.WriteLine($"{ generator2.GetId():N}:{ generator2.GetMessage() }");
}
private static IMessageGenerator ImplementationFactory(IServiceProvider serviceProvider)
{
return new SimpleMessageGenerator();
}
}
As can be seen before one way to do this is using a factory method that will be invoked each time the instance of the service is requested. Obviously in this case I am using a simple instantiation of the instance in the method but I could replace it with a resource pool or other. Here is the console output proving by the instance id that it creates a new instance with each request.
There are more options and techniques you can use with the DI framework but for our purposes that will most likely be the key areas of interest.
Hopefully by seeing how easy and light this is you can understand why I now feel it is worth taking on. Building this solution using the framework allows me to introduce contracts and make the overall solution easily extensible.