Camel Tutorial - Jonathan-Eid/kennuware-wiki GitHub Wiki

Prerequisites

Download the starter repository

git clone https://github.com/code-not-found/apache-camel.git

This repo contains two projects, we will be using the apache-camel-rest project however.

cd apache-camel/apache-camel-rest

Configuration

Let's take a look at some of our dependencies in our pom.xml

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-spring-boot-starter</artifactId>
      <version>${apache-camel-version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-servlet-starter</artifactId>
      <version>${apache-camel-version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-jackson-starter</artifactId>
      <version>${apache-camel-version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-swagger-java-starter</artifactId>
      <version>${apache-camel-version}</version>
    </dependency>

Apache provides a camel-spring-boot-starter for the core Camel framework. Each additional component requires it's own additional import

camel-servlet-starter will be used to start the REST API server

camel-swagger-java-starter will define our REST methods

camel-jackson-start will handle serialization/deserialization between JSON and POJO

Add dependency

We will be adding Twilio integration so add the Camel component as a dependency into the pom.xml

  <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-twilio-starter</artifactId>
      <version>${apache-camel-version}</version>
  </dependency>

Set properties

src/main/resources/application.yml

camel:
  component:
    servlet:
      mapping:
        context-path: /parts-inc/sales/api/*

The previous developer already configured the servlet component to define the route path for the REST API server.

We need to define configuration for our Twilio component, on the same indent level as the servlet config, add:

    twilio:
      account-sid: <TWILIO SID>
      username: <TWILIO USERNAME>
      password: <TWILIO PASSWORD>

Your application.yml file should now look like

camel:
  component:
    servlet:
      mapping:
        context-path: /parts-inc/sales/api/*
    twilio:
      account-sid: <TWILIO SID>
      username: <TWILIO USERNAME>
      password: <TWILIO PASSWORD>

Deep Dive

Let's take a look into the routes defined in src/main/java/com/codenotfound/router/OrderRouter.java

package com.codenotfound.router;

import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.springframework.stereotype.Component;
import com.codenotfound.model.Order;
import com.codenotfound.service.OrderNotFoundException;

@Component
public class OrderRouter extends RouteBuilder {

  @Override
  public void configure() throws Exception {
    restConfiguration()
        // use the servlet component and run on port 8080
        .component("servlet").port("8080")
        // and enable json binding mode
        .bindingMode(RestBindingMode.json)
        // enable swagger
        .apiContextPath("/swagger-doc")
        // setup the api properties
        .apiProperty("api.title", "Order API")
        .apiProperty("api.version", "1.0.0");

    onException(OrderNotFoundException.class).handled(true)
        .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(404))
        .setBody(constant(""));

    // rest operations under the orders context-path
    rest("/orders").description("Order REST service")
        .consumes("application/json").produces("application/json")

        .get("/{id}").outType(Order.class)
        .description("Find an order by ID").param().name("id")
        .description("The order ID").endParam().responseMessage()
        .code(200).message("The order for the given ID")
        .endResponseMessage().responseMessage().code(404)
        .message("Order not found").endResponseMessage()
        .to("bean:orderService?method=findById(${header.id})")

        .post().type(Order.class).outType(String.class)
        .description("Service to submit a new order")
        .responseMessage().code(200).message("The created order id")
        .endResponseMessage().responseMessage().code(400)
        .message("Invalid input data").endResponseMessage()
        .responseMessage().code(500).message("Server error")
        .endResponseMessage()
        .to("bean:orderService?method=createOrder")

Each Camel route class extends a RouteBuilder class that implements a configure method. The class is annotated by @Component so Spring Boot can detect it and automagically add it to the context.

The beginning of the configure() method shows the servlet component being set to listen on port "8080" in the restConfiguration() route.

Now bring your attention to the "rest(/orders)" route. This is where our rest component is defining the entry into our Orders API.

The preliminary block of the route defines the content type the requests and responses will use, JSON in this case.

The next block defines our GET method, which will retrieve and order by id and respond back to the user

View GET Response

What we will be changing is the .post() route block. Instead of just receiving a response and putting a new order into the database, we will also send out an SMS confirmation using twilio and Camel will be used to write that integration!

Add new file at src/main/java/com/codenotfound/router/TwilioRouter.java

package com.codenotfound.router;

import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.springframework.stereotype.Component;
import com.twilio.type.PhoneNumber;


@Component
public class TwilioRouter extends RouteBuilder {

  @Override
  public void configure() throws Exception {
    from("direct:twilio")
        .setHeader("CamelTwilioTo", constant(new PhoneNumber("+13472766941")))
        .setHeader("CamelTwilioFrom", constant( new PhoneNumber("+13392246398")))
        .setHeader("CamelTwilioBody", simple("Hey, ${body.customer} your order for # ${body.id} has been processed"))
        .to("twilio://message/creator");
  }
}

The .post() method will eventually get sent to the "direct:twilio" component where Camel will prepare a message to send out to Twilio. The ${body} is our Order object.

Go back to OrderRouter.java and add this to the end of the configure() method

        from("direct:processOrder")
          .multicast()
          .to("bean:orderService?method=createOrder")
          .to("direct:twilio")
        .end();

The multicast() implements the Multicast EIP that will send the same message to different endpoints, in this case the new direct component we defined and the previous method that handles creating orders.

Change the .to() on the .post() block code from:

        .post().type(Order.class).outType(String.class)
        .description("Service to submit a new order")
        .responseMessage().code(200).message("The created order id")
        .endResponseMessage().responseMessage().code(400)
        .message("Invalid input data").endResponseMessage()
        .responseMessage().code(500).message("Server error")
        .endResponseMessage()
        .to("bean:orderService?method=createOrder")

to

        .post().type(Order.class).outType(String.class)
        .description("Service to submit a new order")
        .responseMessage().code(200).message("The created order id")
        .endResponseMessage().responseMessage().code(400)
        .message("Invalid input data").endResponseMessage()
        .responseMessage().code(500).message("Server error")
        .endResponseMessage()
        .to("direct:processOrder")

OrderRouter.java should now look like:

package com.codenotfound.router;

import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.springframework.stereotype.Component;
import com.codenotfound.model.Order;
import com.codenotfound.service.OrderNotFoundException;

@Component
public class OrderRouter extends RouteBuilder {

  @Override
  public void configure() throws Exception {
    restConfiguration()
        // use the servlet component and run on port 8080
        .component("servlet").port("8080")
        // and enable json binding mode
        .bindingMode(RestBindingMode.json)
        // enable swagger
        .apiContextPath("/swagger-doc")
        // setup the api properties
        .apiProperty("api.title", "Order API")
        .apiProperty("api.version", "1.0.0");

    onException(OrderNotFoundException.class).handled(true)
        .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(404))
        .setBody(constant(""));

    // rest operations under the orders context-path
    rest("/orders").description("Order REST service")
        .consumes("application/json").produces("application/json")

        .get("/{id}").outType(Order.class)
        .description("Find an order by ID").param().name("id")
        .description("The order ID").endParam().responseMessage()
        .code(200).message("The order for the given ID")
        .endResponseMessage().responseMessage().code(404)
        .message("Order not found").endResponseMessage()
        .to("bean:orderService?method=findById(${header.id})")

        .post().type(Order.class).outType(String.class)
        .description("Service to submit a new order")
        .responseMessage().code(200).message("The created order id")
        .endResponseMessage().responseMessage().code(400)
        .message("Invalid input data").endResponseMessage()
        .responseMessage().code(500).message("Server error")
        .endResponseMessage()
        .to("direct:processOrder")

        .put().type(Order.class)
        .description("Service to update an existing order")
        .responseMessage().code(400).message("Invalid input data")
        .endResponseMessage().responseMessage().code(500)
        .message("Server error").endResponseMessage()
        .to("bean:orderService?method=updateOrder")

        .delete("{id}")
        .description("Service to cancel an existing order").param()
        .name("id").description("The order id").endParam()
        .responseMessage().code(404).message("Order not found")
        .endResponseMessage().responseMessage().code(500)
        .message("Server error").endResponseMessage()
        .to("bean:orderService?method=cancelOrder(${header.id})");

        from("direct:processOrder")
          .multicast()
          .to("bean:orderService?method=createOrder")
          .to("direct:twilio")
        .end();
  }
}

Outcome

When a user posts a new order to the server, they will get an SMS confirmation of that order:

SMS Confirmation

⚠️ **GitHub.com Fallback** ⚠️