Spring Integration on Dropwizard
This page was converted from my old blog and hasn't been reviewed. If you see an error please let me know in the comments.
One of my favorite Spring sub projects is Spring Integration. Spring integration is an implementation of the Enterprise Integration Patterns.
I can’t say it much better than the Spring team so I will quote the following from the Spring Integration QuickStart
Using the Spring Framework encourages developers to code using interfaces and use dependency injection (DI) to provide a Plain Old Java Object (POJO) with the dependencies it needs to perform its tasks. Spring Integration takes this concept one step further, where POJOs are wired together using a messaging paradigm and individual components may not be aware of other components in the application. Such an application is built by assembling fine-grained reusable components to form a higher level of functionality. With careful design, these flows can be modularized and also reused at an even higher level.
In addition to wiring together fine-grained components, Spring Integration provides a wide selection of channel adapters and gateways to communicate with external systems. Channel Adapters are used for one-way integration (send or receive); gateways are used for request/reply scenarios (inbound or outbound). For a full list of adapters and gateways, refer to the reference documentation.
The out of the box components include internal enterprise pattern components such as:
- Router
- Channel
- Aggregator
- Filter
- Tranformer
It also includes many external system integration adapters such as:
- ReST/HTTP
- FTP/SFTP
- WebServices (SOAP and ReST)
- TCP/UDP
- JMS
- RabbitMQ
- Kafka
And of course you can write your own.
The below code will introduce you to some of the Spring Integration components and in the spirit of my last post Spring Security on Dropwizard. i will implement the code on Dropwizard.
The full sample is available on GitHub
In this example we will utilize a component approach to convert the temperature from Fahrenheit to Celsius.
<span class="hljs-meta">@MessagingGateway</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">TempConverter</span> </span>{
<span class="hljs-meta">@Gateway</span>(requestChannel = <span class="hljs-string">"convert.input"</span>)
<span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">fahrenheitToCelsius</span><span class="hljs-params">(<span class="hljs-keyword">float</span> fahren)</span></span>;
}
The Messaging Gateway is the first part of our integration flow. It represents the contract that will be used when a caller wants to invoke the flow. In this case it has a single method that takes a float as input and returns a float.
The Spring Integration DSL allows us to implement the data flow. In this case the flow performs the following steps.
- Transform the payload into an XML message.
- Enrich the header of the message with the SOAPAction header
- send the message outbound via HTTP
- transform the result by extracting the value in Celsius.
@Bean
public IntegrationFlow convert() {
<span class="hljs-function"><span class="hljs-title">return</span> f -></span> f
.<span class="hljs-function"><span class="hljs-title">transform</span>(payload -></span>
<span class="hljs-string">"<FahrenheitToCelsius xmlns=\"</span>http:<span class="hljs-comment">//www.w3schools.com/xml/\">"</span>
+ <span class="hljs-string">"<Fahrenheit>"</span> + payload + <span class="hljs-string">"</Fahrenheit>"</span>
+ <span class="hljs-string">"</FahrenheitToCelsius>"</span>)
.<span class="hljs-function"><span class="hljs-title">enrichHeaders</span>(h -></span> h
.header(WebServiceHeaders.SOAP_ACTION,
<span class="hljs-string">"http://www.w3schools.com/xml/FahrenheitToCelsius"</span>))
.handle(new SimpleWebServiceOutboundGateway(
<span class="hljs-string">"http://www.w3schools.com/xml/tempconvert.asmx"</span>))
.transform(Transformers.xpath(<span class="hljs-string">"/*[local-name()=\"</span>FahrenheitToCe lsiusResponse\<span class="hljs-string">"]"</span>
+ <span class="hljs-string">"/*[local-name()=\"</span>FahrenheitToCelsiusResult\<span class="hljs-string">"]"</span>));
}
The second part of the flow converts the payload to a web service call using the WebServiceOutboundGateway component. In this example the code is inline in our configuration class but it could just as easily be extracted out into another class so they can easily be reused.
<span class="hljs-keyword">final</span> <span class="hljs-keyword">Float</span> tempc =
tempConverter.fahrenheitToCelsius(tempf.<span class="hljs-keyword">or</span>(<span class="hljs-number">0</span>f));
Now that we have the interface created we can simply call it to initiate our flow.
This is a very simplistic flow but Spring Integration can support many complex scenarios in a very modular fashion. In a recent project we used the outbound file gateway to save the output of a workflow to a file.
<int-<span class="hljs-built_in">file</span>:outbound-channel-adapter
id=<span class="hljs-string">"outboundJobRequestChannel"</span>
channel=<span class="hljs-string">"file"</span>
mode=<span class="hljs-string">"APPEND"</span>
<span class="hljs-built_in">directory</span>=<span class="hljs-string">"${filesout}"</span>
auto-<span class="hljs-built_in">create</span>-<span class="hljs-built_in">directory</span>=<span class="hljs-string">"true"</span>/>
The outbound adapter is subscriber to the outboundJobRequestChannel and processes the data as it comes in.
Some weeks after the completion of this project the customer asked us to post the file to an http endpoint rather than a file. If we had written this code from scratch, we would have quite a bit of work ahead of us to implement this change. Fortunately, Spring provides an http outbound file adapter. All we did was change the configuration to use it to post the record to the specified URL.
<<span class="hljs-built_in">int</span>-http:outbound-gateway
request-channel=<span class="hljs-string">"outboundJobRequestChannel"</span>
http-<span class="hljs-keyword">method</span>=<span class="hljs-string">"POST"</span>
url=<span class="hljs-string">"${clusterPublishURL}"</span>
mapped-request-headers=<span class="hljs-string">"ContentType:application/json"</span>
/>
We also had a request to look at possibly publishing to Kafka, Again, Spring provided us with what we needed out of the box.
<<span class="hljs-built_in">int</span>-kafka:outbound-channel-adapter>
<kafka-producer-context-<span class="hljs-keyword">ref</span>=<span class="hljs-string">"kafkaProducerContext"</span>
channel=<span class="hljs-string">"file"</span>
topic=<span class="hljs-string">"foo"</span>
message-key=<span class="hljs-string">"bar"</span>>
</<span class="hljs-built_in">int</span>-kafka:outbound-channel-adapter>
We could even easily switch between the different implementations using Spring Profiles.
If Spring didn’t provide what needed we could easily implement our own channel adapters.
Another interesting example is the Cafe Demo example provided in the Spring documentation.
The purpose of the Cafe Demo application is to demonstrate how Enterprise Integration Patterns (EIP) can be used to delegate order processing to a parallel flow. With this application, we handle several drink orders – hot and iced. Cold drinks are prepared quicker than hot. However the delivery for the whole order is postponed until the hot drink is ready.
<<span class="hljs-built_in">int</span>:channel id=<span class="hljs-string">"coldDrinks"</span>>
<<span class="hljs-built_in">int</span>:queue capacity=<span class="hljs-string">"10"</span>/>
</<span class="hljs-built_in">int</span>:channel>
<<span class="hljs-built_in">int</span>😒ervice-activator
input-channel=<span class="hljs-string">"coldDrinks"</span>
<span class="hljs-keyword">ref</span>=<span class="hljs-string">"barista"</span>
<span class="hljs-keyword">method</span>=<span class="hljs-string">"prepareColdDrink"</span>
output-channel=<span class="hljs-string">"preparedDrinks"</span>/>
<<span class="hljs-built_in">int</span>:channel id=<span class="hljs-string">"hotDrinks"</span>>
<<span class="hljs-built_in">int</span>:queue capacity=<span class="hljs-string">"10"</span>/>
</<span class="hljs-built_in">int</span>:channel>
<<span class="hljs-built_in">int</span>😒ervice-activator
input-channel=<span class="hljs-string">"hotDrinks"</span>
<span class="hljs-keyword">ref</span>=<span class="hljs-string">"barista"</span>
<span class="hljs-keyword">method</span>=<span class="hljs-string">"prepareHotDrink"</span>
output-channel=<span class="hljs-string">"preparedDrinks"</span>/>
<<span class="hljs-built_in">int</span>:channel id=<span class="hljs-string">"preparedDrinks"</span>/>
<<span class="hljs-built_in">int</span>:aggregator
input-channel=<span class="hljs-string">"preparedDrinks"</span>
<span class="hljs-keyword">ref</span>=<span class="hljs-string">"waiter"</span>
<span class="hljs-keyword">method</span>=<span class="hljs-string">"prepareDelivery"</span>
output-channel=<span class="hljs-string">"deliveries"</span>/>
The splitter splits the order into a hot drink queue and a cold drink queue. The order cannot be released to the waiter until the aggregator has the complete order.
For further inspiration on the power of Spring Integration see the Samples.