I had the privilege of mentoring several co-workers in 2015. One of the topics they found confusing was Dependency Injection. We use it everywhere. To them it felt like magic. The code just fits together through mystical Containers. In this post we will break down the powerful concepts surrounding Dependency Injection.
Let’s start with some simple classes:
These are all concrete classes. No fancy dependency magic
here. What this code does is very clear. Bar
creates a Foo
then
uses it to print Hello World
.
A complete program using this code is also straightforward:
Create a new Bar
then call Example
. Hello World!
Dependency Inversion Principle Applied
Let’s spice things up! Instead of creating the Foo
in Bar
’s
constructor we can pass it in. Better yet, we can switch to an interface with
all the same methods as Foo
.
So what have we gained? Well, the low level Bar
class no longer knows
anything about the IFoo
implementation it is using. That is now up to
callers using Bar
. We have switched from a concrete implementation to a higher level abstraction.
This decouples the code making it easier to maintain.
We have applied the Dependency Inversion Principle1. As defined by Robert Martin it is:
A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
The abstraction could be anything. Typically it will be an interface, but can also be a base class, delegate or another abstraction. The key is shifting the code from the implementation to a higher level.
What About Dependency Injection?
Another concept closely related to Dependency Inversion is Dependency Injection. In fact, we used it without really knowing it. Where Dependency Inversion was all about the abstractions and layers, Dependency Injection is all about how dependencies are provided.
Don’t worry, it is a really simple idea. Here is the demystified definition by James Shore:
Dependency injection means giving an object its instance variables.
Literally, injecting dependencies into a class. In our previous example, we injected our dependencies using constructor parameters. This is the most common approach, but you can also inject dependencies using properties or specialized methods.
Look look at the program from the last example:
We inject the IFoo
into the constructor of the Bar
. The program can
now choose which IFoo
to use. This is more flexible than when the choice
was buried in the Bar
class.
The Benefits
We have inverted our dependencies and injected them into our classes. This is fantastic! Our code is nicely decoupled. We can easily change what is injected for testing or introducing new features.
Implementations are hidden
behind abstractions and can be easily replaced. Want an IFoo
which writes
out to files? No problem. You can change the code to your new FileFoo
without ever
modifying Bar
.
The original Bar
is impossible to test in isolation. The direct dependency
on Foo
forces the two classes to be tested together. Changes to Foo
could break the tests for Bar
.
By injecting the dependencies, we can use a fake IFoo
in
tests to do whatever we want. This is a great way to set up specific scenarios and/or avoid external
systems (i.e. databases or services).
Dependencies For Everyone!
Creating classes is more challenging when you use Dependency Injection and Dependency Inversion frequently.
We have moved where dependencies are created. This poses a problem for consumers of the original classes. They must both choose what to inject and create all the dependencies. I mean ALL the dependencies.
Your dependencies start to have their own dependencies. While this is not too bad with a few dependencies, once you get into chains of dependencies it gets nasty.
Think about it. One class has a dependency, the dependency has more dependencies and those dependencies have their own dependencies. This is the tip of the iceberg:
If every class repeated setups like this it would be a big problem. Creating anything would be a nightmare. Thankfully there is a better solution, Dependency Injection Containers.
Dependency Injection Containers
With all this dependency madness we need to find a better way to create classes. Don’t worry! There are fantastic libraries to address this problem. They are commonly referred to as Dependency Injection Containers or Containers for short.
Containers contain and seamlessly connect all of your dependencies. Within your application they are used to instantiate dependencies they know about.
Before we get to the real thing I want to walk you through a mental models for Containers.
- Externally they are like one massive Factory for any type
- Internally they are like a Dictionary of Factories
Enter the Factories
A Factory is a common creational pattern. They allow you to abstract what is being created and how it is created. Dependency Injection Containers behave like Super Factories which can create any type they know about.
Want an IFoo
? Use the FooFactory
!
The Factory can be used any time an IFoo
is needed without any knowledge of which IFoo
is created.
We could easily update the FooFactory
to create a FileFoo
.
Factories can partially contain the sprawling dependencies. The more dependencies you have the more factories you will need. Factories will need to call other factories to create nested dependencies. This can get complicated when many dependencies are needed. The extra classes and glue code are tedious to maintain.
Prior to using IFoo
we need to create one using the factory:
Having to use the factory everywhere is not fun. We can do better.
Poor Man’s Dependency Injection Container
What if we could use a single class to get any dependency we wanted? We could use a Dictionary of Factories! The Dictionary would be keyed on types where the values would define how to create their respective types. We could then create any class the Dictionary knows about.
In this section, we are going to create a really simple class to do just that. Our very own simple Dependency Injection Container.
Question: Why is it called a “Container”? It will contain all our application’s dependencies. When our application starts we will give it all the dependencies we want to create and classes we want to inject the dependencies into.
The Container needs to:
- Resolve types our application needs
- Register types our application provides
Enough with the words! Onto the code!
Not bad. One method to Register
dependencies and another to Resolve
them. The methods line up with our “Factory for any type” and “Dictionary of Factories” mental models.
We accept Func<T>
’s as simple factories for each type. Once all the types
have been registered, Resolve
behaves like a Factory for any type.
Let’s implement our simple version:
Internally we have the “Dictionary of Factories”. Resolve
uses the
Func<T>
’s we registered.
Using the Container is easy. In the next example, we register all the types (lines 5-11)
then resolve them (lines 8 and 13). Once we have resolved Bar
we can use it normally!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Program {
static void Main() {
SimpleContainer container = new SimpleContainer();
container.Register<IFoo>( () => new ConsoleFoo() );
container.Register<Bar>(
() => {
IFoo foo = container.Resolve<IFoo>();
return new Bar( foo );
}
);
Bar bar = container.Resolve<Bar>();
bar.Example();
}
}
Cool. We have a Container. I would not use it in a real project. It is awkward
to use. The classes need to be wired together manually. We had to tell it
exactly how to create a Bar
even though it already knew how to
create an IFoo
.
This is where real Dependency Injection Containers are fantastic. They solve the wiring problem and automatically inject dependencies they know about. We can use the Container like glue to bind everything together.
Abstractions are registered as concrete types. Consumers can resolve and use those abstractions directly. They have no knowledge of the concrete types being injected. Instead they rely on the Container. This preserves the Dependency Inversion principle and helps decouple our code.
Awesome Containers
Thankfully, there are many great open source Dependency Injection Containers. The following three are my favourites. Our team has used them on different projects.
Autofac is new, super clean and powerful. The registration API is fun! They also have great support for controlling lifetimes/scoping and cleaning up for you. This would be my first choice when starting a new application.
StructureMap is battle hardened having been the original .NET Dependency Injection Container. The latest version of StructureMap was a massive step forward. The authors incoperated many improvements they learned from 10 years of supporting the project. A great choice you should definitely check out.
Lastly, we use Nancy a lot! For the simpler applications, we exclusively use the built-in TinyIoC. It is simpler than the other Containers and is missing some advanced options. We periodically consider switching to one of the other libraries for these features which we believe would simplify our configuration.
More Out of the Box
These libraries greatly enhance how you register and resolve components. Often these capabilities are connected; features used when registering define how objects are resolved.
All the Containers can wire together classes based on what they need injected. You could
register Bar
and when it is resolved the Container would automatically
inject an IFoo
based on what was registered for IFoo
.
Here is an example of our application using Autofac:
It does the right thing and gives Bar
the registered ConsoleFoo
.
Many Containers have shortcuts for simple transformations, i.e. from T
to Lazy<T>
.
Containers often support resolving/registering open generic types.
Containers can offer the ability to register sets of dependencies in Modules or Registries. This provides a simple way to group registrations together or split them apart. For example, you could register all database related classes in one module separate from your logging module.
Most Containers provide mechanisms
for registering your types based on conventions so you do not
need to configure everything by hand. You can register all classes
implementing a similar interface name, i.e. Foo
would be registered
for IFoo
. This is cool for people who like conventions over configuration,
but can be too much magic other people. We use this approach and only configure
classes which violate our simple conventions.
Perhaps the greatest benefit is how they integrate with various frameworks. Containers often have shortcuts to hook into popular web frameworks, like ASP.NET MVC or Nancy. The framework can use the Container to resolve types it needs. We use this to create Controllers and automatically inject their dependencies. This lets you use Dependency Injection while decoupling your code from the Container itself. Everything magically fits together.
The larger our applications become the more benefit we get from using Dependency Injection Containers. We no longer worry about how we are going to wire our classes together. Instead, we can focus on designing our interfaces and classes.
Connecting the Dots
Phew, you made it this far! I hope this helped shed some light on Dependency Injection and the surrounding concepts.
Instead of using concrete classes we switched to higher level dependencies, applying Dependency Inversion. We needed to get those dependencies from somewhere so we used Dependency Injection, via constructor parameters, to inject the dependencies we wanted.
We explored Dependency Injection Containers using these mental models:
- Externally they are like one massive Factory for any type
- Internally they are like a Dictionary of Factories
Then we dug into complete Dependency Injection Containers and their extra features.
Have fun decoupling your dependencies!
Further Reading
There is so much more you can read and learn. While writing this post I found these additional resources:
Autofac and StructureMap Documentation
Both these libraries are fantastic and their maintainers have put some serious work into writing comprehensive documentation. They share many recommendations and pitfalls for using their frameworks. The most interesting articles include insights into the decisions they made and why they made them.
Dependency Injection Containers impact how you design your application and need to be treated with care. These recommendations will help you avoid problems2.
Real life applications of Dependency Injection in the wild plus a good recap of the concepts.
Inversion of Control Containers and the Dependency Injection pattern
This is a more in-depth explanation of the concepts. The closely related ideas of “Inversion of Control” and “Service Locators” are explained. There is a review of best practices and trade-offs. Some of the best practices may be a little dated, i.e. using a Service Locator instead of Dependency Injection Containers.
Footnotes
1. I could not get this link to work. I found it via this great article explaining how an abstraction is not synonymous with interfaces.
2. For some applications we intentionally call the Container from our tests. We treat the Container configuration as part of our integration tests. I will agree this is not ideal, but it simplifies creating various types and better mimics what our users will run.
Thanks
Thanks to my gracious 2015 mentees for letting me practice this on you.
Thanks again to my co-worker Josh who helped review this article. He had the great recommendation of renaming the “Poor Man’s DI Container section” to “Man with too much time on his hands’ DI Container”. Maybe I need to go write more code.