Apache Camel and Spring Boot

This article covers integration of Apache Camel with Spring Boot framework

1. Introduction

Apache Camel Is a framework for integrating applications and effectively interacting between different systems, which is often encountered in any corporate infrastructure. Apache Camel allows the developer to focus on the logic of the processes without translating the data into the canonical format, since Camel supports over 80 APIs to implement various protocols and data types. Thus, the developer – that is, you – just needs to know how Camel puts everything together. In this article, we will walk you through the steps how to integrate Apache Camel and Spring Boot

But before turning to the environment example Spring Boot, let’s understand the basic concepts and terms of Camel.

1.1. Message

A message is a data object used by systems to communicate with each other.

1.2. Exchange

An exchange object is a container that contains a message and provides interaction between systems. It is a message container that defines the type of messaging.

1.3. Camel context

Camel Context (CamelContext) is a fundamental Camel model that provides access to various services (routes, endpoints, etc.).

1.4. Routes

Routes are an abstraction that allows clients and servers to work independently. Routes described in DSL domain languages ​​are a chain of function (processor) calls.

1.5. Subject-oriented language

Processors and endpoints connect to each other through a Domain-Specific Language (DSL) description – these descriptions constitute the routes. In our case, the DSL is the Java Fluent API, but when using Camel with other languages ​​/ frameworks, it could also be XML or some other DSL language.

1.6. CPU

Processors (processors) perform exchange operations. You can think of routes as a logical unit that connects the processors needed to process a message.

1.7. Component

Components are Apache Camel extension modules. They enable Camel to easily integrate with other systems. Here all major components supported by Camel are listed. Components produce endpoints with a given URI.

1.8. End point

Endpoints are service connection points that connect one system to another. We create endpoints using components using a specific URI. For example, to create an FTP connection, the following URI should be specified in the route: ftp: //[имяпользователя@]hostname[:порт]/directory[?опции] – this is how the components will create the configured FTP endpoint.

1.9. Manufacturer

Producers are Camel modules that create messages and send them to an endpoint.

1.10. Consumer

Consumers are Camel modules that receive messages generated by the manufacturer, encapsulate them inside an exchange object, and send them to the processors.

For now, these Camel concepts will suffice. There is no need to study them in detail, but a basic understanding of Camel’s architecture will help you make good use of its capabilities. In the example below, we’ll look at integrating these capabilities into Spring Boot.

2. General information about the application

We will create an application with the following properties:

  • the application includes two types of data objects: products and discounts;

  • we add products during the initial setup;

  • discounts are automatically applied to products after a certain time (Camel Timer + Camel JPA);

  • there is a REST endpoint to list all products and discounts (Camel REST);

  • used by Swagger documentation (Camel Swagger).

We use H2, Spring Web, Spring JPA and Apache Camel for this.

3. Initial setup of the application

Create a code assembly project with the following dependencies. To prepare the foundation for your application, you can use IDE or Spring initializr… Below is the complete pom.xml file with explanations:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <!--Get required dependencies from a parent-->
   <parent>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-dependencies</artifactId>
      <version>3.3.0</version>
   </parent>
   <artifactId>spring-boot-camel</artifactId>
   <name>spring-boot-camel</name>
   <description>Spring Boot Camel integration tutorial</description>
   <properties>
      <spring-boot-version>2.2.7.RELEASE</spring-boot-version>
      <run.profiles>dev</run.profiles>
   </properties>
   <dependencyManagement>
      <dependencies>
         <!--Import as a pom to let spring-boot to manage spring-boot dependencies version -->
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot-version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
         <!--Import as a pom to let camel manage camel-spring-boot dependencies version-->
         <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-spring-boot-dependencies</artifactId>
            <version>${project.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>
   <dependencies>
      <!--Spring boot dependencies to enable REST, JPA and Core features-->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>
      <!--Camel Spring Boot Dependencies to enable REST, JSON, SWAGGER, JPA features-->
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-spring-boot-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-servlet-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-jackson-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-swagger-java-starter</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-jpa-starter</artifactId>
      </dependency>
      <!--In memory database-->
      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
         <scope>runtime</scope>
      </dependency>
      <!--Spring boot testing-->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot-version}</version>
            <executions>
               <execution>
                  <goals>
                     <goal>repackage</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
</project>

4. Preparing data objects

Before you start working with Apache Camel, you need to prepare the required data objects, services, and repositories.

4.1. Product

Create a data object of type “product” with the following fields: ID, Name, Price, and Discount. We will also create a named query that can be called from Camel by name and get the result in response. The discounted products named query returns all products that have been assigned a discount.

@Entity
@Table(name = "products")
@NamedQuery(name = "discounted-products", query = "select product from Product product where product.discounted IS NOT NULL")
public class Product {

 @Id
 @GeneratedValue
 private int id;

 private String name;

 private Integer price;

 private Integer discounted;

 // Getters and setters
}

Create a ProductRepository class that extends from the interface CrudRepository Spring Data repository. This extension provides us with ready-to-call queries: findAll, findById, save, etc.

public interface ProductRepository extends CrudRepository<Product, Integer> {
}

4.2. Service class

Create a ProductService class and annotate it with a service. To retrieve the ProductRepository from the Spring context, let’s use constructor dependency injection. We provide basic functions findById, findAll and savethat need no explanation.

@Service
public class ProductService {

    private final ProductRepository products;

    @Autowired
    public ProductService(ProductRepository products) {
        this.products = products;
    }

    public Product findById(Integer id) {
        Optional < Product > product = products.findById(id);
        if (!product.isPresent()) {
            throw new IllegalStateException("Product could not found for given id:" + id);
        }
        return product.get();
    }

    public Iterable < Product > findAll() {
        return products.findAll();
    }

    public void save(Product product) {
        products.save(product);
    }
}

Finally, create a file data.sql in src/main/resources and add three products as shown below. On startup, Spring will automatically execute data.sql… You can read more about init scripts. here

INSERT INTO products (id, name, price, discounted)
  VALUES
      (1, 'Book', 25, NULL),
      (2, 'Watch', 100, NULL),
      (3, 'Shoes', 40, NULL);

4.3. Discount

Create a data object of type “discount” with the following fields: ID, Amount, and Product. Only one discount can be applied to a product at a time, so set the Product field to OneToOne.

@Entity
@Table(name = "discounts")
public class Discount {

    @Id
    @GeneratedValue
    private int id;

    private Integer amount;

    @OneToOne
    private Product product;

    // Getters and setters
}

Create the DiscountRepository repository as described above.

public interface DiscountRepository extends CrudRepository<Discount, Integer> {}

Create a DiscountService class similar to the ProductService class. Along with the method findDiscountwhich works similarly to the method findProduct, we also have a function makeDiscount… This function generates a random discount, retrieves a random product from the database, and applies that discount to that product.

@Service
public class DiscountService {

    private final DiscountRepository discounts;
    private final ProductService productService;

    private final Random random = new Random();

    @Autowired
    public DiscountService(DiscountRepository discounts,
        ProductService productService) {
        this.discounts = discounts;
        this.productService = productService;
    }

    public Discount makeDiscount() {
        // create a discount
        Discount discount = new Discount();
        int discountRate = this.random.nextInt(100);
        discount.setAmount(discountRate);

        // select random product
        int productId = this.random.nextInt(3) + 1;
        Product product = productService.findById(productId);

        // set the discount to product and save
        int discountedPrice = product.getPrice() - (discountRate * product.getPrice() / 100);
        product.setDiscounted(discountedPrice);
        productService.save(product);

        discount.setProduct(product);
        return discount;
    }

    public Discount findDiscount(Integer id) {
        Optional < Discount > discount = discounts.findById(id);
        if (!discount.isPresent()) {
            throw new IllegalStateException("Discount could not found for given id:" + id);
        }
        return discount.get();
    }
}

5. Application setting

Create application-dev.ymlto set up the mapping contextPath for Camel. Add the desired discount properties to be used in routes.

camel:
  component:
    servlet:
      mapping:
        contextPath: /javadevjournal/*

discount:
  newDiscountPeriod: 2000
  listDiscountPeriod: 6000/pre>

6. Integration of Apache Camel

So, we have prepared the data for use with Apache Camel. You can start integrating.

6.1. Creating routes

Camel has a base class for creating routes: RouteBuilder. We need to expand and annotate it @Component… As mentioned above, Apache Camel uses its own context to create object references. However, when working with SpringBoot, Camel first searches in the SpringBoot context and then injects the objects found in it into its context. CamelContext, how does RouteBuilder in our example.

After creating our route class by extending from the RouteBuilder, we need to override its configuration method. We need logic that will automatically create discounts after a specified period. First, add the following route to the configuration function with an explanation:

@Component
class TimedJobs extends RouteBuilder {

@Override
public void configure() {
        from("timer:new-discount?delay=1000&period={{discount.newDiscountPeriod:2000}}")
            .routeId("make-discount")
            .bean("discountService", "makeDiscount")
            .to("jpa:org.apache.camel.example.spring.boot.rest.jpa.Discount")
            .log("Created %${body.amount} discount for ${body.product.name}");

        // additional route will be added in the next step
}

Let’s describe our work with Spring Boot in Camel terminology. We create routesusing language Java DSL… Then we use a timer – component timerprovided by Camel by extension. In this case, the following happens “inside” Camel: upon reaching end point the timer (based on the delay settings and execution period we specified) starts manufacturer

Before going any further, it’s worth mentioning that Apache Camel supports using Spring Boot propertieswhat we are doing here. You can refer to properties like this directly using the property name and default value: {{имя_свойства:значение_по_умолчанию}}

Now we will define the route make-discount, which must be unique and which we will refer to in the future. And let’s call the makeDiscount function in the discountService bean. For messages performed exchangewhich can be referenced with a prefix to the body text, and the logger consumes this message is written to the log. For a complete list of expressions you can use, see the documentation for the Simple language… Let’s add another one under the existing route, allowing to list all products with updated prices.

from("jpa:org.apache.camel.example.spring.boot.rest.jpa.Product"
    + "?namedQuery=discounted-products"
    + "&delay={{discount.listDiscountPeriod:6000}}"
    + "&consumeDelete=false")
    .routeId("list-discounted-products")
    .log(
        "Discounted product ${body.name}. Price dropped from ${body.price} to ${body.discounted}");

We are using the JPA component for the product data object by calling it with the command namedQuery… In the JPA settings, we define the delay so that the program has time to create some kind of discounts before the list of products is generated. Inquiry consumeDelete means that we do not want to delete the processed “product” data object. A complete list of possible settings is presented on the page JPA component… This is how the logs of our assignment look like:

Created %27 discount for Watch
Created %84 discount for Book
Created %92 discount for Shoes
Discounted product Book. Price dropped from 25 to 4
Discounted product Watch. Price dropped from 100 to 73
Discounted product Shoes. Price dropped from 40 to 4

6.2. Creating REST endpoints

We have already set up a timer to trigger functions. Now let’s integrate with REST endpoints and create Swagger documentation. Let’s create a new route by extending RouteBuilder… To configure our application, you need to call the Camel function restConfiguration

@Component
class RestApi extends RouteBuilder {

@Override
public void configure() {
        restConfiguration()
            .contextPath("/javadevjournal")
            .apiContextPath("/api-doc")
            .apiProperty("api.title", "JAVA DEV JOURNAL REST API")
            .apiProperty("api.version", "1.0")
            .apiProperty("cors", "true")
            .apiContextRouteId("doc-api")
            .port(env.getProperty("server.port", "8080"))
            .bindingMode(RestBindingMode.json);

        rest("/products").description("Details of products")
            .get("https://habr.com/").description("List of all products")
            .route().routeId("products-api")
            .bean(ProductService.class, "findAll")
            .endRest()
            .get("discounts/{id}").description("Discount of a product")
            .route().routeId("discount-api")
            .bean(DiscountService.class, "findDiscount(${header.id})");
    }
}

We indicate the path contextPath – javadevjournal – and API context path – api-docwhich is used for Swagger. Snap mode is disabled by default. Since we added Jackson JSON to the pom.xml file, the JSON binding format can be used. Here a complete list of possible configurations is given. In the second part of our configuration, we define the end point /products and the returned result is ProductService.findAll. Expanding the end point /productsadding to it /discounts/{id}, and call the function DiscountService.findDiscount with the id obtained in response to the request. {header} refers to the input data mentioned above in the section on Simple, for the placeholder {body}

By going to the address http://localhost:8080/javadevjournal/api-docyou will get Swagger’s answer. Enter http://localhost:8080/javadevjournal/productsand you get:

[
    {
        "id": 1,
        "name": "Book",
        "price": 25,
        "discounted": 4
    },
    {
        "id": 2,
        "name": "Watch",
        "price": 100,
        "discounted": 73
    },
    {
        "id": 3,
        "name": "Shoes",
        "price": 40,
        "discounted": 4
    }
]

Similarly, by following the link http://localhost:8080/javadevjournal/products/discounts/1, You’ll get:

{
    "id": 1,
    "amount": 92,
    "product": {
        "id": 3,
        "name": "Shoes",
        "price": 40,
        "discounted": 4
    }
}

Summary

In this article, we looked at how integrate Apache Camel with Spring Boot… We have briefly described what Apache Camel is and how it can be integrated with Spring Boot using realistic scenarios. The application source code is available at Github


The translation of the article was prepared on the eve of the start of the course “Spring Framework Developer”

On May 14, the demo day of the course will be held – this is an excellent opportunity to ask all questions about the course, learn more about the course program, features of the online format, skills, competencies and prospects that await graduates after training.

BOOK UP DEMO DAY


Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *