Dependency Injection and those dependency injection containers.
I once answered a question on one of the developer forums, the question was “I am starting out a new ASP.net MVC application, Which Dependency Injection container should I use?”
To this I said
“Don’t worry about any Dependency Injection containers as such yet. You are only starting out, construct your business logic first with dependencies that are de-coupled and may be inverted. Then test as you go along. Dependency Injection container is a detail that you can figure out later. Your tests will guide you whether to use a DI container or not and may be also guide you as to which one to use, if you need one?”
And oh boy, I had all sorts of abuse thrown at me. Obviously, I didn’t mean any harm, all I wanted to do was to tell this developer to focus on building the application’s business logic and not get bogged down in the choice of some tool.
The abuse really was nasty and kind of put me off, answering questions on this forum.
Today, I was involved in another discussion on the same topic, but thank god the people were a bit more civil.
Now a days, when you go to an interview, people will ask you, “do you know Entity Framework, nhibernate, MVC, Angular?”, “Which Dependency Injection or Inversion Of Control framework are you familiar with?”
All the questions are aimed at the tools, not on the core principles of software engineering. This kind of breaks my heart.
On the contrast when you call a plumber to fix a leak in your house, for example, do you really bother about what tools he is going to use? Most probably not or may that is the last thing that you look into.
Why is it that in our trade it’s becoming so obsessed about the tools and processes.
Ok enough ranting. Lets get down to the topic of Dependency Inversion
To really understand Dependency Injection, you have to understand “Inversion Of Control” or “Dependency Inversion”. Inversion of control in terms of dependencies means, your top-level objects should not depend directly on the bottom level objects, both the low-level and high level objects should depend upon some sort of abstractions.
Or in other words, In IOC(Inversion Of Control), we are inverting the control, so rather than top-level objects calling the bottom level objects we let the top-level objects call the Abstractions/Interfaces without knowing which bottom level objects will be created and used as details to those abstractions.
And DI, (Dependency Inversion) here is the same thing, and in Robert c. Martin’s words –
”Both the high level objects and the low-level objects should depend upon abstractions. Abstractions should not depend upon details, details should depend upon abstractions”.
So what this means, is that your business logic layer(High Level Layer) for example should not depend upon your data access layer(Low Level).
Imagine that you had a “Customer” Entity and some sort of Service(class) that exposed some stuff that you can do with customer object. Now lets assume that these objects need to dip into the database, applying Dependency Inversion here means that your Customer objects should not call into Database directly. They should outline what they need from the database in Abstract Object. The lower layer should then take this abstract object and provide the implementation. This way you have now inverted the dependencies.
As most of the time the functionality that you want from the database is quite generic or common among most of your entities, this abstraction could thus be a IRepository<T> interface.
If your business logic layer uses lower level data layer via the IRepository interface, it depends upon the abstraction(IRepository) not on the implementation of IRepository. Now you really do not care who implements the IRepository.
On the other side your lower level layer implements the IRepository interface, hence it also depends on the abstraction, it does not care who uses it and how.
With this in mind, Dependency Inversion is a principle, It is not a written piece of software, It’s just a way to decouple your entities, There are a lot of written pieces of software that facilitate this technique. These are known as Dependency Injection/Inversion Containers or IOC(Inversion Of Control) containers.
Inverting dependencies is still software developers job. These containers would not take your dependencies and invert them for you, doing that requires an understanding of polymorphism and many other software engineering principles.
Inversion of control or dependency inversion also applies to the IOC/DI containers.
If we go back to DI principle for a moment (that we should only depend upon abstractions) while we making a decision about DI containers. We should define what we need from the DI container, rather than what functionality it provides. If we think like this then the DI container also becomes an implementation or a provider of a service that is abstracted away in an interface or class or something. If we are inverting the control/dependencies, then why should we depend on a particular container, we should only depend on an abstraction.
Sometimes I hear the remarks such as “Hey man, if you have used one IOC container, you are probably going to stick with it and never change it”. It probably would never change, but what if it does. and really how much of an effort is it to come up with some basic/obvious use cases or functionality that we would require from a DI container and put these into an interface or abstraction.
I know the DI container will offer a lot more than just plugging in your dependencies, but do we really need this “lot more”. If we do, how hard is it to put it behind an abstraction?
If we defined what we needed from a tool in an abstraction, then we can use any tool or even better try to fail some tools till we find the best one.
At the bare minimum all DI containers, provide you with two core functionalities Register and Resolve.
The Registration process is when you are registering the implementation to an abstraction. This then in turn tells the container to store this mapping in its internal repository. Most part of the container is a repository to hold mappings between your abstractions and implementations.
Dependency Resolution is a recursive process. When you ask the container to resolve something for you, it will look into the constructor or attributes using reflection and figure out the dependencies. Then for each of these dependencies it will repeat the process again. Every time it does this process it will go and query its internal repository for implementations to the abstractions you are trying to resolve.
That’s the core functionality any DI framework will provide and if we put this behind an abstraction layer and then use that, we do not have to worry about which particular framework will supply that concrete functionality.
The moral of the story is that, understand why IOC is a good technique, Implement Inversion of control in your code structure, de-couple the code first and then stop and think is you dependency graph so big that you need support from a framework , or is it that you can that register and resolve yourself and ship the product.
One of the reasons, why DI containers have become so popular and in some cases blindly used are because of the facilities it offers while testing. Writing testable code does not depend on DI containers, DI container is just a facility, and icing on the cake. Writing testable code means writing de-coupled code. So de-couple the code first and then think about using a container if needed.
Leave the “choice of DI framework” decision right to the end, when your application architecture forces you to use one.