Inversion of Control (IoC) Explained | Spring and Clean Architecture

Inversion of Control (IoC) Explained | Spring and Clean Architecture

Inversion of control is one of the most important concepts in object-oriented programming that forms the base of popular architectures like the onion, hexagonal and clean architectures.

In this article, I’ll answer the following questions:

  • What's Inversion of Control?
  • How is it implemented in the Spring framework?
  • Why is it a necessary pre-requisite to the onion, hexagonal and clean architectures?

If you prefer, you can also find the same content in explained in video with the aid of animations.

Let's start!

Introduction

Inversion of Control (IoC) is a design pattern that frees our classes from the responsibility of managing their dependencies and places it onto an external framework, such as Spring. But what's a dependency?

A dependency is simply a reference to another class. For instance, a domain service class managing products needs a reference to the repository class to persist and retrieve data. The service class depends on the repository, because it’s unable to perform its functions without it.

Code dependencies

Developers often take for granted that these dependencies are resolved by a framework like Spring. But what would happen if we let each class take control of its construction instead of relying on a framework?

A world without IoC

Coupling would increase because rather than defining dependencies as interfaces, we need to reference one of their concrete implementations. This results in a loss of information hiding—the ability to conceal implementation details behind stable interfaces—making it difficult to change the implementation without all classes that depend on it.

Information Hiding

It's also more challenging to share instances across classes. Typically, database connection pools and clients are configured once and shared across the entire application as singletons. In a world without IoC, we are responsible for implementing this singleton pattern and any error can result in duplicate instances.

Now that we understand what Inversion of Control is and why it’s important, let's see how Spring implements it.

Dependency Injection (DI) and Beans

Spring implements a specialized form of Inversion of Control (IoC) called Dependency Injection.

Dependency Injection is a pattern where objects declare their dependencies as configuration metadata which is then processed by the framework to create a fully configured system.

Spring supports configuration metadata in different formats. The most common today are annotations and configuration classes. However, it’s also possible to define external XML files or Groovy scripts.

The configuration metadata is parsed by the Spring IoC container and translated into BeanDefinitions. A bean is simply an object that is created and managed by the framework. As you can see there are several properties that characterize a bean. The most important ones are class, name and scope.

Bean's properties (BeanDefinition)

The class defines the type of bean while the name is used to distinguish different instances of the same type. The scope determines the lifecycle of the bean.

If we want only one instance of a bean like in the case of a database connection pool, we set the singleton scope, while if we want a new instance each time a bean is requested, we use the prototype scope.

There are also other scopes and bean’s properties, but let’s stick to the basics. If you want more details, just leave me a comment and I’ll cover them in another article. Let’s have a closer look now at the Spring IoC Container

Spring Ioc Container

The Spring IoC Container is characterized mainly by two concepts: the BeanFactory and the ApplicationContext.

BeanFactory and ApplicationContext

The BeanFactory is the root interface responsible for instantiating, configuring, and managing beans. In a nutshell, it provides the basic dependency injection.

The ApplicationContext extends the BeanFactory and provides additional functionality that’s needed to fully configure the application according to our needs. There are several implementations of the ApplicationContext designed to support different configuration formats and application types.

If you’re a young developer, you might have never interacted directly with the ApplicationContext because you most probably use Spring Boot instead.

Spring Boot

Spring Boot's SpringApplication is yet another layer wrapping around the IoC container that auto-configures the application context, reducing the amount of configuration metadata we need to write. Sometimes, we just need to annotate our custom beans, while Spring Boot defaults are enough to configure database connections, authentication, authorization, messaging and much more.

In the past, we used to interact directly with these application contexts. I recall having to define all the configuration metadata as multiple XML files. It used to require some skill and a lot of patience 😅

Anyway, time for our last question: why is IoC necessary for the onion, hexagonal and clean architecture?

IoC and the Great Architectures

Without IoC, it’s not possible to implement the popular great architectures, because they all rely on the idea that the core business logic of the application has no direct external dependencies, like UI, databases or message queues.

We can only comply with this requirement with proper information hiding. In simple words, we need to define a control flow where interactions with the application core are governed by interfaces. It’s then the framework that is responsible to wire these dependencies at runtime. Without IoC this is not possible because we would reference concrete classes rather than interfaces.

If you want to understand more about the great architectures, I have an article for each one of them:

Share and Comment

If you read till here, comment and share so I can grow more this community and make sure that everyone has the opportunity to learn something new!

Ciao! 👋