Dependency Injection

Posted on: Jul 13, 2024 Written by DKP

Dependency Injection (DI) is a design pattern that allows an object to receive its dependencies from an external source rather than creating them itself. This pattern promotes loose coupling and makes your code more modular and testable, and less error prone.

I had an opportunity to refactor DI implemention at my workplace.

What is Dependency Injection?#

Dependency Injection is a technique where the dependencies (objects) of a class are provided (injected) by Spring, typically through constructors, setters, or interfaces. DI helps in separating the creation of dependencies from the business logic, thereby adhering to the principle of Inversion of Control (IoC).

Types of Dependency Injection#

  1. Constructor Injection: Dependencies are provided through a class constructor.
  2. Setter Injection: Dependencies are provided through setter methods.
  3. Field Injection: Dependencies are directly injected into the class fields using annotations.

Problem statement#

Our first implementation relied on a tightly coupled instantiation of services into a component

public class Subsidiary {
String name;
Integer partyId;
List<String> ratings;
public void updateSubsidiary(List<String> ratings) {
this.ratings = ratings;
// update ratings in db
}
}


public class UpdateSub {
private SubsidiaryService subsidiaryService;
public UpdateSub() {
this.subsidiaryService = new SubsidiaryService();
}

public void processUpdates(List<String> ratings) {
subsidiaryService.updateSubsidiary(ratings);
}
}

// Main.java
public class Main {
public static void main(String[] args) {
UpdateSub UpdateSub = new UpdateSub();
UpdateSub.processUpdates(["AA+", "BB-", "CCC"]);
}
}

As visible here, we are creating an object of SubsidiaryService inside UpdateSub and instantiating it.

Challenges with the above approach?#

  1. Tight coupling : Since we create and instantiate the object of SubService manually, it is coupled to the business logic of the UpdateSub itself. Should SubService be made into an interface, we'd need to update the logic inside UpdateSub to instantiate the impl of the interface.

  2. Challenge during testing : When writing junits, we don't need to actually create database connections, rather, just mock them. However, in this case, when we call UpdateSub, it'd end up updating the database connection and it won't be possible to mock it

Stage 1 of solution : Field injection#

To cater to the above limitations, we decided to implement dependency injection. But we decided to go with a type called field injection.

As the name suggests, we inject dependencies as a field of the class.

In code, it looked something like this

public class UpdateSub {
@Autowired
private SubsidiaryService subsidiaryService;
public UpdateSub() {
}

public void processUpdates(List<String> ratings) {
subsidiaryService.updateSubsidiary(ratings);
}
}

Just by writing the @Autowired annotation, we were able to inject the SubService dependency. Now, our junits could mock SubService using @InjectMocks or @Mock from Mockito.

Stage 2 : Limitations of @Autowired#

There are a couple of limitations with this approach

  1. You cannot make the injected service immutable

Since @Autowired will inject the service after the instantiation of UpdateSub, setting it as final will throw a compile time error. This is a challenge when we want to make sure our injections aren't overridden

  1. Chances of NPE

Again, owing to the above reason that injection happens after the root class instantiation, we found null pointer exceptions because we were trying to access the method of a service that spring hadn't yet been able to instantiate

  1. The partial accuracy of inject mocks

In the junits for UpdateSub, if we want to mock UpdateSub, we'd need to mock SubService and pass it along to UpdateSub. We achieved this by @Mocking SubService and @InjectMocking the mock into UpdateSub, but that didn't feel like the right approach

Solution - Constructor injection#

We therefore decided to move to Constructor injection

As the name suggests, we inject services into the constructor, rather than as a field.

Here is how it looked like in code :


public class UpdateSub {

SubsidiaryService subsidiaryService;

@Autowired
public UpdateSub(Subsidiaryservice subsidiaryService) {
System.out.println("Update Sub");
}

public void processUpdates(List<String> ratings) {
subsidiaryService.updateSubsidiary(ratings);
}
}

Here, we autowire the constructor and pass the dependency as a param. The advantage of this is that the dependency will be initialized when object of UpdateSub is created, thus solving the null pointer concerns of above

We can even do away with the explicit @Autowired annotation when there is just one constructor, as is the case above, since Spring handles the initiation during the constructor invocation.

This, considering all factors, seems to us, the most useful and recommended implementation of Dependency injection

Advantages of Dependency Injection#

To summarize, following are the advantages of DI

  1. Loose Coupling: DI reduces the coupling between classes, making the system more flexible and easier to maintain.
  2. Easier Testing: Dependencies can be easily mocked or stubbed during unit testing, leading to more isolated and reliable tests.
  3. Improved Code Readability: DI promotes clean code practices by clearly defining dependencies and their relationships.
  4. Enhanced Maintainability: Changes in dependencies require minimal changes in the dependent classes, making the system more maintainable.
  5. Increased Reusability: DI encourages the use of interfaces and abstract classes, enhancing the reusability of components.

Conclusion#

Dependency Injection is a powerful design pattern that improves the modularity, testability, and maintainability of your code.