Wednesday, October 29, 2014

Using Apache Camel with Spring Boot

Some time ago I created a Spring Boot auto-configuration module for the Apache Camel. Initially it was a part of the Fabric8 Spring Boot support, but I decided that the module deserves its own life under the Apache Camel umbrella, so I've just moved it there. Spring Boot module will be available starting from Camel 2.15.0.


What is Camel auto-configuration for Spring Boot?


Spring Boot Camel component provides auto-configuration for the Apache Camel. Our opinionated auto-configuration of the Camel context auto-detects Camel routes available in the Spring context and registers the key Camel utilities (like producer template, consumer template and the type converter) as beans.


How to enable Camel auto-configuration in my Spring Boot application?


Just drop camel-spring-boot jar into your classpath:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-spring-boot</artifactId>
    <version>${camel.version}</version> <!-- use the same version as your Camel core version -->
</dependency>

camel-spring-boot jar comes with the spring.factories file, so as soon as you add that dependency into your classpath, Spring Boot will automatically auto-configure the Camel for you. Yay! That was fast ;) .



Auto-configured Camel context


The most important piece of functionality provided by the Camel auto-configuration is the CamelContext instance. Camel auto-configuration creates SpringCamelContext for your and take care of the proper initialization and shutdown of that context. Created Camel context is also registered in the Spring application context (under camelContext bean name), so you can access it just as the any other Spring bean.
@Configuration
public class MyAppConfig {
 
  @Autowired
  CamelContext camelContext;
 
  @Bean
  MyService myService() {
    return new DefaultMyService(camelContext);
  }
 
}
Keep in mind however that usually you don't really have to inject CamelContext directly into your application, as you can use the feature called...



Auto-detecting Camel routes


Camel auto-configuration collects all the RoutesBuilder instances from the Spring context and automatically injects them into the provided CamelContext. It means that creating new Camel route with the Spring Boot starter is as simple as adding the @Component annotated class into your classpath...
@Component
public class MyRouter extends RouteBuilder {
 
  @Override
  public void configure() throws Exception {
    from("jms:invoices").to("file:/invoices");
  }
 
}
...or creating a new route RoutesBuilder bean in your @Configuration class:
@Configuration
public class MyRouterConfiguration {
 
  @Bean
  RoutesBuilder myRouter() {
    return new RouteBuilder() {
 
      @Override
      public void configure() throws Exception {
        from("jms:invoices").to("file:/invoices");
      }
 
    };
  }
 
}

Ideally, you should not wire routes into the CamelContext by yourself. It is simpler just to add them to the classpath and let the Spring Boot to wire them for you.

Camel properties


Spring Boot auto-configuration automatically connect Spring Boot external configuration (like properties placeholders, OS environment variables or system properties) with the Camel properties support. It basically means that any property defined in application.properties file...
  
route.from = jms:invoices
...or set via the system property...
java -Droute.to=jms:processed.invoices -jar mySpringApp.jar
...can be used as the placeholder in the Camel route:
@Component
public class MyRouter extends RouteBuilder {
 
  @Override
  public void configure() throws Exception {
    from("{{route.from}}").to("{{route.to}}");
  }
 
}

Basically any property available for the Spring property resolver can be resolved by the Camel properties component as well.

Customizing Camel context configuration


If you would like to perform some operations on CamelContext bean created by Camel auto-configuration, register CamelContextConfiguration instance in your Spring context:
@Configuration
public class MyAppConfig {
 
  ...
 
  @Bean
  CamelContextConfiguration contextConfiguration() {
    return new CamelContextConfiguration() {
      @Override
      void beforeStart(CamelContext context) {
        // your custom configuration goes here
      }
    };
  }
 
}
Method CamelContextConfiguration#beforeStart(CamelContext) will be call just before the Spring starts the auto-configured CamelContext.

Auto-configured consumer and producer templates


Camel auto-configuration provides a pre-configured ConsumerTemplate and ProducerTemplate instances. You can simply inject them into your Spring-managed beans:
@Component
public class InvoiceProcessor {
 
  @Autowired
  private ProducerTemplate producerTemplate;
 
  @Autowired
  private ConsumerTemplate consumerTemplate;

  public void processNextInvoice() {
    Invoice invoice = consumerTemplate.receiveBody("jms:invoices", Invoice.class);
    ...
    producerTemplate.sendBody("netty-http:http://invoicing.com/received/" + invoice.id());
  }
 
}
In the example above I read a message from the JMS queue, convert it into the domain class called Invoice and finally use HTTP call to send confirmation to some external system that I processed the invoice. No Camel routes here, only consumer/producer templates.

By default consumer and producer templates come with the endpoint cache sizes equal to 1000. You can change that values via the following Spring properties:
camel.springboot.consumerTemplateCacheSize = 100
camel.springboot.producerTemplateCacheSize = 200


Auto-configured TypeConverter


Camel auto-configuration registers TypeConverter instance named typeConverter in the Spring context.
@Component
public class InvoiceProcessor {
 
  @Autowired
  private TypeConverter typeConverter;
 
  public long parseInvoiceValue(Invoice invoice) {
    String invoiceValue = invoice.grossValue();
    return typeConverter.convertTo(Long.class, invoiceValue);
  }
 
}
As you can see the type converter is pretty handy when performing conversions between two data types, so it is very useful to have it available in your Spring components.

What's next?


There are still many features I'd like to see in Camel Spring Boot. Just to mention some of them:
  • Test helpers API. We don't really need a full-blown testing API, like for the old Camel or Camel Spring. Spring Boot testing API enhanced by the Camel auto-configuration is flexible enough to handle all the most important testing scenarios (like loading routes, overriding the configuration and so forth) without any additional Camel-specific testing utilities. However having a base class with some extra helper methods to avoid testing-code boilerplate will be always appreciated.
  • Registering custom converters. I'd like to make it really easy to register new Camel type converters. As simple as adding new converter bean to the Spring context.
  • Bridge between Spring and Camel converters. Spring comes with its own types converter API. I will be nice to bridge Spring converters to Camel (and reversely) so end-users could benefit from the maximum number of converters available for them.
  • Making sure that Camel components work nicely with Spring Boot. That would require creating some pull requests to Spring Boot upstream and changes in the code of some components to be sure that those can properly pick up the beans pre-configured by Spring Boot. For example I would like to be sure that camel-jpa component plays nicely with the default Hibernate configuration provided by the Spring Boot JPA auto-configuration.


Give it a shot!


Just try to use the new Spring Boot support for Camel and let me know what do you think of it. Do you feel it lacks some functionality? Something is not working as it should? Our documentation sucks? Feel free to ping me.

30 comments:

  1. Thanks for writing camel-boot. I am running into an issue: The routes get detected before the components are registered so it throws NullPointerExample. Could you please show how to correctly register a component using camel-boot?

    Thanks

    ReplyDelete
  2. Hi Juan,

    Can you send me a sample Maven project demonstrating the issue?

    Cheers.

    ReplyDelete
  3. Hi, it makes many fun to use Spring Boot and Camel like that!
    I have a question: How i can set the CamelContext-Name / ID? I see the auto generateted Name camel-1. How i can change it like XML
    < camelContext id="my-name" >

    I try to make it with the CamelContextConfiguration, but its not working jet, or i use it wrong:
    https://github.com/eddi888/ultrahouse3000/blob/master/src/main/java/org/atomspace/ultrahouse3000/Application.java

    Thanks!

    ReplyDelete
  4. Hi Edgar,

    I'm glad you like it :) .

    Regarding setting name...

    @Bean
    CamelContextConfiguration nameConfiguration() {
    return new CamelContextConfiguration() {
    @Override
    public void beforeApplicationStart(CamelContext camelContext) {
    SpringCamelContext springCamelContext = (SpringCamelContext) camelContext;
    springCamelContext.setName("foo");
    }
    };
    }

    ...works for me. Can you give it a try?

    BTW I'll add support for setting name via property, like...

    camel.springboot.context.name = foo

    Cheers.

    ReplyDelete
    Replies
    1. Yes very nice, its working...

      Thanks!

      Delete
    2. I see it is required to add configuration properties. If change the context name after routes a builded, the name is not updated for all JMX MBeans.
      The routes are builded, before the method "beforeApplicationStart(CamelContext camelContext)" is called.

      https://github.com/apache/camel/pull/384

      Delete
    3. Hi Edgar,

      Many thanks for the pull request :) .

      Cheers.

      Delete
  5. When will 2.15 be released? I'm starting a new project and need to be sure it will be out of SNAPSHOT before we have to do our release.

    ReplyDelete
    Replies
    1. Hi Dan,

      Camel 2.15 should be out before the end of the Q1 2015.

      Laters!

      Delete
  6. There are still many features I'd like to see in Camel Spring Boot. Just to ... ibootsforwomen.blogspot.com

    ReplyDelete
  7. It was really simple to get this working with Camel 2.14.1. I copied the PropertiesComponent @Bean configuration from the Fabric8 1.x CamelAutoConfiguration: https://github.com/fabric8io/fabric8/blob/1.x/process/process-spring-boot/process-spring-boot-starter-camel/src/main/java/io/fabric8/process/spring/boot/starter/camel/CamelAutoConfiguration.java

    ReplyDelete
    Replies
    1. This looks promising. I'm trying to solve the same issue.

      How did you incorporate this into your project?

      Delete
    2. I created a camel-spring-boot project in my source tree. Copied all the classes from the 2.15-SNAP apart from Fatjar/FatWar (i'm building an old sk00l war file) and built it with this pom (bet the formatting of this wont work):


      4.0.0
      org.apache.camel
      camel-spring-boot
      2.14.1
      back port of camel-spring-boot to 2.14


      1.6
      ${java.version}
      ${java.version}




      org.apache.camel
      camel-spring
      2.14.1


      org.springframework.boot
      spring-boot-autoconfigure
      1.2.0.RELEASE


      Delete
    3. paste this into a urldecoder

      http://meyerweb.com/eric/tools/dencoder/

      %3Cproject%20xmlns%3D%22http%3A%2F%2Fmaven.apache.org%2FPOM%2F4.0.0%22%20xmlns%3Axsi%3D%22http%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema-instance%22%20xsi%3AschemaLocation%3D%22http%3A%2F%2Fmaven.apache.org%2FPOM%2F4.0.0%20http%3A%2F%2Fmaven.apache.org%2Fxsd%2Fmaven-4.0.0.xsd%22%3E%0A%20%20%3CmodelVersion%3E4.0.0%3C%2FmodelVersion%3E%0A%20%20%3CgroupId%3Eorg.apache.camel%3C%2FgroupId%3E%0A%20%20%3CartifactId%3Ecamel-spring-boot%3C%2FartifactId%3E%0A%20%20%3Cversion%3E2.14.1%3C%2Fversion%3E%0A%20%20%3Cdescription%3Eback%20port%20of%20camel-spring-boot%20to%202.14%3C%2Fdescription%3E%0A%0A%20%20%3Cproperties%3E%0A%20%20%20%20%3Cjava.version%3E1.6%3C%2Fjava.version%3E%0A%20%20%20%20%3Cmaven.compiler.source%3E%24%7Bjava.version%7D%3C%2Fmaven.compiler.source%3E%0A%20%20%20%20%3Cmaven.compiler.target%3E%24%7Bjava.version%7D%3C%2Fmaven.compiler.target%3E%0A%20%20%3C%2Fproperties%3E%0A%0A%20%20%3Cdependencies%3E%0A%20%20%20%20%3Cdependency%3E%0A%20%20%20%20%20%20%20%20%3CgroupId%3Eorg.apache.camel%3C%2FgroupId%3E%0A%20%20%20%20%20%20%20%20%3CartifactId%3Ecamel-spring%3C%2FartifactId%3E%0A%20%20%20%20%20%20%20%20%3Cversion%3E2.14.1%3C%2Fversion%3E%0A%20%20%20%20%3C%2Fdependency%3E%0A%20%20%20%20%3Cdependency%3E%0A%20%20%20%20%20%20%3CgroupId%3Eorg.springframework.boot%3C%2FgroupId%3E%0A%20%20%20%20%20%20%3CartifactId%3Espring-boot-autoconfigure%3C%2FartifactId%3E%0A%20%20%20%20%20%20%3Cversion%3E1.2.0.RELEASE%3C%2Fversion%3E%0A%20%20%20%20%3C%2Fdependency%3E%0A%20%20%3C%2Fdependencies%3E%0A%3C%2Fproject%3E

      Delete
    4. Hi guys,

      Just keep in mind that this Fabric8 code is really old one. Camel master comes with much much more goodness. Just saying :) .

      Cheers!

      Delete
  8. Hi, The starting order of routes is completely ignored, and instead they appear to be started in the order they appear in the @configuration class. Is this an issue with the way camel-spring-boot creates the roots or a bug in camel itself?
    i.e. route1 sends to route2. route1 appears first @Configuration and has .startupOrder(40), route 2 has .startupOrder(10). route1 is started and failes processing as there are no consumers on "direct:route2" as route 2 hasn't been started yet even though it has the higher start order.

    ReplyDelete
  9. Hi Dan,

    I will look at the startup order issues this week. Stay tuned :) .

    Cheers.

    ReplyDelete
  10. This is a great post Henryk, as are your M2M-IoT efforts on GitHub. I have been promoting camel on/with IoT-like hardware using standalone jar, and camel-spring-boot makes this super easy. Not sure if I missed something, but in your example at top of this post (using @Component annotated class to autodetect), I had to add a main class to RouteBuilder or FatJarRouter , to get spring-boot:run (and java -jar ...). Do you have a camel example on GitHub?

    ReplyDelete
  11. Hi Bruce,

    The approach I suggest to use is to:
    a) create class extending FatJarRouter
    b) add FatJarRouter class to the Spring Boot Maven plugin configuration
    c) add spring.main.sources=your.app.package to the application.properties file

    The configuration above is the my favorite. And yeah, 'mvn spring-boot:run' works with it as well :) .

    Cheers!

    PS I'd like to get touch with you regarding the IoT stuff. We started new project called Camel IoT Labs - https://github.com/camel-labs/camel-labs . You might be interested in it!

    ReplyDelete
  12. Hi Henry,

    Could you let me know how i can add a WebServiceClient in my camelContext.

    Thanks,
    Ravi

    ReplyDelete
  13. Hi Ravi,

    You can just ad it as a Spring @Bean annotated method to the fat jar router class.

    Cheers!

    ReplyDelete
  14. Do you have what you demonstrated here in github or as a zip file?. I'm trying to find for a best template of Apache Camel and Spring Boot project so that I can customize it for my needs.

    ReplyDelete
  15. Hi Sandy,

    Take a look at this [1] example.

    Cheers!

    [1] https://github.com/apache/camel/tree/master/examples/camel-example-spring-boot

    ReplyDelete
    Replies
    1. Thanks for your response. It is helpful. And, I want to look at the application you demonstrated in this page. Do you have uploaded it somewhere that I can get it.

      Delete
    2. Unfortunately not. But the example I sent you before will do as well :) . It is really similar to the example from this post.

      Delete
  16. This comment has been removed by the author.

    ReplyDelete
  17. This comment has been removed by the author.

    ReplyDelete
  18. Hi thanks for very nice article could you please provide me any information on how can I provide security to spring boot,camel based rest api

    ReplyDelete
  19. Hi Henry,

    This is very nice article, I am new to camel with spring boot, I have a requirement to execute shell script using camel route and I coded as below.

    from("direct:start")
    .routeID("ShellRoute")
    .to("exec:bash?args=path/run_setush.sh");

    This route is starting but not executing the script file, basically the auto configuration starting camel route that's it.

    Could you please tell me the solution to execute script file.

    ReplyDelete