Pragmatic Dependency Injection

Any non-trivial codebase out there benefits from Dependency Injection, for two main reasons:

  • to be able to pass in a proxy or a decorated version of the target dependency, or a mock for unit testing
  • to be able to break classes easier whenever you need to

However, there is an old debate around how to inject dependencies: via annotated fields or via constructor (no one really uses method injection). This article compares these techniques and then presents my preferred way: injection via an invisible constructor.

The code snippets use Spring annotations, but the topic is entirely applicable to both Quarkus and Micronaut.

Let's start with the least used technique, the method injection.

Method Injection

Besides field- and constructor-injection, there's one more option that no one really uses: setter injection. But since there's no restriction on the method name or number of arguments(!), the correct name for it is method injection.

There's really a single use-case for this technique: when you want to store in a field a dependency to an object X that you construct based on another bean Y that you inject. Like a sort of "local factory method". Why would you need that? Imagine you need to configure that object X in a highly-specific way because of how you use it in that class. Here's an example:

@Autowired
public void configureTxTemplate(PlatformTransactionManager txManager) {
    this.txTemplate = new TransactionTemplate(txManager);
    this.txTemplate.setPropagationBehaviorName("REQUIRES_NEW");
    this.txTemplate.setTimeout(1);
}

Of course, if the same configuration repeats in many places, you should prefer to defune a global @Bean definition.

So just forget about method injection, and let's continue with the two most used: field- and constructor-injection.

Field Injection

This should be familiar to you:

@Service
class SomeService {
    @Autowired
    private SomeDep someDep;  
}

People add "Spring Framework" to their resume after they understand this code.๐Ÿ˜ And honestly speaking, that's one the two things from Spring that you will see most in your code. Historically, after giving up XML configuration, @Autowired became the preferred way to inject dependencies, and that's why it's everywhere in the codebases today.

Compared to constructor-injection, these are the advantages of field-injection:

  • More Compact (no constructor)
  • Less effort to adjust dependencies (no need to update the constructor)
  • No Sonar complaints about a constructor that takes too many parameters (more than 7 is bad๐Ÿ˜œ)

I know what you're thinking now: 7? SEVEN? Seven dependencies for a Spring bean?! How about loosely coupling principle?

I agree that, in general, having a class with more than 3-5 dependencies makes the class harder to understand and unit-test. But there are valid exceptions to this rule, such as a thin Facade or ApplicationService that orchestrates the work of multiple other components. And I find it awkward to fight with Sonar over those.

In my training sessions I dive deeper and discuss the concept of use-case scoped dependencies: simply put, I am more concerned about the number of dependencies used by a public method and its private sub-functions, than of the total number of dependencies per class. But that's a bit controversial, and applying it clearly assumes a certain degree of maturity and consistency in the team.

Constructor Injection

Several years ago, the Spring team decided that constructor injection is recommended for our spring beans, as depicted by the hint in IntelliJ:

file

ALT-ENTER even offers to generate a constructor for you:

file

If we hit it, here's what we get:

@Service
class SomeService {
    private final SomeDep someDep;  
    public SomeService(SomeDep someDep) {
        this.someDep = someDep;
    }
}

By the way, since Spring 4.3 (out in 2016) @Autowired is not required on the constructor if the bean has a single constructor. The other frameworks, Quarkus and Micronaut, also don't require @Inject on the single constructor.

The advantages of constructor injection are:

  • Cyclic dependencies between Spring beans will cause the startup to fail ๐Ÿ‘Œ
  • Decoupling from Spring: you can manually inject your classes without defining setters or using reflection to populate the private fields
  • You can't forget to pass a required dependency when manually instantiating that class

The question is: when do you want to manually instantiate a spring bean? In a unit-test, of course. But here's a fact: almost every time I see a Spring bean unit-tested without starting up a Spring context, it's almost always created via Mockito's @InjectMocks, like this:

@ExtendWith(MockitoExtension.class) // or @RunWith(MockitoJUnitRunner.class)
public class SomeTest {
  @Mock
  private SomeDep someDep;
  
  @InjectMocks
  private SomeService someService;
  
  ...
}

And it turns out that Mockito is able to inject both into private fields as well as via a constructor. Using field or constructor injection therefore makes no difference when using @InjectMocks.

However, fine-grained tests slicing each class from all its dependencies (=excessive use of @InjectMocks) is a habit that inevitably leads to brittle and hard to maintain tests. Simply put, testing more than a single tiny isolated bit of code would allow more flexibility for refactoring without breaking those tests. But I will explain more in a future blog post.

It's time to see my favorite way to dependency-inject.

Invisible-Constructor Injection

Let's admit it, the constructor-injection does have some clear advantages. But is there a way to also get the advantages of field-injection?

In other words, I don't like that every time I add/remove a dependency, I have to adjust the constructor. And honestly, I don't like that boilerplate constructor at all. As a matter a fact, other modern languages out there like Scala, Kotlin or TypeScript offer a shorthand for avoiding this boilerplate this.x = x; assignments in constructors.

So, let's get rid of this idio(t)matic code, shall we?

@RequiredArgsConstructor
@Service
class SomeService {
  private final SomeDep someDep;
  ...
}

This is how all my Spring components look like, and I've seen increasingly more teams adopting this pragmatic style.

The @RequiredArgsConstructor tells Lombok to add to the compiled bytecode a constructor assigning the final fields. Why do I consider this a safe feature to use in your code? Because most Java developers will immediately get the point: if there are final fields and no constructor, then must be a generated one added later on in the bytecode. Plus, you donโ€™t often call that constructor yourself, so why not?

If you try this out, you will see how easy it is to add and remove a dependency: you just have to add/remove a final field, and the generated constructor is automatically updated. Oh, and Sonar canโ€™t have anything to complain about, as thereโ€™s really no offending source code to accuse.

So we got all the all the benefits of both constructor- and field-injection.

Breaking a class in two becomes a pleasure now, roughtly following these steps:

  1. move the methods you want to take out
  2. copy all private final fields
  3. delete all grayed out fields (unneeded dependencies)

Done. ๐Ÿ™‚ No need to be concerned about constructors anymore.

Pro Tip:  IntelliJ offers a nice way of moving methods from a class to one of its dependencies using "Move" refactor - give it a try.

In addition, adding a @Slf4j on a class will define the classic logger in the bytecode, to avoid writing lines like this everywhere:

private static final Logger log = LoggerFactory.getLogger(SomeService.class);

In my talk about IntelliJ tips and Tricks I also show a way to generate a live template to write this line for you, in case Lombok was banned by your tech lead/architect.

There's one last point to discuss: how do we inject configuration properties, like (in Spring):

@Value("${in.folder}")
private File inFolder;

You have two options:

  • let that field non-final and add a setter to use from tests, or
  • mark the field as final and instruct Lombok to copy the annotation from field to the generated constructor, by adding to src/main/java/lombok.config the following line:
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value

Conclusion

  • Inject dependencies via constructor
  • Consider hiding that constructor using @RequiredArgsConstructor from Lombok
  • Keep the number of dependencies of a class less then 3-5
  • Thin Facades may be the exceptions to this rule
  • Inject a log using @Slf4j
  • Annotations can be propagated from fields to constructor

Did I miss anything? Do you see it differently? Please let me know in the comments.

Why does it matter?

Because most business logic is still typically implemented in Services. That's where most of us live. And our Services, as well as the entire Universe, comply with The Second Law of Thermodynamics - their entropy (chaos) is always increasing, in our case due to bugfixes and added requirements. It is our duty to continuously break our logic classes into smaller parts in order to control that growing chaos. And the ability to rapidly try out different ways to decompose a class into smaller bits is critical to find the most simple and maintainable design for the problem at hand.


History: this entire article originated from a single slide of my Design Patterns training, besides another 200 more slides ๐Ÿ™‚ But since it involved a lot of talking, here is a blog post to explain it all in details๐Ÿ˜‰.

Popular Posts

Leave a Reply

Your email address will not be published. Required fields are marked *