Factory Methods: Decoupling the IoC Container from the Abstraction

We were talking about Inversion of Control and how Dependency Injection helps to handle its complexity.

However, there are some trade off we must make base on our project to not add unneeded complexity.

I will use Spring Boot as an example.

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

Dependency Inversion Series

Let’s do our best: Using @Configuration

Let’s use our previous example:

Compile dependency by annotations

We see how @Service annotation is creating a compile time dependency, so, you really are not totally decoupled from Spring IoC.

Spring Component Annotations: Advantages and Disadvantages

Well, we can use @Component and @Service (together with other like @Repository or @RestController) to define a Spring bean.

This strategy has the following advantages:

  • Easy to understand, when you see a class with those annotations you know they are Spring beans.
  • Fast to code, you only need to add the annotation and that’s all (NOTE: Spring needs to find them, be sure to have a good component scan setup.

And disadvantages:

  • Compile time dependency, as you are adding annotations to your business logic, so, you are coupling your Abstraction, with your IoC container.
  • Dirty code, you shouldn’t care about IoC containers in your business logic.

Spring Factory Pattern: @Bean

Spring gives us a way to avoid adding annotations to our Abstraction.

So, let’s move our current implementation to a better state:

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

MySQLRepository doesn’t change too much.

public class Service{
 private final Repository repository;

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

 //TODO behavior
}

Notice that those classes are pure Abstraction, they don’t know about the IoC container.

@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
 }
}

Our Main doesn’t change at all. However, we add another part to our Main that is responsible to tell Spring how to create and handle our beans, the @Configuration.

@Configuration
public class ApplicationFactory{

 @Bean
 public Repository repository(){
  return new MySQLRepository();
 }

 @Bean
 public Service service(Repository repository){
  return new Service(repository);
 }
}

Before, we use @Service and @Component to tell Spring which classes it must handle. Spring was in charge of instantiation and injection.

Now, @Configuration tells Spring that that class is a factory of beans. It means the developer is in charge of defining how to create beans.

Each factory method is defined by @Bean annotation. Moreover, you can use Dependency Injection to use beans created by other factory methods, like you see in line 10.

Now, let’s see how our application looks like:

Removing compile dependencies by annotations

Well, this approach has the following advantages:

  • There is not more annotation dependency, our Abstraction is totally free.
  • We control how the beans are created, so, you can add logic and complex builders to the beans.
  • We can test easily the factory methods, if we think it is worth.

And disadvantages:

  • More code to maintain, we should write factory methods by bean.
  • Difficult to follow and understand, in a real project, we could have a lot of beans, therefore, a lot of factory classes.

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

Final Thought

Factory methods are a pretty good way to avoid coupling against IoC containers, however, we should trade off those decisions base on the project needs.

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.

9 comments

Leave a comment