Empowering the Hexagonal, Onion and Clean Architecture with Dependency Injection

In this article, you will find a little trick to unlock the full potential of Dependency Injection to empower these architectural approaches

Fábio José
6 min readMay 17, 2019
Photo by Patrick Tomasso on Unsplash

Check out this (trick) solution to eliminate the coupling with Spring annotations or any other vendor-specific definition for dependency injection. Doing it, we will increase the interoperability with other frameworks and make your software architecture more clear.

All sources are available at Github, clone it and have fun!

To take advantages of Spring Boot dependency injection on microservices that follows theses architectures sometimes could be hard, mainly in the core classes, or in the classes that reside in the center of the diagram below. For them, we want to reduce, to eliminate, the dependency with the frameworks around.

Hexagonal Architecture, by Alistair Cockburn — here
The Onion Architecture, by Jeffrey Palermo — here
The Clean Architecture, by Uncle Bob — here

What we want is to abstract away from the blue circle (see in the diagram above), but with abstraction, we take the advantages with loose coupling, but we take a penalty too. If we are using frameworks like Spring Boot where the dependency injection is the main feature that everything depends on it, we may not be able to use the full its potential in the core classes.

To get a well understanding of inversion of control and dependency injection, I personally recommend this great article.

There are other ways to get there

Spring is not the only way to successfully employ dependency injection — DI — to unlock the full potential of loose coupling in our project. To get there we have other flavors for Java:

The most used is Spring, in special Spring Boot, but it’s heavyweight and slow. Well, this slowness is compensated by high productivity. If we want a lightweight and fast, we have a lot of other options.

To compare and see the real benefits, we have three examples:

The Problem

Out-of-the-box, with Spring, we have to use Spring Beans annotations to employ DI, some of them are: @Component @Service @Controller @Repository. These annotations turn classes in managed beans that can be used as dependencies with the @Autowired annotation, but, if we follow the by-the-book principles, we can’t put these annotations in the core classes. Let’s see an example.

Below we have a dependency using the component annotation to become managed. This is ok because MyDependency class is outside of our core.

package com.github.fabiojose.di;import org.springframework.stereotype.Component;@Component //ok
class MyDependency {
public Object bar() {
return "bar";
}
}

Now, we have a core class called MyCoreClass. This class needs to use the MyDependency class with dependency injection. Well, but we can’t use the Spring Beans annotations in the core. If we do that we are married with this framework forever and it may be so bad for our business logic.

By-the-book, avoid any stranger import in the classes that lives at the core package.

package com.github.fabiojose.di.core;// we cant' have framework imports in our core
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/*
* We cant' put @Service annotation at core classes
*/

@Service //nooo...
class MyCoreClass {

@Autowired //nooo...
private MyDependency my;

public void dummy() {
Object foo = my.bar();
// . . .
}
}

You can solve the problem using constructors with arguments, but when a new dependency is needed we must update our constructor’s signature and the injection point.

First, we create the class constructor with arguments and do not employ any kind of annotations.

import com.github.fabiojose.di.port.Dependency;public class MyCoreClass {
private Dependency my;
public MyCoreClass(Dependency my) {
this.my = my;
}


// . . .
}

Then, we use the DI framework factory method annotation to create the managed bean, like this:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfiguration {
@Bean
public MyCoreClass core(Dependency my) {
return new MyCoreClass(my);
}

}

That works but is old-fashion and for every new dependency, we need to change the class constructor and the factory method.

To stay away from this, now lets see the solution!

The (trick) Solution

Fortunately, we have the Java spec for Contexts and Dependency Injection — CDI — that free us of the coupling to vendor-specific definitions. This spec is under JSR-365 and defines a lot of improvements, but we will attempt for Inject annotation that works in the same way as Spring’s Autowired.

With Spring Boot we can use Inject instead of Autowired (thanks Spring), then we do not have to worry. Well, that is the question:

  • how can we turn our core classes into managed beans without architectural principles violation?

There is a very subtle technique (or trick) where we create our own annotation, @Beancare for example, and we just annotate it with the vendor-specific annotation. Then, our annotation inherits their characteristics and becomes, indirectly, what its parents are.

Using Spring Boot, if we want to use the @Service annotation in our core business classes we can do right like below:

package com.github.fabiojose.di;import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.stereotype.Component;/** my annotation to use at core classes **/
@Component

@Retention(RetentionPolicy.RUNTIME)
public @interface Beancare {
}

Now we are able to annotate the core class to turn it in a bean managed by Spring framework. This without any direct reference to spring’s annotations.

package com.github.fabiojose.di.core;/* See that? No stranger imports */import javax.inject.Inject;import com.github.fabiojose.di.Beancare;
import com.github.fabiojose.di.port.Dependency;
@Beancare
public class MyCoreClass {
@Inject
private Dependency my;
public void dummy() {
Object foo = my.bar();
// . . .
}
}

Know what’s the best of this approach? We do this with every single annotation of Spring framework. But, take care, do not use an annotation that does exist only in Spring and cannot be satisfied with another framework.

At runtime, the DI framework will recognize our @Beancare annotation as a proxy to the @Service annotation and will behave as if it were the annotation itself.

Spring vs. Weld vs. Micronaut Examples

Both examples have the same project structure and class names. With this will be so easy to compare them and get some insight about how Spring treats DI and how Weld does.

Micronaut has a good point here because all the references for DI are resolved at compile time, not at runtime. Well, an application made with Micronaut will run faster!

Let’s see a little comparison of their features for dependency injection — DI.

Dependency Injection Frameworks

Bonus

To put more fun on this little battle: what about Vert.x? It does not employ dependency injection out-of-the-box and the options with Weld aren’t good enough or outdated (like this) to use in production.

With Vert.x we implement the application just with four classes and they have low coupling, high cohesion, and the interaction between them goes through the event bus. They are here!

For example, let’s see how to implement MyCoreClass and the MyDependency classes using Vert.x framework:

MyCoreClass using Vert.x
MyDependency using Vert.x

See that? Simple, clean and direct.

That’s all folks! See all of you in the next article! Leave some claps! Thanks for your time to read these lines!

Get all sources at Github!

References

--

--