Open source Java projects: Spring Integration

Develop a robust message-passing architecture with Spring Integration

1 2 3 4 Page 4
Page 4 of 4

Stitching applications together with Spring Integration

You have learned how messages, channels, adapters, and gateways work together to abstract message consumers from message producers and you've seen how to integrate ActiveMQ and JMS into a Spring Integration example. The real power in using Spring Integration is when you start thinking about building modular and reusable services and then stitching together application workflows using Spring Integration. For example, an event-driven application could publish a message with either a lightweight payload (that identified the system that generated the event and the ID(s) of the affected resources) or a heavyweight payload (that contained the entire contents of the modified resource).

The benefit to using a lightweight payload is that if the resource changes several times, you do not need to be worried about the content of the heavyweight payload: you go back to the system of record, which is the source of truth, and ask it for the latest version of the affected resource. The drawback, of course, is that every time a system generates an event, all listeners are going to call that system back, which may add too much load. The compromise, therefore, is that the system can generate a heavyweight payload and forbid listeners from calling it back. This means that if the system generates multiple events for the same resource that some of the payloads may be out-of-date and the burden of reconciling events falls on the listeners.

We could configure Spring Integration to handle both scenarios by wiring together inbound and outbound channels and modular components. For a concrete example, let's consider the listener defined in the previous example. Here is the Spring Integration configuration for this listener:


    <!-- Create a channel for a listener that will consume messages-->
    <int:channel id="listenerChannel" />

    <int-jms:message-driven-channel-adapter id="messageDrivenAdapter"
                                            channel="listenerChannel"
                                            destination-name="topic.myTopic"
                                            pub-sub-domain="true" />

    <int:service-activator input-channel="listenerChannel" ref="messageListenerImpl" method="processMessage" />

In theory, the payload already exists and is passed to the messageListenerImpl's processMessage() method. But what if it didn't? What if the message sent to the topic.myTopic destination was only the ID of the changed resource and it was up to us to call the system back and retrieve the payload? Would we have to rewrite our logic? Luckily, we wouldn't. We can define another bean that retrieves our payload for us, route the message to that bean, and route the response from that bean to the messageListenerImpl bean.

Listing 16 shows the source code for a new RetrievePayloadImpl class.

Listing 16. RetrievePayloadImpl.java


package com.geekcap.springintegrationexample.service;

import org.springframework.stereotype.Service;

@Service
public class RetrievePayloadServiceImpl implements RetrievePayloadService
{
    @Override
    public String getPayload(String id)
    {
        // Go back to the SOR and retrieve the payload for the specified id ...
        return "Payload for " + id;
    }
}

This class would theoretically go back to the system of record (SOR) and retrieve the payload for the component with the specified ID and return that payload. In this case it just returns the String: "Payload for ..."

Now let's wire this new service in between the message-driven-channel and the messageListenerImpl, shown in listing 17.

Listing 17. Updated applicationContext.xml file with the RetrievePayloadService


    <!-- Create a channel for a listener that will consume messages-->
    <int:channel id="listenerChannel" />

    <int-jms:message-driven-channel-adapter id="messageDrivenAdapter"
                                            channel="getPayloadChannel"
                                            destination-name="topic.myTopic"
                                            pub-sub-domain="true" />

    <int:service-activator input-channel="listenerChannel" ref="messageListenerImpl" method="processMessage" />

    <int:channel id="getPayloadChannel" />

    <int:service-activator input-channel="getPayloadChannel" output-channel="listenerChannel" ref="retrievePayloadServiceImpl" method="getPayload" />

Instead of routing the message-driven-channel-adapter directly to the listenerChannel, we instead route it to the getPayloadChannel. The getPayloadChannel invokes the retrievePayloadServiceImpl bean's getPayload() method and routes its output to the listenerChannel, which sends the augmented payload to the messageListenerImpl's processMessage() method. We were able to leave the MessageListenerImpl class completely alone and route the message through another service, all through configuration.

Note that if the server exposes a RESTful interface then the RetrievePayloadService could be replaced entirely by an HTTP Outbound Gateway. For example, listing 18 shows the configuration from a project that I built that extracts the "ResourceLink" from a Message object and uses it as part of an HTTP request.

Listing 18. Using an HTTP Outbound Gateway to callback to a service


    <int:channel id="createEntityChannel" />
    <int-http:outbound-gateway request-channel="createGuestChannel"
                               url="http://localhost:8080{link}"
                               http-method="GET"
                               expected-response-type="com.mycompany.model.Entity"
                               reply-channel="transformEntityChannel" >
        <int-http:uri-variable name="link" expression="payload.getResourceLink()" />
    </int-http:outbound-gateway>

Listing 18 is only one part of a much larger applicationContext.xml file, but it illustrates how a message that is sent to the createEntityChannel is routed to an HTTP Outbound Gateway to retrieve a com.mycompany.model.Entity from http://localhost:8080/link and then is passed to the transformEntityChannel for the next step in the orchestration. Spring Integration provides a rich set of adapters and gateways that allow you to focus on your business objective and not the plumbing code required to call services, publish message, read from topics, and so forth.

In conclusion

Spring Integration helps solve enterprise integration issues by implementing the design patterns defined in the Enterprise Integration Patterns book. This includes an asynchronous messaging paradigm that abstracts message producers from message consumers. Rather than invoking a method directly, Spring Integration has you send a message to a channel. An adapter or gateway manages that channel and routes the message to the appropriate destination, whether that destination is another service running in the same virtual machine or a service running in another data center that is connected by an enterprise service bus.

In this installment of Open source Java projects I defined the terms message, channel, adapter, and gateway. I then demonstrated how to use Spring Integration to pass a message from one component to another, how to handle responses, and how to integrate components together using JMS and ActiveMQ as a message bus. Finally, I demonstrated the process of wiring applications together by writing modular components and defining channels to control the flow of messages.

This introductory article has only scratched the surface of what you can do with Spring Integration. JMS is one type of adapter, but Spring Integration support others; for instance you could use Spring integration to pass messages by email, file systems, web service calls, tweeting, and more. The key is to think in terms of routing messages to the correct components until you arrive at your desired destination.

1 2 3 4 Page 4
Page 4 of 4