Quarkus Camel, First Impressions on a Game Changing Framework

Quarkus Camel, First Impressions on a Game Changing Framework

March 15, 2019 ( last updated : September 17, 2019 )
quarkus kubernetes camel

https://github.com/alainpham/quarkus-camel-first-impressions


Abstract

On March 07, 2019 Red Hat & the JBoss Community announced Quarkus. It is a Java framework that enables ultra low boot times and tiny memory footprint for applications and services. It does that by taking advantage of GraalVM's native compilation capabilities to produce an executable out of your Java code. Why is this considered to be a game changer ? In this article I will give my view and first impressions on this framework as someone who spends a bit of time writing applications and services. I will get my hands dirty by building a working Quarkus + Apache Camel example.

What's the big deal about Quarkus?

When Quarkus was announced 1 week ago, many have commented that this is a game changer for the future of Java Application Development. I must say that I have rarely been so excited about a project as I am right now for Quarkus ! So what's the big deal here ?

The official website quarkus.io states that Quarkus can be qualified as "Supersonic Subatomic Java". It is a "A Kubernetes Native Java stack tailored for GraalVM & OpenJDK HotSpot, crafted from the best of breed Java libraries and standards".

The existing Java ecosystem

To understand the huge value that Quarkus brings, we have to take a step back and get in the shoes of a typical Enterprise Dev Team. Java has been so broadly adopted that it has become the default technology for application development in many organizations. Throughout the years a huge ecosystem of tools and libraries flourished around Java to solve all kinds of challenges and problems.

The adoption of containers combined with Java

The recent adoption of Containers and Kubernetes has lead to a wave of innovation around workload orchestration and scheduling. Still, organizations wanted to keep leveraging the expertise around Java their developers have acquired. It became a natural practice to put Java applications and micro services into containers to get the best of both worlds. Organizations wanted a rich ecosystem of libraries and all the innovations around container orchestration at the same time.

os container jvm app stack

The paradigm shift needed for Java to exist in the world of Containers

When a micro service needs to be scaled up and down quickly is when Java shows some weakness. In fact Java has not been designed for being a short lived process that might be scheduled anywhere on a distributed infrastructure. It has rather been designed and optimized to run application servers for a long time and taking up all the resources available on a VM or a bare metal server. The boot time was not such a critical constraint. In usual Java application the boot time of 4 to 10 seconds (and even more) was entirely acceptable. The initial memory footprint is usually also negligible due to the fact that a large amount of memory would be dedicated to the application server anyway.

The paradigm has shifted in the world of containers and Kubernetes. The boot time has become highly critical. A low boot time is what would allow a service to scale up and down quickly and achieve higher workload density on cloud infrastructures. A minimal initial memory footprint per instance is what would allow the service to start small and be scaled efficiently when needed. In the extreme case, when considering the rise of Functions as a Service booting up quickly becomes even more critical. In this case we aim at scaling the service down to zero instances when there are no requests and starting it only when client requests are coming in. We can see here that the boot time needs to be several orders of magnitude lower than what we are used to.

scaling up from 0

Quarkus to the rescue

Given this context, we can now better understand the value that Quarkus brings. In short, it's a framework that enables dev teams to use widely adopted Java libraries to build applications that have an extreme low boot time and low initial memory footprint. Quarkus takes advantage of GraalVM to compile java projects into a native executable that can just run.

Get started : build a hello world

Now lets go ahead and build our first hello world to see how it performs.

Create a simple project using the quarkus-maven-plugin

mvn io.quarkus:quarkus-maven-plugin:0.11.0:create \
-DprojectGroupId=techlab \
-DprojectArtifactId=quarkus-hello-world \
-DclassName="techlab.GreetingResource" \
-Dpath="/hello"

This is the structure of the project :

|-- pom.xml
|-- quarkus.log
|-- src
| |-- main
| | |-- docker
| | | `-- Dockerfile
| | |-- java
| | | `-- techlab
| | | `-- GreetingResource.java
| | `-- resources
| | `-- META-INF
| | |-- microprofile-config.properties
| | `-- resources
| | `-- index.html

The dependencies are pretty minimalist and clean since most of it is taken care of by the quarkus bom and plugins.

<dependencyManagement>
    <dependencies>
        <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-bom</artifactId>
        <version>${quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
....
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-arc</artifactId>
</dependency>
...
<build>
    <plugins>
        <plugin>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-maven-plugin</artifactId>
            <version>${quarkus.version}</version>
...

The plugin creates a simple project with a rest service operation returning hello as you can see in the file GreetingResource.java. It is pretty much the only thing that needs to be done here to have a running service. Quarkus is rather an opinionated framework that aims at accelerating development using configuration files and annotations.

package techlab;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
    return "hello";
    }
}

Let's run and test the project

Now lets go ahead and run this project to see how fast it actually boots up.

mvn compile quarkus:dev
[apham@aplinux quarkus-hello-world]$ mvn compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building quarkus-hello-world 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ quarkus-hello-world ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ quarkus-hello-world ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- quarkus-maven-plugin:0.11.0:dev (default-cli) @ quarkus-hello-world ---
[INFO] Using servlet resources /home/workdrive/TAZONE/WORKSPACES/ws-quarkus/quarkus-hello-world/src/main/resources/META-INF/resources
Listening for transport dt_socket at address: 5005
2019-03-17 23:10:29,640 INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
2019-03-17 23:10:29,980 INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 340ms
2019-03-17 23:10:30,155 INFO  [io.quarkus] (main) Quarkus 0.11.0 started in 0.602s. Listening on: http://127.0.0.1:8080
2019-03-17 23:10:30,156 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

We should bare in mind that here we are using the OpenJDK HotSpot and are not yet compiling our project to a native executable. The results are already pretty remarkable as I could observe a boot time of 602 milliseconds.

(Edit Sept 17, 2017, prior to this date all my time conversions from seconds to milliseconds have been wrong. Thank you Faris for pointing it out :) )

Compile to native executable and run

In order to compile the project we need to setup GraalVM. Download the VM from the following website http://www.graalvm.org/downloads/. Extract the package somewhere and set the env variable GRAALVM_HOME to point to the root of its folder.

Now we can run the following command to build a native executable.

mvn package -Pnative

We run the native executable with the following command.

./target/quarkus-hello-world-1.0-SNAPSHOT-runner
[apham@aplinux quarkus-hello-world]$ ./target/quarkus-hello-world-1.0-SNAPSHOT-runner
2019-03-17 23:34:04,690 INFO [io.quarkus] (main) Quarkus 0.11.0 started in 0.004s. Listening on: http://127.0.0.1:8080
2019-03-17 23:34:04,691 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]

We can observe here that the boot time is enhanced by a significant order of magnitude which is 4 milliseconds.

(Edit Sept 17, 2017, prior to this date all my time conversions from seconds to milliseconds have been wrong.)

Enhanced Developer Experience

Now the good surprises don't end there. Since the boot times are so good, it also solve a big challenge for development workflow. In fact, Quarkus also implements a livereload mechanism to reflect the changes performed on the code instantaneously. Try to change some of the code in the project, hit Ctrl+s and we can observe how fast the service reloads to take the changes into account. This is a big leap for developer productivity. As we can see here it took only 386 milliseconds to reload.

(Edit Sept 17, 2017, prior to this date all my time conversions from seconds to milliseconds have been wrong.)

2019-03-18 09:59:58,988 INFO  [io.qua.dev] (XNIO-1 task-1) Changes source files detected, recompiling [/home/workdrive/TAZONE/WORKSPACES/ws-quarkus/quarkus-camel-first-impressions/src/main/java/techlab/GreetingResource.java]
2019-03-18 09:59:59,274 INFO  [io.quarkus] (XNIO-1 task-1) Quarkus stopped in 0.001s
2019-03-18 09:59:59,275 INFO  [io.qua.dep.QuarkusAugmentor] (XNIO-1 task-1) Beginning quarkus augmentation
2019-03-18 09:59:59,366 INFO  [io.qua.dep.QuarkusAugmentor] (XNIO-1 task-1) Quarkus augmentation completed in 91ms
2019-03-18 09:59:59,373 INFO  [io.quarkus] (XNIO-1 task-1) Quarkus 0.11.0 started in 0.098s. Listening on: http://127.0.0.1:8080
2019-03-18 09:59:59,373 INFO  [io.quarkus] (XNIO-1 task-1) Installed features: [cdi, resteasy]
2019-03-18 09:59:59,373 INFO  [io.qua.dev] (XNIO-1 task-1) Hot replace total time: 0.386s

Let's add Camel to the mix

Now that we have our hello world running. Let's try to add Camel to the mix to enable reusable integration patterns and connectors in our projects. Quarkus already offers a variety extensions and Camel is one of those. At the time of writing there are just a few extensions available. We won't get the whole set of 200+ Camel components compiled to native executable packages, but I'm quite sure that the Quarkus ecosystem will grow quickly.

Let's list the available extensions.

mvn quarkus:list-extensions

Then let's add the camel extension to it.

mvn quarkus:add-extension -Dextensions=io.quarkus:quarkus-camel-core

As a result you should see the dependency to the quarkus camel extension being added to the pom file

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-camel-core</artifactId>
</dependency>

Lets create a simple class containing a Camel route to see if things work. Add a new class called CamelRouteBuilder to the source folder. With the following content.

package techlab;

import org.apache.camel.builder.RouteBuilder;

public class CamelRouteBuilder extends RouteBuilder {

    public void configure() {
        from("timer://testTimer").log("test");
    }
}

At the time of writing there is no documentation on how to get started with camel in Quarkus. I found an error just trying to run this class complaining about properties not being available.

Property with key [camel.defer] not found

Searching through the source code I found that it is looking some properties in the application.properties file. The one file of interest can be found here

Update July 4, 2019 : As of today, version 0.18 is out now the camel core plugin has changed and does not require to add those properties anymore

So lets go ahead and create a file named application.properties in the folder src/main/resources with the following content

camel.defer=false
camel.conf=
camel.confd=
camel.routesUri=
camel.dump=false

Now we can run our project with

mvn compile quarkus:dev

2019-03-18 10:43:27,702 INFO  [io.qua.cam.cor.run.FastCamelContext] (main) Route: route1 started and consuming from: timer://testTimer
2019-03-18 10:43:27,771 INFO  [io.quarkus] (main) Quarkus 0.11.0 started in 1.509s. Listening on: http://127.0.0.1:8080
2019-03-18 10:43:27,771 INFO  [io.quarkus] (main) Installed features: [camel-core, cdi, resteasy]
2019-03-18 10:43:28,720 INFO  [route1] (Camel (camel-1) thread #1 - timer://testTimer) test
2019-03-18 10:43:29,703 INFO  [route1] (Camel (camel-1) thread #1 - timer://testTimer) test

We can note that the boot time is a bit higher when including camel, it is now 1.509s.

Let's compile it to a native executable and see how it behaves.

 
mvn package -Pnative
/target/quarkus-camel-first-impressions-1.0-SNAPSHOT-runner
2019-03-18 11:05:57,835 INFO  [io.qua.cam.cor.run.FastCamelContext] (main) Route: route1 started and consuming from: timer://testTimer
2019-03-18 11:05:57,838 INFO  [io.quarkus] (main) Quarkus 0.11.0 started in 0.007s. Listening on: http://127.0.0.1:8080
2019-03-18 11:05:57,838 INFO  [io.quarkus] (main) Installed features: [camel-core, cdi, resteasy]
2019-03-18 11:05:58,835 INFO  [route1] (Camel (camel-1) thread #0 - timer://testTimer) test

We can see that the boot time is of 7 milliseconds!

(Edit Sept 17, 2017, prior to this date all my time conversions from seconds to milliseconds have been wrong.)

In summary, why is Quarkus so amazing ?

Other resources

Originally published March 15, 2019
Latest update September 17, 2019

Related posts :