REST Services With Apache Camel
Apache Camel can be run anywhere as a standalone or embedded as a library, and it aids integration. Read on to learn how to use it to expose REST services.
Join the DZone community and get the full member experience.
Join For FreeMicroservices are becoming a very popular architectural style for developing greenfield applications and also for brownfield solutions. People are moving away from the monolith solutions due to added advantages like faster development and go-to-market, increased adoption of containerization and small teams, and a lighter codebase.
You can use either Spring Boot, Quarkus, or Lagom frameworks to develop the REST services. I would like to show how we can use Apache Camel, a leading opensource integration framework, to write REST services easier and quicker using the REST domain-specific language (DSL).
Camel is a Java-based implementation of Enterprise Integration Patterns (EIPs) that supports most of the EIP design patterns and newer integration patterns from microservice architecture.
Apache Camel can be run anywhere as a standalone and also embedded as a library within Spring Boot, Quarkus, Application Servers, and in the clouds.
Apache Camel helps to integrate with everything by supporting hundreds of components that are used to access databases, message queues, and APIs.
How to Use Apache Camel to Expose REST Service
The Camel REST allows the creation of REST services using Restlet, Servlet, and many such HTTP-aware components for implementation.
As you all know, Camel's main feature is the routing engine. Routes can be developed using either Java-based DSL or XML-based. In this article, I will follow the Java DSL to develop a REST service.
Defining Endpoints
To define the endpoints, we need to use the Apache Camel DSL with Java DSL (although you can use XML).
Java DSL is below:
rest("/api/products")
.get().route().to("...")
.post().route().to("...")
.delete().route().to("...");
It is similar to the Camel route but uses rest()
. We need to mention the component services use to expose the endpoint. Camel supports the below components to Bootstrap REST service implementation:
- Servlet
- Spark REST
- Netty HTTP
- Jetty
If you are planning to integrate Camel with Spring Boot framework to expose the services, it's better to use the servlet
component because Spring Boot supports embedded Tomcat, which Camel can use.
Let's configure the REST configuration as:
// Define the implementing component - and accept the default host and port
restConfiguration()
.component("servlet");
How to Override the Port
You can override the default port 8080 with any other port number of your choice either by setting the .port()
to restConfiguration()
API or, if you integrated the Apache Camel with Spring Boot, you can use the server.port=8082
in application.properties
.
Override the Context Path
By default, Camel maps imports requests to /camel/*
. You can override it to any specific path of your choice by using the application.properties
as camel.component.servlet.mapping.context-path=/services/api/*
.
Configure the binding mode to marshal the request to the POJO object. If set to anything other than "off," the producer will try to convert the body of the incoming message from inType to the JSON or XML, and the response from JSON or XML to outType. There are five enums and the value can be one of the following: auto, off, JSON, XML, or json_xml. To achieve this, you need to set the binding mode to restConfiguration()
as bindingMode(RestBindingMode.auto);
.
Take a look at the sample configuration of REST API below:
@Component
public class HttpRouteBuilder extends BaseRouteBuilder {
@Override
public void configure() throws Exception {
super.configure();
// it tells Camel how to configure the REST service
restConfiguration()
// Use the 'servlet' component.
// This tells Camel to create and use a Servlet to 'host' the RESTful API.
// Since we're using Spring Boot, the default servlet container is Tomcat.
.component("servlet")
// Allow Camel to try to marshal/unmarshal between Java objects and JSON
.bindingMode(RestBindingMode.auto);
rest().get("/kyc/{uid}").route().process("httpRequestProcessor").to("log:?level=INFO&showBody=true").endRest();
rest().post("/kyc").type(RequestObject.class).route().to("bean-validator:myvalidatorname")
.process("httpRequestProcessor").to("log:?level=INFO&showBody=true");
}
}
You can validate your incoming request using the Apache Camel bean validator component which requires the camel-bean-validator
dependency to be added to your Maven POM.
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-bean-validator</artifactId>
</dependency>
Define Validation Rules in Request Object
To implement input request validation, you need to add validation annotations to fields in your POJO/request class. These are available in the package javax.validation.constraints
. The most common ones from the JSR-303 API are:
@NotNull
— checks that the field is notnull
@AssertTrue
/@AssertFalse
— checks that the field is true or false@Pattern(regex=, flags=)
— checks that the field matches the givenregex
, with the givenflags
There are some Hibernate-specific annotations in org.hibernate.validator.constraints
, like:
@Email
— checks that the field contains a valid email address@CreditCardNumber
— this one’s probably obvious@NotEmpty
— checks whether the annotated field is not null nor empty
How to Handle Exceptions
You can handle different types of exceptions and send the custom error messages to the client using the Apache Camel exception clause (onException
) either at route level or at the global level. You can also override the response HTTP code and message for the REST API calls.
public class BaseRouteBuilder extends RouteBuilder {
@Override
public void configure() throws Exception {
onException(BeanValidationException.class).handled(true).process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
Throwable cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class);
exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
exchange.getMessage().setBody("{error:" + cause.getMessage() + "}");
}
});
onException(InvalidRequestException.class).handled(true).process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
Throwable cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class);
exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
exchange.getMessage().setBody("{error:" + cause.getMessage() + "}");
}
});
onException(Exception.class).handled(true).process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
Throwable cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class);
exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 500);
exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
exchange.getMessage().setBody("{error:" + cause.getMessage() + "}");
}
});
}
Note: Here I created a base class to handle all sorts of exceptions, and in my main REST API builder class (HttpRouteBuilder
), it extends BaseRouteBuilder
.
Finally, POM:
<dependencyManagement>
<dependencies>
<!-- Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Camel BOM -->
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-dependencies</artifactId>
<version>${camel.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-jackson-starter</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-servlet-starter</artifactId>
</dependency>
<!-- Testing Dependencies -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-swagger-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-bean-validator</artifactId>
</dependency>
</dependencies>
Summary
Now that you know how to expose REST API with Camel, you might be wondering when/why you should use Apache Camel to build REST services. The simple answer is if you are already using Apache Camel to integrate the data between different protocols and applications, then REST is another data source you need to support instead of building REST services with Spring Boot or any other framework. You can utilize the Camel REST component to expose REST API and to consume/produce messages using known Camel DSL, which helps you to standardize the technical stake. You can also extend the Camel REST to include Swagger for providing the API specification using the camel-swagger
component.
Opinions expressed by DZone contributors are their own.
Comments