Wednesday, 20 March 2013

Spring Integration vs plain Java code

Adding frameworks to our system may solve some problems and cause too much complexity at the same time.

Let's imagine a simple flow:


We have a MessageRouter object that routes received message to either XMLHandler or JSONHandler. The processed message in both handlers are then passed to Persister that is storing the message.

Modelling it is not that difficult. Here's our MessageRouter class:

public class MessageRouter {
private XMLHandler xmlHandler = new XMLHandler();
private JSONHandler jsonHandler = new JSONHandler();
public void route(String message) {
if (message.startsWith("<?xml")) {
xmlHandler.process(message);
} else {
jsonHandler.process(message);
}
}
}
Handlers:
public class XMLHandler {
private Persister persister = new Persister();
public void process(String message) {
System.out.println("Processing xml message: " + message);
persister.persist(message);
}
}

public class JSONHandler {
private Persister persister = new Persister();
public void process(String message) {
System.out.println("Processing json message: " + message);
persister.persist(message);
}
}
And finally persister:
public class Persister {
public void persist(String message) {
System.out.println("persisting " + message);
}
}
We can see it in action by running the following class:
public class App {
public static void main(String[] args) {
MessageRouter router = new MessageRouter();
router.route("{'json'}");
router.route("<?xml/>");
}
}
view raw AppPlain.java hosted with ❤ by GitHub
Everything seems fine here, but we have a problem - tight coupling.
Message router knows about handlers and handlers know about persister.
We would gain loose coupling and high cohesion if they weren't aware of each other which would automatically make them concentrate on one task only.

Spring Integration can help us achieve this.
It is a separate module of Spring, enabling lightweight messaging within application and supporting Enterprise Integration Patterns.

All the arrows from the above picture will be represented as channels.

The XML Spring context configuration for the channels looks pretty straightforward:

<int:channel id="messageChannel"/>
<int:channel id="XMLChannel"/>
<int:channel id="JSONChannel"/>
<int:channel id="persisterChannel"/>
view raw channels.xml hosted with ❤ by GitHub
Actually it's not even required as Spring can create them by default.

Apart from the channels, our MessageRouter only role is to return the channel name to which the message will be passed.
@Component
public class MessageRouter {
@Router
public String route(String message) {
return message.startsWith("<?xml") ? "XMLChannel" : "JSONChannel";
}
}
Also the handlers and persister need to become Service Activators (their methods will be invoked when the message will go through the channel).
@Component
public class JSONHandler {
@ServiceActivator
public String process(String message) {
System.out.println("Processing json message: " + message);
return message;
}
}
@Component
public class XMLHandler {
@ServiceActivator
public String process(String message) {
System.out.println("Processing xml message: " + message);
return message;
}
}
view raw XMLHandler.java hosted with ❤ by GitHub
@Component
public class Persister {
@ServiceActivator
public void persist(String message) {
System.out.println("persisting " + message);
}
}
view raw Persister.java hosted with ❤ by GitHub
The config for those:
<int:router input-channel="messageChannel" ref="messageRouter"/>
<int:service-activator input-channel="XMLChannel" output-channel="persisterChannel" ref="XMLHandler"/>
<int:service-activator input-channel="JSONChannel" output-channel="persisterChannel" ref="JSONHandler"/>
<int:service-activator input-channel="persisterChannel" ref="persister"/>
To see it in action we can run the following class:
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
MessageChannel channel = context.getBean("messageChannel", MessageChannel.class);
channel.send(MessageBuilder.withPayload("{'json'}").build());
channel.send(MessageBuilder.withPayload("<?xml/>").build());
}
}
Notice that the code is concise and simple.
The components do not depend on each other.
What is more, if we wanted to modify the flow and move Persister component in front of the MessageRouter, we would need to change the xml config to:

<int:service-activator input-channel="XMLChannel" output-channel="nullChannel" ref="XMLHandler"/>
<int:service-activator input-channel="JSONChannel" output-channel="nullChannel" ref="JSONHandler"/>
<int:service-activator input-channel="persisterChannel" output-channel="messageChannel" ref="persister"/>
Changing the flow in the first version that is using plain Java code would require much more modifications.

Nevertheless we increased the complexity of our application. Now we depend on a framework and we need to maintain additional configuration in an XML file.

Another big disadvantage is testing. We could easily unit test the previous code using mocks. Now we need to test the Java code and Spring Integration configuration as well, which is not that simple.
I'll show how to do it in the next post.