DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Leveraging Salesforce Using Spring Boot
  • Step-by-Step Guide to Use Anypoint MQ: Part 1
  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • How to Develop Microservices With Spring Cloud and Netflix Discovery

Trending

  • Segmentation Violation and How Rust Helps Overcome It
  • Building Scalable and Resilient Data Pipelines With Apache Airflow
  • Java's Quiet Revolution: Thriving in the Serverless Kubernetes Era
  • Enhancing Avro With Semantic Metadata Using Logical Types
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Aggregating REST APIs Calls Using Apache Camel

Aggregating REST APIs Calls Using Apache Camel

In this post, we are going to build an aggregator for API calls using the Enricher EIP. A tutorial on how to aggregate sequential calls to REST APIs in a single response.

By 
André Luiz De Gusmão user avatar
André Luiz De Gusmão
·
Nov. 03, 21 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
21.4K Views

Join the DZone community and get the full member experience.

Join For Free

What is Apache Camel?

As described by the creator itself, Claus Ibsen on Apache Camel in Action, 2nd edition:

At the core of the Camel framework is a routing engine—or more precisely, a routing-engine builder. It allows you to define your own routing rules, decide from which sources to accept messages, and determine how to process and send those messages to other destinations. Camel uses an integration language that allows you to define complex routing rules, akin to business processes. As shown in Figure 1.1, Camel forms the glue between disparate systems.

One of the fundamental principles of Camel is that it makes no assumptions about the type of data you need to process. This is an important point, because it gives you, the developer, an opportunity to integrate any kind of system, without the need to convert your data to a canonical format.

Among the most diverse integration solutions that we can build with Camel. In this article, we're gonna build an aggregator for API calls using the Enricher EIP. The project presented in this article is available on Github.

The Scenario

To demonstrate how the Enricher EIP can be used with Camel we're going to build an application that aggregates calls from two APIs:

  • Authors API with the endpoints:

    • [GET] /authors

    • [GET] /authors/{name}

  • Books API with the endpoint:

    • [GET] /books/{authorId}

Our Camel application has two endpoints:

  • [GET] /integration/authors - Query Authors API.

  • [GET] /integration/authors/{name} - Search author by name (eg: `jr-tolkien`, `jk-rowling`) and enriches the response with all authors' books.

The Camel Application

Creating Project

To create the project, we can access https://start.spring.io/ and fill the project metadata with these parameters: 

YAML
 
Spring Boot: 2.3.1

Group: br.com.camel.bookstore

Artifact: camel-bookstore-aggregator

Version: 1.0

Name: camel-bookstore-aggregator

Dependencies: 'Apache Camel'


Next, click on Generate to create the project and then edit the pom.xml file to add the dependencies that we're going to use (undertow, rest, http).

The pom.xml should look like the following:

XML
 
<?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>
     <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.3.1.RELEASE</version>
          <relativePath/> <!-- lookup parent from repository -->
     </parent>

     <groupId>br.com.camel.bookstore</groupId>
     <artifactId>camel-bookstore-aggregator</artifactId>
     <version>1.0</version>
     <name>camel-bookstore-aggregator</name>
     <properties>
          <java.version>1.8</java.version>
          <camel.version>3.1.0</camel.version>
     </properties>

     <dependencies>
          <dependency>
               <groupId>org.apache.camel.springboot</groupId>
               <artifactId>camel-spring-boot-starter</artifactId>
               <version>${camel.version}</version>
          </dependency>        

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

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

          <dependency>
               <groupId>org.apache.camel</groupId>
               <artifactId>camel-http</artifactId>
               <version>${camel.version}</version>
          </dependency>
       
       ...
       
     </dependencies>
</project>


The Rest Route

We're going to create two classes with Camel routes. The endpoint will be in the RestRoute and the routes that access the APIs and enrich content will be in the RestAggregatorRoute class.

Java
 
@Component
public class RestRoute extends RouteBuilder {

  @Override
  public void configure() throws Exception {
    //Define the server configurations like host address and port
    restConfiguration()
      .host("0.0.0.0").port(8080)
      .bindingMode(RestBindingMode.auto);
 
    //Mapping the REST endpoints
    rest("/integration")
      //Endpoint that queries all authors
      .get("/authors")
        .route().routeId("rest-all-authors")
        .to("direct:call-rest-all")
      .endRest()
     
      //Endpoint with the enrich EIP
      .get("/authors/{name}")
        .route().routeId("rest-author-by-name")
        .to("direct:call-rest-author")
      .endRest();
  }
}


The endpoints /integration/authors e /integration/authors/{name} when called invoke the routes direct:call-rest-all and direct:call-rest-author respectively, they are defined in the other class.

The Integration Route

Querying the Authors API

Our route uses the http component to consume the Authors API and return all authors.

Java
 
from("direct:call-rest-all")
  .routeId("all-service")
  .removeHeaders("CamelHttp*")
  .setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://{{authors.url}}/authors");


In the route, we use the removeHeaders("CamelHttp*") ensuring that our API call has only HTTP component-related headers, and we're going to use the HTTP_METHOD header with GET as value. The .to method receives "http://{{authors.url}}/authors" as a parameter, when we use double brackets around authors.url, Camel is able to replace the value from the application.properties, so the file must contain the URL value, for instance:

YAML
 
#application.properties

authors.url=localhost:8081


That is all we need for this route. The Authors API endpoint response is returned straight from the Camel application.

Enriching and Aggregating Author and Books Responses

The direct:call-rest-author route search for the author by name and we use the response to retrieve the authors id, after we enrich the response with the author's books, according to the image:

The code for the route is the following:

Java
 
from("direct:call-rest-author")
  .routeId("call-rest-services")
  .to("direct:author-service")
    .process(new GetAuthorIdProcessor())
  .enrich("direct:books-service", new JsonRestCallsAggregator());


Let’s approach the parts individually:

  • to("direct:author-service"): Like the endpoint that returns all authors, we make a similar call to the authors API:

Java
 
from("direct:author-service")
  .routeId("author-service")
  .removeHeaders("CamelHttp*")
  .setHeader(Exchange.HTTP_METHOD, constant("GET"))
.toD("http://{{authors.url}}/authors/${header.name}");


When we call the /integration/authors/{name} endpoint from our Camel app, the name passed as path becomes available as a header of the exchange (eg: /integration/authors/jr-tolkien; /integration/authors/jane-austin), because the path is variable depending on the name we have to use .toD instead of the .to method.

The author API response is a JSON with the author information, to retrieve the id we can parse this JSON, but it's located encapsulated inside the exchange message,  that Camel uses to pass data between integrations. In the http component, the response is stored in the body of the In message, like the following image:

More details about the exchange can be found in the JavaDoc: (https://www.javadoc.io/doc/org.apache.camel/camel-api/latest/org/apache/camel/Exchange.html) or in the Camel in Action book (https://livebook.manning.com/book/camel-in-action-second-edition).

  • process(new GetAuthorIdProcessor()): For retrieving the id we're using a processor to read the exchange, the GetAuthorIdProcessor class implements org.apache.camel.Processor for that purpose.

Java
 
public class GetAuthorIdProcessor implements Processor {
     @Override
     public void process(Exchange exchange) throws Exception {
        String author = exchange.getIn().getBody(String.class);
        JsonParser parser = JsonParserFactory.getJsonParser();
        Map<String, Object> jsonMap = parser.parseMap(author);
        String authorId = (String) jsonMap.get("id");   

        exchange.getIn().setHeader("id", authorId);
        exchange.getIn().setBody(author);
    }
}


The process method is responsible for reading the authors' API response, which lies in the In message body. For parsing the json we're using some classes available in the Spring Boot framework. After retrieving the ID, we create a header with the name id and set the author response JSON back in the In message body.

  • enrich("direct:books-service", new JsonRestCallsAggregator()): This line is responsible for making the call for the book API and aggregating the responses (author and books) in the class `JsonRestCallsAggregator`.

Java
 
from("direct:books-service")
  .routeId("books-service")
  .removeHeaders("CamelHttp*")
  .setHeader(Exchange.HTTP_METHOD, constant("GET"))
.toD("http://{{books.url}}/books/${header.id}");


Similar to the direct:author-service call we use the http component to call the book API. Because the endpoint receives an id in the path we use the one that we extract from the authors' response in the process(new GetAuthorIdProcessor()), like in the following image:

The value {{books.url}} should be declared in the application.properties, like for instance:

YAML
 
#application.properties
authors.url=localhost:8081
books.url=localhost:8082


The class responsible for the responses aggregation implements org.apache.camel.AggregationStrategy:

Java
 
public class JsonRestCallsAggregator implements AggregationStrategy {
    @Override
    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
        JsonParser parser = JsonParserFactory.getJsonParser();

        String books = newExchange.getIn().getBody(String.class);
        String author = oldExchange.getIn().getBody(String.class);       

        JsonObject authorJson = new JsonObject(parser.parseMap(author));        
        authorJson.put("books", new JsonArray(parser.parseList(books)));

        newExchange.getIn().setBody(authorJson.toJson());
        return newExchange;
    }
}


The aggregate method has two parameters, the exchange before and after the enrich invocation. Using Spring Boot classes to parse and manipulate JSON, we can retrieve the author JSON (oldExchange) and aggregate with the books from the author JSON (newExchange), like in the image:

Our response aggregation will generate a final response JSON with the following format:

JSON
 
{
    "author": {
        "id": "...",
        "name": "...",
        "fullName": "...",
        "books": [ ... ]
    }
}


Handling Failure

With our Camel application running, we can make calls to the /integration/authors/{name} endpoint, and the app are going to make calls to the two APIs and aggregate the result. But what happens if we have a failure in one of the APIs?

Let's cover two cases and refactor our Camel route to handle those failures. 

Failure Scenarios

Book API failure: In our scenario, if we call the book API endpoint (/books/{authorId}) using an authorId that does not have books registered, the API will respond with 404 (Not Found). To avoid errors in our Camel app, we are going to refactor our RestAggregatorRoute class.

The http Camel component already had implemented to throw a HttpOperationFailedException if the HTTP response status code returned 404 or above.

Java
 
//Without error handler
from("direct:books-service")
  .routeId("books-service")
  .removeHeaders("CamelHttp*")
  .setHeader(Exchange.HTTP_METHOD, constant("GET"))
.toD("http://{{books.url}}/books/${header.id}");


//With error handler
from("direct:books-service")
  .routeId("books-service")
  //Exception handler
  .onException(HttpOperationFailedException.class)
    .handled(true)
    .setBody(constant("[]"))
  .end()
  .removeHeaders("CamelHttp*")
  .setHeader(Exchange.HTTP_METHOD, constant("GET"))
.toD("http://{{books.url}}/books/${header.id}");


With the refactor, every time we have a 404 response from the books API, the Camel app returns a json empty array to complete the enrichment and aggregation.

Authors API failure: In our scenario, if we call the authors API endpoint (/authors/{name}) using a name not registered, the API will return 204 (No Content). If that happens, we must terminate the request at that point, avoiding the enrich and aggregation. The 204 status code is in the success family, so Camel http doesn't throw HttpOperationFailedException. To handle this we can check the response status code, and if it is 204 return another status, like 204 itself, 404, or other status code.

Java
 
//Without error handler
from("direct:call-rest-author")
  .routeId("call-rest-services")
  .to("direct:author-service")
    .bean("authors", "getId")
  .enrich("direct:books-service", new JsonRestCallsAggregator());


//Variables
private static final int OK_CODE = 200;
private static final int APP_RESPONSE_CODE = 204;

//With error handler
from("direct:call-rest-author")
  .routeId("call-rest-services")
  .to("direct:author-service")
  .choice()
    .when(header(Exchange.HTTP_RESPONSE_CODE).isEqualTo(OK_CODE))
      .bean("authors", "getId")
      .enrich("direct:books-service", new JsonRestCallsAggregator())
  .otherwise()
    .setHeader(Exchange.HTTP_RESPONSE_CODE).constant(APP_RESPONSE_CODE);


With this refactor, every time the authors API returns 204 the Camel app returns the response code defined in the APP_RESPONSE_CODE variable, ending the request, avoiding a call to the books API and a possible JsonParseException.

Conclusion

The Apache Camel is a lightweight framework capable of building the most diverse types of integrations as presented in this article. The Enricher EIP, just like others, can be very handy to build those applications. For more details about each one, you can read the documentation here.

Next Steps

The Camel application implemented in this article has some improvement possibilities like the addition of a new endpoint or APIs, an improvement on the error handling (checking new exceptions), the inverse mapping searching a book, and enriching the response with the author information. I hope the article gave you insights into Camel and motivates you to build integration apps using Apache Camel.

Apache Camel REST Web Protocols API Book Spring Framework application app Java (programming language) JSON

Opinions expressed by DZone contributors are their own.

Related

  • Leveraging Salesforce Using Spring Boot
  • Step-by-Step Guide to Use Anypoint MQ: Part 1
  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • How to Develop Microservices With Spring Cloud and Netflix Discovery

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: