Chapter 4. API Gateway with Apache Camel

Now that you know how to build microservices, you could continue building more and more. However, as the number of microservices grows, the complexity for the client who is consuming these APIs also grows.

Real applications could have dozens or even hundreds of microservices. A simple process like buying a book from an online store like Amazon can cause a client (your web browser or your mobile app) to use several other microservices. A client that has direct access to the microservice would have to locate and invoke them and handle any failures they caused itself. So, usually a better approach is to hide those services behind a new service layer. This aggregator service layer is known as an API gateway.

Another advantage of using an API gateway is that you can add cross-cutting concerns like authorization and data transformation in this layer. Services that use non-internet-friendly protocols can also benefit from the usage of an API gateway. However, keep in mind that it usually isn’t recommended to have a single API gateway for all the microservices in your application. If you (wrongly) decided to take that approach, it would act just like a monolithic bus, violating microservice independence by coupling all the microservices. Adding business logic to an API gateway is a mistake and should be avoided.

Apache Camel

Apache Camel is an open source integration framework that is well suited to implementing API gateways. The framework implements most of the patterns for enterprise application integration (EAI) described in the book Enterprise Integration Patterns, by Gregor Hohpe and Bobby Woolf (Addison-Wesley). Each enterprise integration pattern (EIP) describes a solution for a common design problem that occurs repeatedly in many integration projects. The book documents 65 EIPs, taking a technology-agnostic approach.

Apache Camel uses a consistent API based on EIPs to have a well-defined programming model for integration. With over 200 components, the developer can connect Apache Camel to almost any source/destination. In addition to HTTP, FTP, File, JPA, SMTP, and Websocket components, there are even components for platforms like Twitter, Facebook, AWS, etc.

Apache Camel is very powerful, yet very simple to use. This makes it an ideal choice for creating the API gateway for our microservices.

Apache Camel can be executed as a standalone application or be embedded in a existing application. For our API gateway example, we will use Camel in a Spring Boot application.

Getting Started

Camel applications can be created by declaring the Maven dependencies in an existing application, or by using an existing Maven Archetype.

Since we already showed how to use the Spring CLI to create the hello-springboot application, this time we will use the Maven Archetype approach.

The following command will create the Spring Boot application with Camel in a directory named api-gateway:

$ mvn archetype:generate -B \
   -DarchetypeGroupId=org.apache.camel.archetypes \
   -DarchetypeArtifactId=camel-archetype-spring-boot \
   -DgroupId=com.redhat.examples \
   -DartifactId=api-gateway \
   -Dversion=1.0

From the api-gateway directory, try running the following command:

$ mvn spring-boot:run

If everything boots up without any errors, you should see some logging similar to this:

2018-12-18  INFO 782 --- [main] MySpringBootApplication
    :Started MySpringBootApplication in 3.5 seconds
    (JVM running for 6.866)
Hello World
Hello World
Hello World
Hello World

Note that Hello World will be printed in the console every two seconds.

Building the API Gateway

Now that we have a Spring Boot application that can run Camel routes, let’s change the functionality that prints Hello World every two seconds to call the hello-springboot and hello-microprofile microservices.

When we deploy our microservices in a Kubernetes cluster in the next chapter, the only microservice that will be exposed to the outside world will be this API gateway. The API gateway will call hello-springboot and hello-microprofile, which will call the backend. Figure 4-1 shows the overall architecture of our microservices and their interaction.

Figure 4-1 Calling another service
Figure 4-1. Calling another service

Before we start modifying our code, we need to declare the dependencies that we will use to connect to our microservices using the HttpClient v4 library, the servlet to register the REST endpoints, and the JSON library to marshal the result.

Open up the pom.xml file for the api-gateway microservice and add the following Maven dependency in the <dependencies>...</dependencies> section:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-http4-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-servlet-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jackson-starter</artifactId>
</dependency>

Next, we’ll modify the MySpringBootRouter class to connect to both microservices as shown in Example 4-1. We have already learned that the @ConfigurationProperties(prefix="gateway") annotation connects the gateway string property to this class. The @Component annotation makes Spring Boot find and register this class as a Spring Bean. Later, Camel will look for every Spring Bean that extends the class RouteBuilder to be used to configure the Camel routes using the method configure().

Example 4-1. src/main/java/com/redhat/examples/MySpringBootRouter.java
@Component
@ConfigurationProperties(prefix="gateway")
public class MySpringBootRouter extends RouteBuilder {

    private String springbootsvcurl, microprofilesvcurl;

    private static final String REST_ENDPOINT=
        "http4:%s/api/greeting?httpClient.connectTimeout=1000"
        +
            "&bridgeEndpoint=true
        +
            &copyHeaders=true +

        &connectionClose=true";

    @Override
    public void configure() {
        from("direct:microprofile").streamCaching()
                .toF(REST_ENDPOINT, microprofilesvcurl)
                .log("Response from MicroProfile microservice:
                  ${body}")
                .convertBodyTo(String.class)
                .end();

        from("direct:springboot").streamCaching()
                .toF(REST_ENDPOINT, springbootsvcurl)
                .log("Response from Spring Boot microservice: +
                                                     ${body}")
                .convertBodyTo(String.class)
                .end();

        rest()
            .get("/gateway").enableCORS(true)
            .route()
                .multicast(AggregationStrategies.flexible()
                    .accumulateInCollection(ArrayList.class))
                .parallelProcessing()
                    .to("direct:microprofile")
                    .to("direct:springboot")
                .end()
            .marshal().json(JsonLibrary.Jackson)
            .convertBodyTo(String.class)
        .endRest();
    }

    public void setSpringbootsvcurl(String springbootsvcurl) {
        this.springbootsvcurl = springbootsvcurl;
    }

    public void setMicroprofilesvcurl(String microprofilesvcurl) {
        this.microprofilesvcurl = microprofilesvcurl;
    }

}

The from("direct:...") method in the MySpringBootRouter class declares a connector to the hello-microprofile and hello-springboot microservices. The URI used by this Camel connector is specified by the string declared in the REST_ENDPOINT final variable. The format http4:_hostname_[:_port_][/_resourceUri_][_?options_] is documented in the reference page for the HTTP4 Camel component.

After we have declared the destination routes direct:microprofile and direct:springboot, we declare our REST entry point using the method rest(). The method is followed by the verb get("/gateway"), which specifies what HTTP method and path this endpoint expects. This rest method also enables cross-origin resource sharing (CORS), which allows this endpoint to receive requests from a server on a different origin (domain).

Next, the REST request is routed via multicast to both routes using parallel processing, and the results are accumulated in an ArrayList collection. This is specified in the route(), multicast(), and parallelProcessing() methods. Finally, the result is marshaled into JSON format using the Jackson library in the .marshal().json (JsonLibrary.Jackson) method.

The Camel Fluent API is very flexible yet makes the code very expressive. With less than 20 lines of code, we just built a REST endpoint that connects to other microservices in parallel, collects the results, and transforms them to JSON.

Configuring the API Gateway

The API gateway needs to connect to hello-microprofile and hello-springboot. The addresses of these microservices must not be hardcoded and should be configured externally. For that reason, we used the variables springbootsvcurl and microprofilesvcurl to provide those “service URLs.”

Another configuration that we need provide to our application is the context path that Camel will use for the REST endpoints. By default, Camel will bind the REST endpoints to /camel/*, but we want to use /api/* instead. For that reason, we will also configure the property camel.component.servlet.mapping.context-path in the src/main/resources/application.properties file.

Let’s add all these properties to that file now:

gateway.springbootsvcurl=localhost:8180
gateway.microprofilesvcurl=localhost:8280

# to reconfigure the camel servlet context-path mapping
# to use /api/* instead of /camel/*
camel.component.servlet.mapping.context-path=/api/*

Calling All Microservices

Now that our API gateway is ready, we will start all four microservices locally using different ports:

  • backend: 8080

  • hello-springboot: 8180

  • hello-microprofile: 8280

  • api-gateway: 8380

To start up the backend service on port 8080, navigate to the backend directory and run the following:

$ mvn clean wildfly:run

To start up the hello-springboot service on port 8180, navigate to the hello-springboot directory and run the following:

$ mvn clean spring-boot:run -Dserver.port=8180

To start up the hello-microprofile service on port 8280, navigate to the hello-microprofile directory and run the following:

$ mvn thorntail:run \
  -Dswarm.network.socket-binding-groups.standard-sockets
  .port-offset=200

Finally, start the api-gateway service on port 8380. Navigate to the api-gateway directory and run the following:

$ mvn spring-boot:run  -Dserver.port=8380

Now, point your browser to http: //localhost:8380/api/gateway to verify that the API gateway calls the two microservices and aggregates both responses in a JSON array, as shown in Figure 4-2.

Figure 4-2 Successful API Gateway
Figure 4-2. Successful API gateway

Where to Look Next

In this chapter, you learned about Apache Camel and the API gateway pattern. You also learned how to expose REST endpoints, configure Apache Camel routes, and make calls to external services. This was meant as a quick introduction to Apache Camel and is by no means a comprehensive guide. Check out the following links for more information:

Get Microservices for Java Developers, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.