Dependency Injection: Solving the Inversion of Control complexity

We talked about Inversion of Control (IoC) and how this helps us with the object creation challenge, splitting the Main and Abstraction concepts.

However, coding by ourselves the Inversion of Control only works when the project is pretty small.

In this post, we are going to explore the Dependency Injection pattern, using Spring Boot as a framework, and how it helps us to solve the Inversion of Control complexity.

TRY IT YOURSELF: You can find this post source code here.

Dependency Inversion Series

What is Dependency Injection

Dependency injection is one way to apply the Inversion of Control concept.

In this case, we define a central point, that will create every object, and supply them to other objects, as dependencies.

Starter class is the central point that supplies dependencies to other objects

However, a normal software project usually has hundreds of classes and dependencies, so, doing this at hand is expensive. We usually use a Dependency Injection framework like Spring.

NOTE: There are other ways to apply Inversion of Control, for instance using Abstract Factories or Service Locators patterns. However, Dependency Injection is one of the most used due to we can find several frameworks handling it out of the box.

NOTE: There are other Dependency Injection frameworks, like JEE, in Android we have Dagger2 or Koin if you use Kotlin, .NET has one by default.

Spring Dependency Injection Basics

IoC Container

We created a Starter class who assembles our application, creating objects and supplying them as dependencies to other objects.

Spring defines a complete container, who is in charge of instantiating, assembling, and managing objects.

Beans

Here’s a definition of beans in the Spring Framework documentation:

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.

One way to define beans is using annotations on the top of the classes:

  • @Service, @Component: Means this class is created as a Spring bean, it is a candidate for auto-detection by the IoC container. These beans don’t have anything special.
  • @Repository, @Controller, and so on: They are more specialized beans, and they add special features like connecting to a database or creating restful services.

How to define a dependency: @Autowired

We just saw how to define a bean, but, how do you supply them as dependency?. Well, @Autowired helps us.

We usually use it in the constructor, in the class where we require to have the dependency.

NOTE: There are other places where you can use the @Autowired annotation like setters methods or properties, however, I like to use the constructor because is more clear and the class cannot be created without its dependencies, so, they are required. Moreover, it is easy to test and mock.

Moving our example to Spring Boot

In the Inversion of Control post we used this example:

Inversion of Control

Let’s start with the Repository classes:

@Component
public class MySQLRepository implements Repository{
 //TODO constructor and behavior
}

Now, our Service class.

@Service
public class Service{
 private final Repository repository;

 @Autowired
 public Service(Repository repository){
   this.repository = repository;
 }

 //TODO behavior
}

See the polymorphic injection through constructor.

NOTE: Remember, @Component and @Service doesn’t add anything special to the beans, use them by your discretion.

And now, our Starter class.

@SpringBootApplication
public class Starter{

 public static void main(String[] args){
  //Starting IoC container
  ApplicationContext iocContainer = SpringApplication.run(Starter.class, args);

  //Getting Service object from the IoC container
  Service service = iocContainer.getBean("service", Service.class);

  //TODO use service behavior
 }
}

@SpringBootApplication tells Spring how to start the IoC container using the run method on line 6.

By default, Spring scans, on boot up, the whole current package and load every annotated class into the IoC container.

Later, we use the IoC container to get the Service object.

TRY IT YOURSELF: You can find this source code here.

Are we really applying full Dependency Inversion Principle?

Well, let’s see how our new implementation looks like:

Spring as our IoC container

Looks great, doesn’t it? well, let’s get deep on how the dependencies really look like:

Spring as IoC container with the whole dependencies

As we see, our Abstraction knows about Spring in compile time due to the @Service annotation.

It means, if you want to change your IoC container from Spring to other like JEE, you must change your Abstraction to support new annotations.

Final Thought

Dependency Injection is an excellent tool to help us managing Inversion of Control complexity.

We used Spring as an example, however, we let our Abstraction to know about Spring somehow, and you know we should about this.

Here, there is a decision you must make:

  • Let your Abstraction know in some level about your Dependency Injection framework as we saw. This can let you move faster in your development and is easy to understand.
  • Be cleaner, and find the way to avoid this dependency. However, that is more work and could be hard to understand. But remember, you will get the whole Dependency Inversion potential.

In a next post, I am going to be cleaner, and avoid this dependency using Spring Boot and @Configuration annotation as an example.

If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

12 comments

  1. very good article

    It was interesting to understand that there are more ways to apply IoC … like for example using the abstract factory pattern

    Now I can explain well the difference between IoC and Dependency Injection

    Cheers

    Like

Leave a comment