Beyond Injection of Concerns (IOC): Elimination of IOC in Business Logic Where It is Not Merited
Join the DZone community and get the full member experience.
Join For Free
The Spring Framework provides a comprehensive programming and configuration model for modern Java-based enterprise applications - on any kind of deployment platform. A key element of Spring is infrastructural support at the application level: Spring focuses on the "plumbing" of enterprise applications so that teams can focus on application-level business logic, without unnecessary ties to specific deployment environments.
One of the commonly used features of Spring is Injection of Concerns (IOC). Injection of concerns allows for the reuse of boiler plate code by allowing client code to be “injected” into the shared logic. This is similar to (or some would say the same as) Inversion of Control (IOC) or Dependency Injection (see Koirala, Shivprasad, Dependency Injection (DI) vs. Inversion of Control (IOC) [2]). Injection, however, is not without cost and in many cases this cost comes without benefit. Injection should not be routinely used in cases where no benefit can be clearly demonstrated. This means that in most cases the writers of business logic should not use injection. We will demonstrate this with a simple example. This example is a simple application but the principles demonstrated here are also applicable to all applications, especially large-scale applications including large-scale web applications.
The entire project for this example, including all source code is available at:
https://svn.code.sf.net/p/springexample/code/tags/release_1_0_0/
For this example we will start with a handful of services that provide information about fruit. These services will be used in a simple plane old Java object (POJO) example and in a Spring-wired example. The services we use will provide information about the cost, price, location, and available types for a given fruit. These services will be used by an action class that provides a data value object (DVO) that represents the data available for the selected fruit.
The source code for the fruit cost service looks like this:
public class FruitCostService { public int getCost(Fruit fruit) { int rtn = 0; switch (fruit) { case APPLE: rtn = 20; break; case MANGO: rtn = 40; break; case MELON: rtn = 10; break; case BANANA: rtn = 25; break; default: throw new RuntimeException(); } return rtn; } }
The source code for the fruit price service looks like this:
public class FruitPriceService { public int getCost(Fruit fruit) { int rtn = 0; switch (fruit) { case APPLE: rtn = 22; break; case MANGO: rtn = 44; break; case MELON: rtn = 11; break; case BANANA: rtn = 28; break; default: throw new RuntimeException(); } return rtn; } }
The source code for the location service looks like this:
public class FruitLocationService { public Location getLocation(Fruit fruit) { Location rtn = null; switch (fruit) { case APPLE: rtn = Location.BIN_1; break; case MANGO: rtn = Location.BIN_2; break; case MELON: rtn = Location.BIN_3; break; case BANANA: rtn = Location.BIN_4; break; } return rtn; } }
The source code for the fruit type service looks like this (some of the business logic has been excluded for brevity):
public class FruitTypeService { public List<String> getTypes(Fruit fruit) { List<String> rtn = null; switch (fruit) { case APPLE: rtn = getApples(); break; case MANGO: rtn = getMangos(); break; case MELON: rtn = getMelons(); break; case BANANA: rtn = getBananas(); break; default: rtn = getUnknown(); break; } return rtn; } }
For our example we will be retrieving data from the services and presenting this data in a single data value object (DVO) as shown below.
public class FruitDvo { private Fruit fruit; private Location location; private int cost; private int price; private List<String> types; public Fruit getFruit() { return fruit; } public void setFruit(Fruit fruit) { this.fruit = fruit; } public Location getLocation() { return location; } public void setLocation(Location location) { this.location = location; } public int getCost() { return cost; } public void setCost(int cost) { this.cost = cost; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public List<String> getTypes() { return types; } public void setTypes(List<String> types) { this.types = types; } }
From the service classes shown above it is very easy to create an action class that gets data for a given fruit type using simple Java coding as shown below.
public class FruitAction { public FruitDvo getFruitInfo(Fruit fruit) { FruitDvo rtn = new FruitDvo(); rtn.setFruit(fruit); rtn.setCost(new FruitCostService().getCost(fruit)); rtn.setLocation(new FruitLocationService().getLocation(fruit)); rtn.setPrice(new FruitPriceService().getCost(fruit)); rtn.setTypes(new FruitTypeService().getTypes(fruit)); return rtn; } }
There are a few ways this example can be Spring-wired. For this example we will do a simple XML wiring for the services and wire the services into the action class through a constructor.
To do this we will first modify the action class to allow the services to be injected. We could have also made the services member variables of the class and done our injection through those member variables either in the XML or through annotations.
public class FruitAction { // // instance variables // private FruitCostServiceDefinition fruitCostService; private FruitLocationServiceDefinition fruitLocationService; private FruitPriceServiceDefinition fruitPriceService; private FruitTypeServiceDefinition fruitTypeService; // // constructor // public FruitAction( FruitCostServiceDefinition fruitCostService, FruitLocationServiceDefinition fruitLocationService, FruitPriceServiceDefinition fruitPriceService, FruitTypeServiceDefinition fruitTypeService) { super(); this.fruitCostService = fruitCostService; this.fruitLocationService = fruitLocationService; this.fruitPriceService = fruitPriceService; this.fruitTypeService = fruitTypeService; } /** * Method to get data for fruit. */ public FruitDvo getFruitInfo(Fruit fruit) { FruitDvo rtn = new FruitDvo(); rtn.setFruit(fruit); rtn.setCost(fruitCostService.getCost(fruit)); rtn.setLocation(fruitLocationService.getLocation(fruit)); rtn.setPrice(fruitPriceService.getPrice(fruit)); rtn.setTypes(fruitTypeService.getTypes(fruit)); return rtn; } }
The services and action class can now be Spring-wired in XML according to the following:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- original implementation --> <bean id="fruitCostService" class="org.yaorma.simplespringexample.service.fruit.FruitCostService" /> <bean id="fruitLocationService" class="org.yaorma.simplespringexample.service.fruit.FruitLocationService" /> <bean id="fruitPriceService" class="org.yaorma.simplespringexample.service.fruit.FruitPriceService" /> <bean id="fruitTypeService" class="org.yaorma.simplespringexample.service.fruit.FruitTypeService" /> <bean id="fruitAction" class="org.yaorma.simplespringexample.action.fruit.spring.FruitAction"> <constructor-arg name="fruitCostService" ref="fruitCostService" /> <constructor-arg name="fruitLocationService" ref="fruitLocationService" /> <constructor-arg name="fruitPriceService" ref="fruitPriceService" /> <constructor-arg name="fruitTypeService" ref="fruitTypeService" /> </bean> </beans>
There are basically two problems that come from adding Spring wiring to this example. The first is the increase in the amount of code that is required to perform the same task. In our POJO example all of the code required to accomplish the task of getting the information we want by the action class is completed in that class. The addition of the Spring wiring introduces several lines of XML.
This may not seem substantial in this small example. This example, however, only uses a single action class. The production web applications we have developed often have hundreds of action classes and hundreds of services. The addition of even a handful of lines of code for each service and for each action leads to the creation of thousands of lines of XML that invariably become difficult to maintain.
The second problem that arises from the introduction of Spring wiring to this example is the violation of encapsulation we introduce. In the action class of the POJO example, all of the work, resources, and dependencies required to accomplish the task of getting data for a given fruit are encapsulated in that class.
When we introduce Spring wiring to that class it becomes necessary to expose the resources used by that class to the public interface of that class either by creating member variables and public setters for those resources or (as we did in this example) by requiring the client code to provide these resources in the constructor.
One of the common reasons sited for using Spring wiring is reuse and, specifically, the ability to easily or simply replace one implementation with another.
For example, our existing Spring-wired FruitAction can be reused and rewired to use an alternative implementation of each of the services. This type of substitution is especially useful in situations where an entire layer or system of an application needs to be replaced and must be considered and planned for when the probability of this type of substitution is foreseen or expected. For example, this might be a consideration in a project where one database implementation was used initially (e.g., MySQL) and a more robust database implementation was expected as the project matured (e.g., Oracle), or when one framework might be replaced by another (e.g., Cayenne, vs. Hibernate), etc.
For this example we created alternate implementations of the service classes in the org.yaorma.simplespringexample.service.fruit2 package. These services can be injected into the existing FruitAction class by adding the following to the XML that defines our Spring wiring as shown below.
<!-- second implementation --> <bean id="specialFruitCostService" class="org.yaorma.simplespringexample.service.fruit.FruitCostService" /> <bean id="specialFruitLocationService" class="org.yaorma.simplespringexample.service.fruit.FruitLocationService" /> <bean id="specialFruitPriceService" class="org.yaorma.simplespringexample.service.fruit.FruitPriceService" /> <bean id="specialFruitTypeService" class="org.yaorma.simplespringexample.service.fruit.FruitTypeService" /> <bean id="specialFruitAction" class="org.yaorma.simplespringexample.action.fruit.spring.FruitAction"> <constructor-arg name="fruitCostService" ref="specialFruitCostService" /> <constructor-arg name="fruitLocationService" ref="specialFruitLocationService" /> <constructor-arg name="fruitPriceService" ref="specialFruitPriceService" /> <constructor-arg name="fruitTypeService" ref="specialFruitTypeService" /> </bean>
Our rewired class can now be called from a Java client as shown below.
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); FruitAction action = (FruitAction) context.getBean("specialFruitAction"); test(action); }
Thus, we can reuse our existing FruitAction class, not change any Java code (except for the creation of the additional services), and thereby completely change the services used by the FruitAction class. The only modifications required to accomplish this were to update the XML that defines the Spring wiring. This is often cited as one of the great advantages of Injection of concerns. Before we accept this, lets look at what is required to fulfill the same requirement without using Spring.
To create a FruitAction class that uses our new services without using Injection of Concerns we can simply create a new action class that uses the new services.
public FruitDvo getFruitInfo(Fruit fruit) { FruitDvo rtn = new FruitDvo(); rtn.setFruit(fruit); rtn.setCost(new SpecialFruitCostService().getCost(fruit)); rtn.setLocation(new SpecialFruitLocationService().getLocation(fruit)); rtn.setPrice(new SpecialFruitPriceService().getPrice(fruit)); rtn.setTypes(new SpecialFruitTypeService().getTypes(fruit)); return rtn; }
Therefore, we are able to achieve in a handful of lines of Java code (one line for each of the services we wish to replace) that which required approximately the same amount of XML wiring to achieve the same result using Spring Injection of Concerns.
In conclusion, unless the developer can rationalize the introduction of Spring wiring into a class, it is not appropriate to use Spring wiring. The rationalization for using Spring wiring must be concrete and demonstrable. Rationalizations such as reuse or the ability to change implementations are not sufficient unless more specific benefits can be identified.
This conclusion is consistent with but not identical to the assertion of Abel Avram that adding Spring lowers the quality of JEE applications [3] (based on the work of Jay Sappidi [4]).
Sappidi states “Java applications must not be based on personal preferences and should instead be carefully evaluated and rationalized against objective facts and business requirements.” This analysis is often performed in big picture decisions: “Do we need to use Spring, Struts, Hibernate, Cayenne, etc. for this project?” but this type of analysis also needs to be made on a daily basis during the implementation phase of the software development process: “Do I need to use Spring injection in this situation? Does using Spring injection to achieve this task increase the quality of the code? Can I specifically give a concrete example of how the code is improved through the use of spring in this situation?” In all cases in which the developer does not have a concrete and specific answer to this final question, Spring injection should not be used.
References:
[1] http://www.springsource.org/spring-framework
[2] Koirala, Shivprasad, Dependency Injection (DI) vs. Inversion of Control (IOC) http://www.codeproject.com/Articles/592372/Dependency-Injection-DI-vs-Inversion-of-Control-IO
[3] Avram, Abel, CAST: Adding Spring Lowers the Quality of JEE Applications, http://www.infoq.com/news/2013/02/CAST-JEE-Spring-Quality
[4] Sappidi, Jay, Java Applications and Coffee: The Variations are Endless, http://www.castsoftware.com/news-events/event/appmarq-java-applications
Opinions expressed by DZone contributors are their own.
Trending
-
SRE vs. DevOps
-
5 Key Concepts for MQTT Broker in Sparkplug Specification
-
Getting Started With the YugabyteDB Managed REST API
-
10 Traits That Separate the Best Devs From the Crowd
Comments