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.

 

Apache Camel

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.