02 septiembre 2011

Some Java "dependency injection" bad practices

When Spring and other dependency injection frameworks appeared on the Java space, some of its goals where to be "non-invasive" and to facilitate unit testing (see this old Rod Johnson presentation).

But when DI frameworks started to take advantage of annotations introduced in Java 5, the term "non-invasive" became relative, why? Here are some reasons:

@Autowire and @Inject annotation in private fields/private constructors

Spring, CDI and Guice allows you to annotate private fields, to let the DI framework inject the appropriate instance. But this feature has two problematic consequences, lets see them in a example taken from the Jboss CDI documentation:

public class Checkout {
       private @Inject ShoppingCart cart;
}


Consequences:

  1. Is impossible to create a valid Checkout instance without using a framework that supports the @Inject annotation. 
  2. You would not see the dependency directly by looking into the Checkout public interface or JavaDocs.
(1) is bad because your code becomes more coupled with the DI framework. For example if you want to create an unit test that uses a Checkout instance, you have to set up the DI framework first.

There are annotations and test classes to make tests for Spring beans, but in my experience (after having to maintain application-context.xml files created and used by multiple tests) is an unnecessary complexity that could be removed if the code is not coupled with the DI framework.

Some people argue that (2) is good (for example: this Guice "best practice"). The idea behind that is that developers will do "bad things" if they can: instead of obtaining the required instance by using dependency injection, they will use the constructor.
But that has more to do with a communication problem, and should be resolved by using code reviews, pair programming and walkthroughs.

@Required
In the same tone as the @Inject for private fields comes the @Required annotation supported by Spring. That annotation allows you to write things like:

public class Shape {
       private Color color;
       @Required
       public void setColor(Color c) {
            color = c;
       }
       ....
}

Then the DI framework will give you an exception if the dependency is missing.

But what happens if you instantiate a Shape outside the DI framework? You don't know if you have to set the color or not.

A constructor is simple: no dependencies with the DI framework and comes free with the Java language :) (This part from the "famous" Martin Fowler article on DI, also talks about constructors and setters in DI frameworks)

Note for the reader: I wrote the last articles in English because I wanted to practice that language. But I think that is better to keep the blog posts in one language, and Spanish is my native language, that's why this will be my last post in English (at least in this blog).



No hay comentarios.:

Publicar un comentario