Spring boot and Apache Camel
As the world of software moves on, more complex systems are being developed, which have to integrate with each other. It started with SOA and it continues with microservices.
Camel is the number one integration tool that comes to my mind since nowadays spring boot with camel is a very strong combination.
The first step is to include the camel dependencies to our spring project.
buildscript { ext { springBootVersion = '1.5.9.BUILD-SNAPSHOT' } repositories { mavenCentral() maven { url "https://repo.spring.io/snapshot" } maven { url "https://repo.spring.io/milestone" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' group = 'com.gkatzioura' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() maven { url "https://repo.spring.io/snapshot" } maven { url "https://repo.spring.io/milestone" } } dependencies { compile('org.apache.camel:camel-spring-boot-starter:2.20.0') testCompile('org.springframework.boot:spring-boot-starter-test') testCompile('org.apache.camel:camel-test-spring:2.20.0') }
In order to have a faster project setup from scratch you can always use the online spring initializer.
Now let’s add a simple route
package com.gkatzioura.springcamel.routes; import org.apache.camel.builder.RouteBuilder; import org.springframework.stereotype.Component; @Component public class TimerRoute extends RouteBuilder { public static final String ROUTE_NAME = "TIMER_ROUTE"; @Override public void configure() throws Exception { from("timer:initial//start?period=10000") .routeId(ROUTE_NAME) .to("log:executed"); } }
We don’t have to worry about the camel context configuration since the Camel auto-configuration creates a SpringCamelContext for you and takes care of the proper initialization and shutdown of that context.
Also camel auto-configuration collects all the RouteBuilder instances from the Spring context and automatically injects them into the provided CamelContext. Thus we don’t have to register our routes to the CamelContext.
As you can see our route has a timer with a period of 10000 milliseconds which routes to a log endpoint. The log endpoint will print the executed string every 10000 milliseconds.
Keep in mind that if no routeId is specified, camel will assign a name on its own, therefore giving a name to our route definition is a good practice in case we want to retrieve the root definition.
In order for camel to stay up, we need to keep our main thread blocked. Thus we add this configuration to our application.yml file.
camel: springboot: main-run-controller: true
Instead of this we can include the spring-boot-starter-web dependency, but our application has as few dependencies as possible, and we need to keep it this way.
However the most difficult part in the integration with other systems is testing. Throughout the years there have been rapid advancements on testing and the tools that we use.
Camel also comes packaged with some great tools in order to unit test.
For example we will implement a test of the route specified previously.
@RunWith(CamelSpringBootRunner.class) @SpringBootTest public class SpringCamelApplicationTests { @EndpointInject(uri = MOCK_RESULT) private MockEndpoint resultEndpoint; @Autowired private CamelContext camelContext; @EndpointInject(uri = MOCK_TIMER) private ProducerTemplate producer; private static final String MOCK_RESULT = "mock:result"; private static final String MOCK_TIMER = "direct:mock-timer"; @Before public void setup() throws Exception { camelContext.getRouteDefinition(TimerRoute.ROUTE_NAME) .autoStartup(true) .adviceWith(camelContext, new AdviceWithRouteBuilder() { @Override public void configure() throws Exception { replaceFromWith(MOCK_TIMER); interceptSendToEndpoint("log*") .skipSendToOriginalEndpoint() .to(MOCK_RESULT); } }); } @Test public void sendMessage() throws Exception { resultEndpoint.expectedMessageCount(1); producer.sendBody("A message"); resultEndpoint.assertIsSatisfied(); } }
Let’s have a look on each part of the test.
Our JUnit runner of choice would be the CamelSpringBootRunner.class
@RunWith(CamelSpringBootRunner.class)
We inject a ProducerTemplate. The ProducerTemplate interface allows you to send message exchanges to endpoints in a variety of different ways to make it easy to work with Camel Endpoint instances from Java code.
Then we inject a MockEndpoint. The MockEndpoint will serve us by replacing the original endpoint. Then we will set the expected number of messages to be received. Once the processing is done we assert that the amount of received messages is satisfied.
On our setup method we will replace our original endpoint with the fake producer template endpoint. Thus our route will receive the events that we will issue from the ProducerTemplate.
Then we will also intercept the log endpoint and direct the message to the MockEndpoint previously specified.
So we ended up withe a camel application and a unit test for the route specified. You can find the source code on github.
Published on Java Code Geeks with permission by Emmanouil Gkatziouras, partner at our JCG program. See the original article here: Spring boot and Apache Camel Opinions expressed by Java Code Geeks contributors are their own. |