Sunday, May 17, 2015

Using Spring DI to break dependency injection pattern

No doubt, Spring is a flexible framework for building web apps, but in some cases more flexible than it should be! In this article I focus on Spring DI, the dependency injection framework and the ability it gives you to break the idea behind the dependency injection pattern.

To expose the issue, I use the famous message-printer toy app, with Spring v4.1.6.RELEASE, orchestrated by spring-boot v1.2.3.RELEASE. The code I use in this post can be found on github.

Domain Modelling


Let's say we have a  MessageService interface and two dummy domain class implementations, MessagePrinterImpl1, MessagePrinterImpl2 as shown:


Controller


We will need also a dummy controller that maps every HTTP request to a method that just logs the message in the console.

And of course a main method to run the app.
Run the app with maven or gradle:
- maven:  mvn spring-boot:run
- gradle:  ./gradlew bootRun

Visit http://localhost:8080 and you get the following log in your console which verifies that an MessagePrinterImpl1 object was injected as a MessageService:

What is wrong


Let's explain the Spring DI magic that happens here.
PrinterController has a field of type MessageService that needs to be injected. Since there are two MessageService implementations in the classpath, Spring DI will need a way to decide which one to inject. One way to do this is the @Resource(name = "impl1") annotation, which asks for the bean named "impl1" to be injected. Spring will search in the application context to find a bean named "impl1" which is of type MessageService. As we have annotated MessagePrinterImpl1 with @Component(value = "impl1"), an instance of this class will be injected.

In short, what we have achieved is to couple the PrinterController class with a concrete implementation of MessageService and we did that by using dependency injection... But hold on a sec, the goal of dependency injection pattern is to decouple code, directly the opposite than what we have done.

Solution


In order to use DI pattern for all the good reasons it was invented, you need to write a Config class that will bind interfaces to concrete implementations. By the way this is very similar with Guice Modules (thank God, Spring got rid of ugly xml config files!).

It is important that the @Bean annotation names the instantiated bean with service, as this is the name of the field we want to inject to. Alternatively, one can rename the method provider to service. This way, we enforce a clear separation between the business logic (PrinterController) and the instantiation of object graph (PrinterConfig). Testing this app is then made easy by providing a different Config class which binds MessageService to a mock implementation.

Conclusion


The cause of the problem is that Spring DI annotations are so flexible that even allow you to break the dependency injection idea.The lesson to be learned is to use this flexibility with caution. As Spring does not necessarily enforce the developer to follow the spirit besides the format of design patterns, it's the developer's responsibility to enforce them.

No comments:

Post a Comment