What is an IoC container?
An IoC Container is a mechanism for simplifying adherence to the Dependency Inversion (DI) principle of SOLID.
The Dependency Inversion principle states:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
Source: Wikipedia https://en.wikipedia.org/wiki/Dependency_inversion_principle
The intention of the principle is to facilitate a decoupling within Object-Oriented (OO) software to make it easier to change that software as new requirements come to light.
Making the struggle real…
In a quite contrived example, let’s say we are asked to build a service that submits CSV data to a web service endpoint.
In this scenario we need a class which generates the CSV format:
Which is called by a higher level piece of code:
It is easy to imagine this logic happening in a few places as we likely want to have various interactions with this endpoint.
If we now get another requirement to send the data in an XML format to a message queue, possibly for another client, or because the receiving system has changed, we need to go and make some changes to our solution.
First we need to create an XML serializer:
Then we need to change our service class to cater for our desired behaviour:
So what are the problems with this?
- Testability: Due to the direct references to ConfigurationManager, HttpClient and MqClient, it is impossible to test this service without having these dependencies also available.
- Change: The changes in dependency have lead to what is basically a total rewrite (I did say this was contrived). The testability issue above also means we struggle to test easily as well.
Time for a Plan B
So how can we restructure this code in a way that overcomes the issues above. The Dependency Inversion principle gives us some help here.
The first thing we need to do is create our common abstraction for serialization:
We also need common abstractions for our Http and MQ clients:
Once we have these common abstractions, we can "inject" them into our service:
So this simplifies our Service somewhat, makes it testable by creating in-memory/stub/fake/mock versions of IClient and means we can create and modify our components independently. However, we still have one issue...
How do we register what our dependencies are and inject them into the constructor?
We could do something like this:
And for this simple case, it is probably ok to do so, but as our solution becomes more complex we also need to manage combinations of dependencies, multiple execution contexts and different lifecycles. This is where the IoC Container becomes useful.
Using an IoC Container
Bootstrapping our application with an IoC Container looks something like this:
A bit more code in our bootstrapping, but hopefully it is clear the benefit this gives as our application evolves.
In this central configuration we can set our combinations, contexts and lifecycles so our application logic does not need to worry about them.
We also no longer need to define the dependencies for our service inline and so can change these dependencies freely. I find this most useful when dealing with MVC/WebAPI Controller dependencies, Services and Repositories.
What are some of our IoC Container options?
If this is the right thing how do you choose one?
Dependency resolution speed is a consideration for choosing an IoC Container, I won't cover it in depth here as it has been done so by Daniel Palme here.
Unity and Spring are delivered by Microsoft and Oracle respectively, they also have a great deal of support within the community so it is likely if you have an issue you can get help somewhere. A less popular container such as DryIoc, although a great project, is likely to provide lower levels of support in the industry.
Are you happy to accept attributes on your code to instruct the IoC Container? Or do you want it all to be configuration driven? Do you want to define it all in code?
Is Community Driven Palatable?
If the organisation you work for does not accept community driven components, then an enterprise supported version such as Unity may be the easiest to get support for.
Hopefully this is useful. If you have any comments or queries, feel free to add them in the comments and I'll amend as appropriate.