Win $20,000. Help build the future of education. Answer the call. Learn more

Explore lean serverless Java with Quarkus command mode

Quarkus has been gaining developer mindshare over the past couple of years since its release. The Quarkus motto of “supersonic, subatomic Java” means that Java developers have a complete framework for building fast-starting, lean-running Java applications for microservices and serverless deployment. In this article, we won’t cover the basics of Quarkus but we will show how to make Quarkus applications start even faster and run more efficiently.

What’s the magic behind making Quarkus even more extreme? The recently released Quarkus command mode. You can read more about Quarkus command mode. Quarkus has the ability to compile to a native executable, which gives near instant startup, but doing so has some trade-offs. What we want to show you here is how to run Quarkus in standard JVM mode, so there are no compromises. Quarkus command mode was originally created to build lightweight command-line programs; we’ll use it to build serverless cloud functions.

Deployment into Apache OpenWhisk

The environment we’ll be deploying to is Apache OpenWhisk, which is what IBM Cloud Functions are based on. Apache OpenWhisk is an open source serverless platform that supports most popular programming languages and is a system for the deployment and management of IBM Cloud Functions. If you’re not familiar with OpenWhisk or IBM Cloud Functions, don’t worry; we’ll show you how easy it is to build and deploy into this platform.

Understanding the ActionLoop architecture

You can skim this section if you’re not interested in the low-level mechanics, but you might find it useful when you’re building your Quarkus OpenWhisk actions. The preferred way for building and deploying your code into OpenWhisk is to use what is called the ActionLoop Protocol. A lightweight Go binary serves as the HTTP endpoint, which in turn starts your code (app), sends the request, and expects a response – orchestrated over stdin and stdout (over a specific UNIX file descriptor).

What this means for the code we’re actually writing is this: We don’t need to start an HTTP endpoint or, in fact, have any other plumbing or overhead, so our code can be very lean. The Go HTTP server starts within a few milliseconds and uses little memory, so there’s little overhead to this approach.

Quarkus and ActionLoop

To build a custom action platform in OpenWhisk, we simply need to create a Docker container that follows the OpenWhisk interface. Since we’re building to the specific ActionLoop protocol, we will have a Docker image that does the things below. Note that we’ve already done this for you; you can simply use our pre-built Docker image, or build one yourself and push it into Docker hub so OpenWhisk and IBM Cloud Functions can access it to deploy your app. The Quarkus ActionLoop Docker image:

  • Contains the Go HTTP server mentioned in the previous section
  • Bundles a specific version of the Quarkus libraries you are building against
  • Contains scripts to start the Quarkus application
  • Contains the Eclipse OpenJ9 JVM, Java 11
  • Accepts a JAR file with your Quarkus “runner” application

When building your Quarkus OpenWhisk Cloud Function, using this approach, there are no limitations:

  • Can use any libraries
  • Uses standard Quarkus configuration
  • The provided example uses the Jackson JSON library

Clone the project and follow along. Here’s the GitHub project. First, we need to build the JAR file:

  • cd actionloop-quarkus/quarkus
  • mvn package

That will download the dependencies. And we’ll have a JAR file in the target directory, which we’ll use later to deploy to the Cloud. Do the following to test it out locally:

mvn quarkus:dev

Copy the contents of test.json from the GitHub repo and paste it into the console where you ran the above command.

{ "value":{"name" :  "pratik"}}

The app will run and return: {"greeting":"hello pratik"}.

Have a quick look at our application code in the src/main/java/org/apache/openwhisk/sample/GreetingMain.java file.

@ApplicationScoped
public class GreetingMain implements OWMainInterface {


    @Inject
    GreetingService service;


    @Override
    public JsonNode run(JsonNode rootNode) throws Exception {
        String name = rootNode.at("/name").asText();
        ObjectNode response = JsonNodeFactory.instance.objectNode();
        response.put("greeting", service.greeting(name));
        return response;
    }
}

As you can see, we are doing dependency injection of a service with @Inject. We need to mark this object — our primary entry point for our IBM Cloud Function — as @ApplicationScoped and implement the OWMainInterface. We’re using Jackson to read the request as a JSON object, and we generate a return JSON object.

Web development

OpenWhisk Cloud Functions (Actions) can be executed using an HTTP endpoint – you just mark it as such using the “–web true” flag from the IBM Cloud CLI or from the IBM Cloud Functions dashboard. So how do you handle the HTTP headers, find out the type of request (GET, POST, etc.)? All of that data will be in the rootNode object, which contains the request information. Pull the specific data you need out of that rootNode object and process it accordingly. You can use some code like so for the incoming request JSON:

String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
System.out.println(json);

In OpenWhisk, anything you send to standard out and error will get logged, so you can view it in the IBM Cloud Functions activation log or dashboard.

Deploy and test sample cloud function

Once you’ve done mvn package, you are ready to deploy the function and test it:

ibmcloud fn action create QCLITest1 target/quarkus-openwhisk-action-1.0.0-SNAPSHOT-runner.jar --docker prpatel/actionloop-quarkus-1.8.1:latest
ibmcloud fn action invoke QCLITest1 --result -p name pratik

That will print this out to the console:

{
    "greeting": "hello pratik"
}

Notice in the first bullet above how we specify the “–docker” option for our Quarkus-ActionLoop Docker image. Version 1.8.1 indicates the specific version of the Quarkus base libraries, which are included in the container. You can include more libs or upgrade the version yourself.

To run it from a browser:

  • ibmcloud fn action update QCLITest1 –web true (enable for HTTP/web invocation)
  • ibmcloud fn action get QCLITest1 –url (get the URL)
  • Open a browser and invoke it by appending “.json” to the end

Troubleshooting locally and rebuilding the container

If you run into any issues when you deploy the application, check the logs and run this in a separate terminal window:

ibmcloud fn activation poll

This is like a tail -f on all your IBM Cloud Functions executions.

You can also use some tooling included in the GitHub repo to test things locally. This section also includes instructions on how to rebuild the Quarkus-ActionLoop container and push it to GitHub — just don’t forget to change the tag in the makefile and when you create the action using ibmcloud fn action create. Also, the makefile will rebuild and push the container to your Docker Hub account.

Conclusion

In this article, we explained the integration we’ve built for Quarkus running inside of OpenWhisk and IBM Cloud Functions. This is a lightweight and efficient solution. We can make it even more lightweight by using some of the features of the JVM we bundled in the Docker container, Eclipse OpenJ9, such as class data sharing.