Dependency Injection and Inversion of Control – a marriage made in heaven ♥

Dependency injection (DI) supports the dependency inversion principle that is one of the 5 SOLID principles. At Vertica we have adopted both the principles of Dependency Injection and Inversion of Control in pretty much all our projects for a number of reasons I hope to explain in this post.

So, what is Dependency Injection?

In short, the principle can be summarized in – a loosely coupled system built upon components that are not responsible for creating their own dependencies. Instead the dependencies are provided from outside the component itself. Components in a loosely coupled system can be replaced with alternative implementations that provide the same services.

A tight coupled system...

The opposite to loosely coupled system is a tight coupled system. Here is an example of a tight coupled dependency. The component VideoService is responsible for creating its own dependency to an implementation of type VideoRepository.

... and a loosely coupled system

Here is the same example but implemented in a loosely coupled system. The component VideoService is no longer responsible itself to create dependencies to other components in the system. The dependency to an implementation of type VideoRepository has been removed by introducing an interface IVideoRepository. And the responsibility to provide a concrete implementation of that interface has been moved to outside the component itself, and is now injected in the constructor.

Advantages implementing DI

So, what are the benefits by implementing the later alternative, the loosely coupled system? Here are a few advantages that I think is the most important to remember.

  • It is obvious that component VideoService requires a component of type IVideoRepository to work and perform its tasks – you can’t create an instance of type VideoService without the dependency injected in the constructor.
  • We can use VideoService with any implementation of interface IVideoRepository, we are not restricted to a specific implementation.
  • It is possible to implement tests to verify the full interaction between VideoService and IVideoRepository.
  • Since we are implementing an interface IVideoRepository it is possible to fake/mock this type in tests. This opens up the possibility to test the VideoService component isolated from its dependencies to other components.

Image from tecnokolik. com / autofac-kullanimi /

Disadvantages implementing DI

An obvious drawback with a loosely coupled system is that when your application is getting bigger and more complex, so does the list of dependencies. This could easily end up with a complex and deep dependency graphs. This example is not too complex but might illustrate the problem if the graph is getting longer and deeper in your application.

But if there are side effects, there are also solutions - so it's time to introduce the concept of Inversion of Control (IoC).

So, what is Inversion of Control?

To solve the problem with complex and deep dependency chains passed to the constructors in a loosely coupled system we introduce a mechanism called Inversion of Control (IoC). In short, the mechanism can be summarized in – the responsibility to create instances of classes in the system is moved to a so called container and you no longer need to create your instances yourself, you simply ask the container to create the instance for you. The container needs to be configured in the system to work and we can then resolve the instances of the implementations in the system by resolving them from the container, like this: 

In the best of worlds, you will not need to resolve instances in this way by allowing the system to handle this in the end-points that executes your code. For example, you can configure a dependency resolver for the controller factory in that is responsible for creating instances of your controllers and all its dependencies to other components.

Examples of popular IoC frameworks are:

A marriage made in heaven

Here are some examples of advantages you will achieve by implementing both DI and IoC in your systems:

  • The loosely coupled system enables a more flexible and pluggable solution and facilitates reuse of code.
  • Improves testability significantly by enabling so-called "mocking".
  • The combination of abstraction by constructing interfaces enables us to replace the implementation for dependencies in runtime, i.e. when the code executes. This is pretty cool ;-) and useful in many scenarios.
  • Your code is easier to refactor and changing the signature of a constructor is just a simple container configuration away and it compiles and runs.

Although DI and IoC may seem complex and difficult to understand, do not give up! It is quite easy to implement and understand conceptually when you have tried it out and worked with the mechanisms for a while. This is also often referred to as one of the disadvantages of implementing DI and IoC, but I’m not sure this is actually being the case if you just give it a GO!