n the past few years, some major Java-related developments have occurred in enterprise integration and messaging solutions. Several open source Java Message Service (JMS) solutions such as OpenMQ
have emerged, and enterprise service buses (ESBs) such as ServiceMix
have been gaining popularity. Now SpringSource has thrown its hat into the ring with the recent release of Spring Integration
(SI) version 1.0.0. The Spring Integration framework supports the construction of message-passing architectures and legacy application integration by implementing most of the patterns described in Enterprise Integration Patterns
by Gregor Hohpe and Bobby Woolf, considered one of the seminal works on the subject.
As with all the SpringSource projects, SI uses many of the preexisting conventions and a simple POJO (plain old java object) configuration, which makes it quick and easy to use. However, SI does not provide the ability to persist messages in a database, a common solution that enables systems to couple message delivery to transactions, retry delivery, and recover after a server restart. This article demonstrates how to create a basic message-passing application with SI, as well as how to use a custom channel adapter and data access object (DAO) for persistent messaging. By writing a custom channel adapter, you can easily integrate database persistence into the overall SI framework.
Spring Integration Components
Spring Integration contains three main components: messages, message channels, and message endpoints.
- Messages: Composed of a payload and headers, a message carries information from a sender to a message endpoint. The payload is the business domain object you want to send. Message headers contain message metadata such as a unique message ID, creation date, and other fields used by SI. Applications can put their own information in the message headers to extend the capabilities of the framework.
- Message channels: The paths that messages take to arrive at the endpoint are called message channels. SI supports many different types of message channels, such as the Direct Channel for sending a message within the thread of the sender and the Queue Channel, which allows messages to be queued for asynchronous delivery.
- Message endpoints: The destination of a message is called the message endpoint. Endpoints work in many use cases, including routing, splitting messages, combining messages, and invoking services.
The current SI release contains many different implementations of channels and endpoints. Review the Spring Integration manual for more information on these components.
Basic Message Flow Construction
To construct a basic workflow or message bus, you create and connect the core components using the SI domain-specific namespace in a bean configuration file (similar to any other Spring Framework configuration). Listing 1
presents a very basic message flow configuration using one message gateway, a channel, and a message endpoint. Figure 1
presents the same message flow visually.
|Figure 1. Basic Message Flow Configuration: This diagram shows a basic message flow with a gateway, channel, and endpoint.|
Using a message gateway, SI creates an anonymous implementation of your application-specific interface. This anonymous implementation hides the underlying message bus from the rest of the application. The service activator invokes a message on a normal application business object to deliver the message payload.
This pattern continues through the rest of the framework, allowing the details of the underlying message framework (such as the channel type, transformations, persistence, and routing) to be completely transparent and reconfigurable without any changes to application code. The use of the gateway and service activator removes any dependency on the framework from the application code.
Use Case for Custom Channel Adapter
While SI does bundle a number of useful endpoints and connectors, including JMS, files, remote method invocation, and HTTP invokers, it does not include an endpoint for message persistence in a database. One common solution when deploying an application server is to configure the message flows to use JMS for persistence. JMS provides guaranteed message delivery, error handling, and can participate in distributed transactions to tie message sending to transaction success. However, the configuration, deployment, and maintenance of JMS can be overkill for an application that simply uses message passing internally for asynchronous processing. In those cases, using JMS also can require distributed transactions, which lower scalability and performance.
Extending SI with a custom channel adapter allows you to skip JMS and distributed transactions all together while still meeting application requirements such as the following:
- Transaction coupling: Messages should not be delivered until the business transaction is committed. This is important to ensure that emails and orders are not processed if the initial operation fails.
- Retrying messages: If a message fails to process, you have the option to retry the message or drop it based on the message flow configuration. Some message channels are important, while others can be silently dropped.
- Persistent: The messages must survive a server shutdown or crash. By default, SI is mostly an in-memory messaging framework. Assuring that messages will not be lost if the application shuts down or crashes is a necessity.