DeepakBhalla

Dependency Injection - Why I learned to love constructor injection

By Deepak Bhalla Wed 28 March 2018 5 min read

Pretty much every form of dependency injection has at least two 'methods' of dependency injection. Either, field injection which looks a little something like this:

class DummyService {

  @Inject 
  private UserRepository userRepository;

  @Inject
  private UserService userService;

  public User accessRepository() {
    userRepository.doSomething();
  }
}

Or constructor injection which looks like this:

class DummyService {

  private UserRepository userRepository;
  private UserService userService;

  @Inject
  DummyService(UserRepository userRespository, UserService userService) {
    this.userRepository = userRepository;
    this.userService = userService;
  }

  public User accessRepository() {
    userRepository.doSomething();
  }
}

You might use something like @Autowired in place of @Inject or an equivalent.

The Problem with Constructor Injection

Whats immediately obvious is that field injection produces very little boilerplate code when compared to constructor injection 1. You'll also notice both versions have private fields like constructor injection so you basically get access to the same access modifier.

In fact the more you look in to constructor injection and begin to use it, the more pain points you begin to experience:

  1. You produce a metric tonne of boilerplate code (this is likely to be error prone unless automated)
  2. You'll find cases of circular dependencies. This is where a dependency (A) injects itself another dependency (B) where B also needs A as a dependency. This becomes much more obvious in constructor injection.

So why would you ever move away from field injection?

The Problem with Field Injection

I've started doing TDD across all my projects. When you unit test you often don't do it in the context of your dependency injector (although you absolutely can - but tests are often slow). You find the moment you exit the dependency injectors context (Called the ApplicationContext context in Spring) and want to use your classes without dependency injection you run into a problem.

Take this little snippet, you go to create a new Unit test and hit a snag:

  DummyService dummyService = new DummyService();
  dummyService.accessRepository(); // NullPointerException, IllegalArgumentException

In production this code runs fine because you are being hand held by your dependency injector but the moment you stray you begin to realise just how fragile your new classes and their methods are. Our class API did not inform us of the DummyService's core dependency usage and that resulted in errors.

Of course, we wrote the code so we know what it depended on. To fix this we just add a setter for each individual dependency and manaully wire them up in the test (with mocks, spys or instantiations). Like so:

  DummyService dummyService = new DummyService();
  dummService.setUserRepository(UserRepository());
  dummService.setUserService(UserService());
  dummyService.accessRepository();

Now we have a setter for each dependency where we might not have needed one. But wait, what about private final dependencies? We can't use those anymore.

Imagine you downloaded the SDK for a popular and unscrupulous social service and were told: 'In order to find out all the data about your end users simply use the methods of the DataMiner class'. But when you go to instantiate the class every method call gives you a NullPointerException. Hardly the API you expect - how are you going to win elections with that?

The problem with our API is that we've allowed the user use our classes in an 'invalid' state. The obvious solution, only allow users to instantiate our class in a valid state (with all its dependencies intact).

The Constructor Injection Use Case

Constructor Injection may be verbose but there are a few reasonable reasons for using it:

  1. You communicate mandatory dependencies publicly.
  2. When constructor injection gets overwhelming (or increasingly complex) it is a good sign that your classes are too complex.
    • When you start to have very large constructors with lots of dependencies its a form of code smell. You should be asking "Am I missing a possible abstraction in my model?", "Does this class really abide by the 'Single Responsibility Principle'?". If not then its time to refactor.
    • Seeing circular dependencies is usually a sign of tight coupling. In my experience, the solution to a circular dependency is to either move logic between classes until it makes sense or to create new classes to hold some logic based on the Single Responsibility Principle.
  3. You can create private final fields which force immutability. Final fields allow you to find mandatory dependencies (final) and optional ones (non final dependencies).
  4. Your code can operate independently of its context and you make testing easier and less error prone by communicating its mandatory dependencies.

When it comes down to which one I use it will always be constructor injection. It's a slight pain to use but pays of in producing cleaner APIs and smaller classes.

Aside: Dealing with boilerplate

Dealing with lots of boilerplate code is not that difficult with tools like Lombok.

@Component
@RequiredArgsConstructor(onConstructor = @__(@Inject))
public class DummyService {
    private UserRepository userRepository;
    private UserService userService;

    public User accessRepository() {
      userRepository.doSomething();
    }
}

You now have a fully functional constructor which uses all dependencies.

Why I Changed My Mind About Field Injection - Petri Kainulainen

Field Dependency Injection Considered Harmful - Vojtech Ruzicka

  1. No one really has a problem with boilerplate code in Java because we all have IDEs with live templates. You get to look super cool sumbitting a 1000 line PR despite only having 100 lines of business logic
Deepak Bhalla