Gluing Things Together
Could every programmer that has needed to join one system to another one please put their hand up? If you have your hand up, keep it up if you’ve found doing so a little tedious. If you still have your hand up, keep it up if handling retries and failures made you want to weep. It’s likely that a lot of you still have your hands up, I do. Repress those memories of socket servers that cause timeouts every so often and make your scheduled process explode and read on…
One Hump Or Two.
routes which Camel manages according to the definition you create. Routes originate with an endpoint like “netty:tcp://localhost:1234” and in the simplest case either reply back to that endpoint or send the message on to another endpoint.
Enterprise? Nooooo.
The Camel pages throw words like “enterprise” around and any sane person heads for the hills when Java and enterprise are spoken in the same sentence. Worry not, Groovy can save the day as in this example:
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.impl.DefaultCamelContext
@Grab(group="org.apache.camel",module="camel-jetty",version="2.7.0")
@Grab(group="org.slf4j",module="slf4j-nop",version="1.6.1")
class WebRouteBuilder extends RouteBuilder {
void configure() {
from)
.inOut()
.setBody(constant("<html><title>Cake</title><body>Chocolate Cake Is The Best</body></html>"))
.end()
}
}
def camelContext = new DefaultCamelContext()
camelContext.addRoutes(new WebRouteBuilder())
camelContext.start()
That code can be dropped straight into a file and run with Groovy on the command line and serves up the snippet of HTML when )
Quite simple this one, tells Camel that the Jetty component should be used to consume messages that flow through the route at the start.
Makes this route a request-reply modelled route, such that when it reaches the end of the route the message body is sent back to the originator.
.setBody(constant("CakeChocolate Cake Is The Best"))
With this, the message body is set to a string value, the constant method is used as setBody takes an expression and supports a few of them to allow much more dynamic body values to be used.
Marks the end of the route, end of line.
One Way Street.
In the example above, the route was modelled on a request-reply pattern, but not all situations make sense to use that particular model. These things could be required of a support system for a game:
- Consume session completed objects off of an Amazon SQS queue.
- Perform a heat map analysis of matches every hour, push the latest heat map onto an FTP server so that people in the office can see it.
- Write out to a file the session completed data for potential future use.
Here’s the slightly more complicated route that would perform that particular function:
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.impl.DefaultCamelContext
import org.apache.camel.*
import org.apache.camel.processor.aggregate.GroupedExchangeAggregationStrategy
@Grab(group="org.apache.camel",module="camel-stream",version="2.7.1")
@Grab(group="org.slf4j",module="slf4j-nop",version="1.6.1")
class HeatmapProcessor implements Processor {
def void process(Exchange exchange) {
exchange.getIn().setBody("It's hot.")
}
}
class WebRouteBuilder extends RouteBuilder {
void configure() {
from("direct:sqs") // Not really SQS as that's harder to test locally.
.multicast()
.to("direct:heatmap", "direct:storage")
from("direct:heatmap")
.aggregate(constant("all"), new GroupedExchangeAggregationStrategy()).completionInterval(1000)
.process(new HeatmapProcessor())
.to("stream:out") // Would ordinarily be: to("ftp:heatmapformygame.com")
from("direct:storage")
.transform(simple("\${in.body}\r\n"))
.to("file:.?fileName=events.txt&fileExist=Append")
}
}
def camelContext = new DefaultCamelContext()
camelContext.addRoutes(new WebRouteBuilder())
camelContext.start()
while(camelContext.getStatus() != ServiceStatus.Started) Thread.sleep(100)
def producerTemplate = camelContext.createProducerTemplate()
for (count in 0..3) producerTemplate.sendBody("direct:sqs", "Game Complete!")
Thread.sleep(2000)
for (count in 0..5) producerTemplate.sendBody("direct:sqs", "Game Complete!")
Thread.sleep(2000)
camelContext.stop()
The direct endpoint is for handling messaging between routes inside a JVM and is the most appropriate way to get messages into a route programmatically. Here, the aggregation is collecting the messages together over a 1 second interval so that the message “It’s hot.” only gets printed to the console twice.
Further Reading.
Another 10 posts wouldn’t be enough to cover most of the functionality of Camel, but these links might help with the rest:
- Camel In Action by Claus Ibsen and Jonathan Anstey, lots of detail on getting things done.
- Apache Camel online documentation, useful for finding out information on components, newer ones especially, as it lists the configuration options for each.
- Camel integration with Akka, actors and lots of types of transports provide a nice combination.
- Scalaz-Camel is a Scala based DSL for constructing routes, makes terse code even terser.
- Groovy Grapes was used in the examples to do the automagic downloading of dependencies and is what makes using Groovy with this really easy.
- The Spring integration is there if you really like XML.