POJO Message Handling with Spring Integration

POJO Message Handling with Spring Integration

By Robert Adelmann, OCI Software Engineer

April 2013


Introduction

You have just completed an enhancement to your application to allow it to retrieve data from a service either hosted within the enterprise or hosted by a vendor. Since there was only one service to call you were able to extend your application by writing some code that would gather the data required for the request, transform it into the format required by the service, send the request to the service over its' supported transport protocol, and then map the results back to your application's format. Everything works and your customer is happy with the results.

As time goes on the business requirements for your application changes and now you need to integrate with additional services. This time you might have to call a service that requires a different data format and a different transport protocol. Depending on the results you might have to call another service. The original code you wrote now has to handle multiple communication protocols, data formats, and make routing decisions all while being able to recover from the failure of any request. 

Welcome to the domain of Enterprise Application Integration! 

Solution

Obviously adding more code to handle the additional requirements is going to be a lot of work. What you need is a framework that will take care of the underlying functions required for enterprise application integration and allow you to focus on the business logic of your application.

Spring Integration was created to help solve these kinds of problems. Spring Integration is basically an enterprise service bus embedded in a Spring Context that lets you seamlessly connect your business logic to services through messaging. As with the Spring Framework you can use XML schemas for configuration, take advantage of POJO-friendly APIs, and of course use dependency injection. It has almost no barrier to entry, and is conceptually going to be simpler than any "I'll just write it myself" solution.

The design of Spring Integration is based on enterprise integration patterns defined in the book "Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions" by Gregor Hohpe. These set of patterns have become the unofficial standard for Enterprise Service Bus implementations replacing the Java Business Integration (JSR 208) standard. ESB Open Source projects such as Mule and Apache Camel have based their design on these patterns.

Basic Components

The best way to explain what Spring Integration can do for you is to use it to create a simple messaging application. Before we get to that let's look at some of the main components of Spring Integration.

Message

A message is made up of a header and a payload. All message implementations in Spring Integration implement the Message interface.

  1. public interface Message<T> {
  2. MessageHeaders getHeaders();
  3. T getPayload();
  4. }

The message header is basically a java.util.Map instantiated as Map (String, Object). It's holds information about the message such as the message id, timestamp, correlation id, and return address. As a developer you are allowed to add your own values to the header.

The message payload can be any Java type. Implementations of the Message interface define the payload as a final variable that is set in the constructor. So if you want to change values you have to create a new message instance.

Since Spring Integration is designed to be POJO-friendly you seldom have to act directly on a message object in your code. This will be become apparent in the example application we will create later in this article.

Message Channel

Now that we have a message we need a way to send it somewhere. The message channel provides the first part of this functionality. Looking at the MessageChannel interface we can see that a channel has no information about where the message should go.

  1. public interface MessageChannel {
  2. boolean send(Message<?> message);
  3. boolean send(Message<?> message, long timeout);
  4. }

Therefore a message channel is really just a queue for holding messages. For a message to complete its' journey a channel must have another component subscribe to it. These types of components are referred to as message endpoints.

Message Endpoint

In Spring Integration Message Endpoints are the components that consume messages from message channels. Spring Integration provides many different types of Message Endpoint implementations. The one thing they all have in common is that they must be configured for an input channel. If the Message Endpoint needs to send a message to another Message Endpoint then it must be configured for an output channel.

To better explain what a Message Endpoint is let's examine some of Message Endpoints provided by Spring Integration.

Transformer

The purpose of a Message Transformer is to take a message from a channel, convert the message's payload to another format, and then send the transformed payload to another channel.

Filter

A Message Filter allows you to stop messages from being passed to a channel based on some criteria.

Router

Routers consume messages from a channel and forward each consumed message to one or more different Message Channel depending on a set of conditions.

Splitter

A Splitter is an Endpoint that takes a message off of a channel, then splits it into multiple messages, and sends the messages to an output channel.

Aggregator

The Aggregator Endpoint receives multiple messages from a channel that have been sent from a Splitter Endpoint. It will execute its' operation when it has determined that it has received all of the messages based on a completion strategy.

Service Activator

A Service Activator is a generic endpoint that allows you to add business logic to the messaging system. The input Message Channel must be configured, and if the service method to be invoked is capable of returning a value, an output Message Channel must also be provided.

Channel Adapter

A Channel Adapter is used to interface with systems that are external to the Spring Context that your application is running under. An Inbound Channel Adapter will receive a message from an external system and place it on a channel. An Outbound Channel Adapter will take a message from a channel and send it to an external system.

Messaging Gateway

This component can be used to directly connect your business logic to the Spring Integration Context. Basically you define a Java Interface that has a method that accepts a parameter of the type of the object you wish to place on a channel. The interface is configured in the Spring Integration Context as a Gateway with a request channel specified. If the method has a return value then a reply channel must also be specified.

Example

To demonstrate how easy it is to use Spring Integration we will develop a small messaging application.

The goal of this messaging application is to connect three separate systems by using messaging. The systems that we will integrate are the following:

  1. A client application such as a loan origination system.
  2. An identity verification application.
  3. A credit score application.

Basic Flow

The client application needs to retrieve a credit score for a customer but it must first verify their identity by calling the Identity Verification Service. If the customer's identity is verified then the Credit Bureau Service can be called and a score is returned to the client. If the customer's identity cannot be verified the call to the Credit Bureau Service is skipped and a failure response is returned to the client.

Result

The completed example will demonstrate the use of the following Spring Integration components:

  1. Message Gateway
  2. Service Activator
  3. Router
  4. Transformer
  5. Channels

Getting Started

This example application will use Maven as the build system so our first step is to create a POM file and add the dependencies for Spring Integration.

  1. <project>
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>com.oci.esb</groupId>
  4. <artifactId>SpringIntegrationExample</artifactId>
  5. <version>1.0.0.BUILD-SNAPSHOT</version>
  6. <packaging>jar</packaging>
  7. <name>Spring Integration Example</name>
  8.  
  9. ...
  10. <dependencies>
  11. <!-- Spring Integration -->
  12. <dependency>
  13. <groupId>org.springframework.integration</groupId>
  14. <artifactId>spring-integration-core</artifactId>
  15. <version>2.2.0.RELEASE</version>
  16. </dependency>
  17. ...
  18.  
  19. </dependencies>
  20. </project>

The next step to using Spring Integration is enabling its' usage in a Spring Context. To do this we need to create a standard Spring configuration file and add the Spring Integration namespace as highlighted below.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:int="http://www.springframework.org/schema/integration"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/integration
  8. http://www.springframework.org/schema/integration/spring-integration.xsd">
  9.  
  10. </beans>

That is it for the setup. All that is left to do now is create some Java classes and configure them in the Spring context.

Time to Code

First lets code the objects that will contain the request and response data.

The Customer class is just a simple POJO that contains the customer information and will be used as our first request message.

  1. package com.oci.esb.domain;
  2.  
  3. public class Customer {
  4.     private String lastName;
  5.     private String firstName;
  6.     private String ssn;
  7.     private String address;
  8.     private String city;
  9.     private String state;
  10.     private String zip;
  11.     boolean identityVerified;
  12.     int creditScore;
  13.      
  14.     public String getLastName() {
  15.         return lastName;
  16.     }
  17.     public void setLastName(String lastName) {
  18.         this.lastName = lastName;
  19.     }
  20.     ..
  21.     additional getters and setters
  22.     ...
  23. }

The CreditBureauRequest class will be returned from the messaging system and will contain the customer's score.

  1. package com.oci.esb.domain;
  2.  
  3. public class CreditBureauRequest {
  4. private String name;
  5. private String ssn;
  6. private int score;
  7. private String status;
  8.  
  9. public String getName() {
  10. return name;
  11. }
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. public String getSsn() {
  16. return ssn;
  17. }
  18. public void setSsn(String ssn) {
  19. this.ssn = ssn;
  20. }
  21. public int getScore() {
  22. return score;
  23. }
  24. public void setScore(int score) {
  25. this.score = score;
  26. }
  27. public void setStatus(String status) {
  28. this.status = status;
  29. }
  30. public String getStatus() {
  31. return status;
  32. }
  33. }

Before we create the client code we need a way to place the Customer object onto the messaging system. For this will create a Java interface with a method that takes the Customer class as a parameter.

  1. package com.oci.esb.service;
  2.  
  3. import com.oci.esb.domain.CreditBureauRequest;
  4. import com.oci.esb.domain.Customer;
  5.  
  6. public interface ServiceGateway {
  7.  
  8.     public CreditBureauRequest send(Customer customer);
  9.  
  10. }

We do not have to implement this interface in our code. All we have to do is define it as a Messaging Gateway in the Spring Context and Spring Integration will automatically provide the implementation at runtime.

We also need to define a message channel for the gateway to send the incoming message to next endpoint and another message channel that will allow messages that have completed processing to be passed back to the Messaging Gateways reply channel and therefore back to the client application.

 
  1. <int:channel id="requestChannel"/>
  2.  
  3. <int:channel id="replyChannel"/>
  4.  
  5. <int:gateway id="gateway"
  6.         default-request-timeout="5000"
  7.         default-reply-timeout="5000"
  8.         default-request-channel="requestChannel"
  9.         default-reply-channel="replyChannel"
  10.         service-interface="com.oci.esb.service.ServiceGateway">
  11.         <int:method name="send"/>
  12.          
  13. </int:gateway>

Now that we have an entry point to the messaging system we can proceed with the construction of the client code. For this demonstration we will use a JUnit test for the client.

The client loads a Spring context, injects the Messaging Gateway into the ServiceGateway interface, and then sends the Customer object to the messaging system by invoking the send method on the ServiceGateway interface.

  1. package com.oci.esb;
  2.  
  3. import static org.junit.Assert.*;
  4. import org.junit.Test;
  5. import org.springframework.context.ApplicationContext;
  6. import org.springframework.context.support.ClassPathXmlApplicationContext;
  7. import com.oci.esb.domain.CreditBureauRequest;
  8. import com.oci.esb.domain.Customer;
  9. import com.oci.esb.service.ServiceGateway;
  10.  
  11. public class ServiceGatewayTest {
  12.  
  13. @Test
  14. public void testServiceGateway() {
  15.  
  16. Customer customer = new Customer();
  17. customer.setFirstName("John");
  18. customer.setLastName("Doe");
  19. customer.setAddress("123 Main Street");
  20. customer.setCity("Saint Louis");
  21. customer.setState("MO");
  22. customer.setZip("63141");
  23. customer.setSsn("000-00-0000");
  24.  
  25. final ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/integration/spring-integration-context.xml", ServiceGatewayTest.class);
  26.  
  27. final ServiceGateway service = context.getBean(ServiceGateway.class);
  28.  
  29. CreditBureauRequest reply = service.send(customer);
  30. assertEquals("Expecting identity verified","SUCCESS", reply.getStatus());
  31. }
  32. }

Now that we have our request in the messaging system the first stop is the Identity Verification Service. For this demonstration we will create a class that has a verify method with the Customer class as a parameter. It's going to set the Customer's identity Verified field to true and return the Customer object.

If this were a real system this class would make a call to an external system.

  1. package com.oci.esb.service;
  2.  
  3. import com.oci.esb.domain.Customer;
  4.  
  5. public class IdentityVerificationService {
  6.  
  7. public Customer verify(Customer customer) {
  8. customer.setIdentityVerified(true);
  9. return customer;
  10. }
  11. }

We want to configure this class as a Service Activator so that it can consume the message sitting on the requestChannel. Since we have configured an output-channel, the Customer object returned by the verify method will become a message that is placed on the checkIdentityVerificationChannel.

  1. <int:channel id="checkIdentityVerificationChannel"/>
  2.  
  3. <int:service-activator id="identityVerificationServiceActivator"
  4. input-channel="requestChannel" output-channel="checkIdentityVerificationChannel"
  5. ref="identityVerificationService" method="verify" />
  6.  
  7. <bean id="identityVerificationService"
  8. class="com.oci.esb.service.IdentityVerificationService" />

We now want to check to see if the customer's identity was verified. If it was then we want to route the Customer object to the creditBureauTransformerChannel. If it was not the we will route the Customer object to the errorChannel.

  1. package com.oci.esb.service;
  2.  
  3. import com.oci.esb.domain.Customer;
  4.  
  5. public class Router1 {
  6. public String route(Customer customer) {
  7. return customer.isIdentityVerified() ? "creditBureauTransformerChannel" : "errorChannel";
  8. }
  9. }

To make the Router1 class a Router Endpoint we add the following configuration to the Spring Context.

  1. <int:channel id="creditBureauTransformerChannel"/>
  2. <int:channel id="errorChannel"/>
  3.  
  4. <int:router id="IdentityVerificationRouter" ref="router1"
  5. input-channel="checkIdentityVerificationChannel" />
  6.  
  7. <bean id="router1" class="com.oci.esb.service.Router1"/>

Now that customer's identity has been successfully verified we can now call the Credit Bureau Service to get their credit score. The one problem here is the Credit Bureau Service does not accept a Customer class as a parameter. So we create the CreditBureauRequestTransformer class to marshall the required data contained in the Customer object to the CreditBureauReport class.

  1. package com.oci.esb.service;
  2.  
  3. import com.oci.esb.domain.CreditBureauRequest;
  4. import com.oci.esb.domain.Customer;
  5.  
  6. public class CreditBureauRequestTransformer {
  7. public CreditBureauRequest transform(Customer customer) {
  8. CreditBureauRequest creditBureauRequest = new CreditBureauRequest();
  9. creditBureauRequest.setName(customer.getLastName() + ", " + customer.getFirstName());
  10. creditBureauRequest.setSsn(customer.getSsn());
  11. return creditBureauRequest;
  12. }
  13. }

To make this class a Transformer Endpoint we add the following configuration to the Spring Context.

  1. <int:channel id="creditBureauRequestChannel"/>
  2.  
  3. <int:transformer
  4. input-channel="creditBureauTransformerChannel"
  5. output-channel="creditBureauRequestChannel"
  6. ref="creditBureauRequestTransformer"/>
  7.  
  8. <bean id="creditBureauRequestTransformer"
  9. class="com.oci.esb.service.CreditBureauRequestTransformer" //>

We have finally made it to the Credit Bureau Service. All we have to do is create a class with a method that accepts a CreditBureauRequest object. Since we want to score return back to the client we make the method return a CreditBureauRequest object.

  1. package com.oci.esb.service;
  2.  
  3. import com.oci.esb.domain.CreditBureauRequest;
  4.  
  5. public class CreditBureauService {
  6. public CreditBureauRequest getScore(CreditBureauRequest creditBureauRequest) {
  7. creditBureauRequest.setScore(555);
  8. creditBureauRequest.setStatus("SUCCESS");
  9. return creditBureauRequest;
  10. }
  11. }

To make this class consume the message containing the CreditBureauRequest object we will make it a Service Activator.

  1. <int:service-activator id="creditBureauServiceActivator"
  2. input-channel="creditBureauRequestChannel" output-channel="replyChannel"
  3. ref="creditBureauService" method="getScore" />
  4.  
  5. <bean id="creditBureauService"
  6. class="com.oci.esb.service.CreditBureauService" />

Since we configured the Service Activator's output channel to be the replyChannel of the MessageGateway the CreditBureauRequest will be returned back to the client. The request is now completed.

Summary

The example app we create demonstrated some of the features that make Spring Integration a strong product.

  1. Easy to setup
  2. No reliance on proprietary APIs.
  3. Easy configuration.
  4. Integrates well with the Spring Framework.

Just because it's easy to use don't be fooled into thinking that it is not a robust product. It does so much more than what we have touched on in this article. A great way to learn more is to look through the Spring Integration Samples that are hosted on Github.

References

secret