Serverless functions enables you to decompose business operations into very fine grain chunks of code. Functions provide advantages to an organization by increasing agility and cost savings by allowing "scale to zero" when a function is no longer actively being used.
In this quicklab we will look at how to write Serverless Functions in Java and run them on Apache Openwhisk hosted on IBM Cloud.
-
Go to https://cloud.ibm.com and click Log in on the top right of the page to log on with your IBM ID.
-
Once logged in, open an IBM Cloud Shell by clicking (1)
and wait until the session is ready to use.
-
The default region for the shell is
us-south. Europe based Trial Account users most likely haveeu-gbas their region. If necessary, switch region by typing:ibmcloud target -r <your_region>
where
<your_region>is the value of the region your need to switch to. Contact the workshop organiser if you're not sure what your region is. -
Next, select the default resource group by typing:
ibmcloud target -g default
-
Then, create a Cloud Functions namespace:
ibmcloud fn namespace create <YOUR_NAMESPACE_NAME>
-
And complete the IBM Cloud CLI configuration by typing:
ibmcloud fn namespace target <YOUR_NAMESPACE_NAME>
-
Make sure you select the IBM Cloud Shell tab in your browser. On the command line clone the repo by typing:
git clone https://github.com/IBMDeveloperBNL/java-serverless-quicklab
-
Change directory to the cloned repo:
cd java-serverless-quicklab
-
Run the following command to invoke a test function from the command-line:
ibmcloud fn action invoke whisk.system/utils/echo -p message hello --result
You should get back a result that looks like this:
{ "message": "hello" }This command verifies that IBM Cloud CLI is configured correctly . If this does not work, please contact the workshop organiser to provide assistance!
Let's build and deploy our own Java serverless function.
-
Build and jar the Java application:
./mvnw package
-
Deploy the function to IBM Cloud:
ibmcloud fn action create helloJava target/hello-world-java.jar --main com.example.FunctionApp
-
Execute the function:
ibmcloud fn action invoke --result helloJava --param name World
You should see:
{ "greetings": "Hello World" }--resultmeans wait for the call to complete and show the results. Adding--resultto the call implicitly makes it a blocking call as well. Next, omit that and see what you get back :)
Let's take a deeper look at some of the common commands you will be using when running functions on OpenWhisk.
So far we have been executing functions synchronously with the --result tag. Let's take a look at executing functions asynchronously.
-
To execute a function in asynchronous mode simply omit
--resultwhen invoking the function:ibmcloud fn action invoke helloJava --param name World
You should get a response that includes an id you can use to look up the result of the function later:
ok: invoked /_/helloJava with id c51e11cf3bad42a39e11cf3badb2a3a3 -
Use the below command to retrieve the result of the function invocation:
ibmcloud fn activation result [id]
You should get a response that looks something like this:
{ "greetings": "Hello World" }Note: Functions execute in asynchronous mode by default, you can also use the tag
--blockingto explicitly invoke a function and then wait for it to complete.
When invoking a function OpenWhisk is generating diagnostic information that can be used for tracking system usage, debugging, and other purposes.
-
You can view the invocation information of the function we executed earlier with this command:
ibmcloud fn activation get [id]
You should get a response back that looks something like this:
{ "namespace": "e7417dfb-0d1f-4a7e-b74a-c4c2d49c2b39", "name": "helloJava", "version": "0.0.1", "subject": "edward_ciggaar@nl.ibm.com", "activationId": "eaf1c26406874a2fb1c26406877a2f02", "start": 1670338759140, "end": 1670338759143, "duration": 3, "statusCode": 0, "response": { "status": "success", "statusCode": 0, "success": true, "result": { "greetings": "Hello World" } }, "logs": [ "2022-12-06T14:59:19.143076Z stderr: Dec 06, 2022 2:59:19 PM com.example.FunctionApp main", "2022-12-06T14:59:19.143104Z stderr: INFO: invoked with params:" ], "annotations": [ { "key": "path", "value": "e7417dfb-0d1f-4a7e-b74a-c4c2d49c2b39/helloJava" }, { "key": "waitTime", "value": 320 }, { "key": "transId", "value": "10c98e55b43d67cbe963ed76e70d1c7b" }, { "key": "kind", "value": "java:8" }, { "key": "timeout", "value": false }, { "key": "limits", "value": { "concurrency": 1, "logs": 10, "memory": 256, "timeout": 60000 } } ], "publish": false }
ibmcloud fn activation get returns the logs from an invocation, but you can also just view the logs from invocation to make debugging a bit easier.
-
To view the logs from an invocation run the following:
ibmcloud fn activation logs [id]
You should get a return thaty looks like this:
2022-12-06T14:59:19.143076Z stderr: Dec 06, 2022 2:59:19 PM com.example.FunctionApp main 2022-12-06T14:59:19.143104Z stderr: INFO: invoked with params: -
For longer running functions, you can tail the logs a function is producing with the following command:
ibmcloud fn activation poll [id]
For shorthand purposes you can use the tag --last in-lieu of an id to retrieve information about an activation.
ibmcloud fn activation [get|result|logs] --lastYou can view recent function invocations; id, function executed with the following:
ibmcloud fn activation listYou can view a list of all functions available in the current namespace with the following:
ibmcloud fn listFunctions can be setup so they can be called directly over http as well. Let's take a look at how to do this.
-
To allow a function to be executed over http run the following command:
ibmcloud fn action update helloJava --web true -
To find the url to execute the function run the following:
ibmcloud fn action get helloJava --url
This command will return with the url to call you function:
https://[region].functions.cloud.ibm.com/api/v1/web/SAMPLE_URL/default/helloJava -
Because this command returns JSON, we will need to append the end of the url with
.jsonwhen calling it:curl -i https://[region].functions.cloud.ibm.com/api/v1/web/SAMPLE_URL/default/helloJava.json
-
You might have noticed the result was different this time. Previous we have been passing the param name to the function when invoking it through the command line
--param name World. We can accomplish this same behavior by passing a value as a query param (e.g.?name=World):curl -i https://[region].functions.cloud.ibm.com/api/v1/web/SAMPLE_URL/default/helloJava.json?name=World
So far we have been just return JSON from our function, but functions are more flexible than that! Let's setup a function to return HTML:
-
Change the current directory we are in to the root package of our Java app:
cd src/main/java/com/example -
Create and open a new Java file
WebHello.javawith this command:vi WebHello.java
-
Copy in the body of the Java file:
package com.example; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import java.util.logging.Logger; /** * Hello FunctionApp */ public class WebHello { protected static final Logger logger = Logger.getLogger("basic"); public static JsonObject main(JsonObject args) { JsonObject response = new JsonObject(); JsonPrimitive nameArg = args.getAsJsonPrimitive("name"); String result; if (nameArg == null) { result = "Welcome to OpenWhisk"; } else { result = "Hello " + nameArg.getAsString(); } response.addProperty("body", "<html><body><h3>" + result + "</h3></body></html>"); logger.info("invoked with params:"); return response; } }
-
Save and exit from vi by typing
:wq -
Return to the root of the repo:
cd ../../../../.. -
Rebuild the Java .jar:
./mvnw package
-
Functions can be updated if you want to change their behavior. Run the following command to add the new
webHelloaction:ibmcloud fn action create webHello target/hello-world-java.jar --main com.example.WebHello --web true -
Get the url for the function with the following command like earlier:
ibmcloud fn action get webHello --url
-
Invoke the above URL directly from the your web browser.
-
Like earlier, you can change the
namequery parameter to change the value being returned.
IBM Cloud provides a convenient dashboard for viewing your functions. You can access this dashboard here: https://cloud.ibm.com/functions/actions. It should list the following functions:
These functions have been created via the CLI at the start of this lab. Optionally, you could also define an API that can be further explored in the API section of the dashboard. Check out our deep-dive repo -- listed at the bottom of this lab -- if you want to know more about this topic.
-
The serverless functions
helloJavaandwebHelloare both written in Java. Hence, the code cannot be viewed and changed via the dashboard. They can be invoked though.Invoke the function
helloJavaby clicking the function. Next, click Invoke.As you can see the result is similar to when the function is invoked via the command line.
-
Next, change the Input by clicking Change Input and change the input to:
{ "name": "your name here.." }Change the value of
nameto your own name, or something you like, and click Apply. Click Invoke to invoke this function with the changed input. The result should be:{ "greetings": "Hello your name here..." }Finally, return to the functions dashboard.
Use the command below to obtain a list of the most recent activations of your serverless functions.
ibmcloud fn activation listThe result should look similar to:
2022-12-06 15:18:15 9a072b3e597a48f5872b3e597a08f559 java:8 warm 4ms success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:18:13 cb78e963d78a44d6b8e963d78a64d6e7 java:8 warm 4ms success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:18:12 7c74cdc63a364fcdb4cdc63a364fcda1 java:8 warm 3ms success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:18:02 59f5aed9244b4679b5aed9244b56795e java:8 cold 361ms success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:17:29 9ed06fe9ec50416e906fe9ec50e16e6a java:8 cold 841ms success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:14:45 ebe6953905454b1da695390545bb1d3f java:8 cold 793ms success e7417dfb-0...49c2b39/webHello:0.0.1
2022-12-06 14:59:19 eaf1c26406874a2fb1c26406877a2f02 java:8 warm 3ms success e7417dfb-0...49c2b39/helloJava:0.0.1
2022-12-06 14:59:10 055c80db6be547e19c80db6be5c7e1a3 java:8 cold 681ms success e7417dfb-0...49c2b39/helloJava:0.0.1
2022-12-06 14:58:39 305b334700dc43309b334700dc9330ec nodejs:12 cold 32ms success e7417dfb-0...49c2b39/echo:0.0.515
Check out the cold starts of your Java functions in this list. As you can see, they can take a relatively long time to complete.
The following paragraph is taken from the https://quarkus.io website and explains very well in a nutshell what Quarkus is all about.
What is Quarkus
Traditional Java stacks were engineered for monolithic applications with long startup times and large memory requirements in a world where the cloud, containers, and Kubernetes did not exist. Java frameworks needed to evolve to meet the needs of this new world.
Quarkus was created to enable Java developers to create applications for a modern, cloud-native world. Quarkus is a Kubernetes-native Java framework tailored for GraalVM and HotSpot, crafted from best-of-breed Java libraries and standards. The goal is to make Java the leading platform in Kubernetes and serverless environments while offering developers a framework to address a wider range of distributed application architectures.
So, let's see how we can benefit by using Quarkus for our sample serverless function.
To run a Java function on OpenWhisk that is built with Quarkus, we need to create a so-called custom runtime image. This image needs to implement the Action interface. See Creating and invoking Docker actions for more info on the how to .
-
For this lab, the image has already been prepared for you. So let's create a new function that uses our custom Quarkus runtime image.
ibmcloud fn action create helloQuarkus --docker eciggaar/action-quarkus:v2.14.2 -m 128
Note that the action is created with only 128M as maximum memory limit!!
-
Next, invoke the action a couple of times to generate some activity. You might wanna replace the value of the
nameparameter with your own...ibmcloud fn action invoke helloQuarkus --result --param name Quarkus
-
Now do the same for the regular Java action.
ibmcloud fn action invoke helloJava --result --param name Quarkus
-
Finally, retrieve the list of activations again to see the results.
ibmcloud fn activation list
This should result in output similar to:
2022-12-06 15:20:53 c2889f3878054840889f387805a84080 java:8 warm 7ms success e7417dfb-0...49c2b39/helloJava:0.0.2 2022-12-06 15:20:51 d2e865bc0ff74c9ea865bc0ff72c9e5d java:8 warm 5ms success e7417dfb-0...49c2b39/helloJava:0.0.2 2022-12-06 15:20:44 cc5f892e2bd74f449f892e2bd74f4484 blackbox warm 4ms success e7417dfb-0...49c2b39/helloQuarkus:0.0.1 2022-12-06 15:20:43 a226111e9e104aaea6111e9e10caae2e blackbox warm 2ms success e7417dfb-0...49c2b39/helloQuarkus:0.0.1 2022-12-06 15:20:41 f8d37463225c4f1f937463225c6f1fc3 blackbox cold 55ms success e7417dfb-0...49c2b39/helloQuarkus:0.0.1 2022-12-06 15:20:30 760935f57ac545bf8935f57ac545bfcb blackbox cold 61ms success e7417dfb-0...49c2b39/helloQuarkus:0.0.1 2022-12-06 15:18:15 9a072b3e597a48f5872b3e597a08f559 java:8 warm 4ms success e7417dfb-0...49c2b39/helloJava:0.0.2 2022-12-06 15:18:13 cb78e963d78a44d6b8e963d78a64d6e7 java:8 warm 4ms success e7417dfb-0...49c2b39/helloJava:0.0.2 2022-12-06 15:18:12 7c74cdc63a364fcdb4cdc63a364fcda1 java:8 warm 3ms success e7417dfb-0...49c2b39/helloJava:0.0.2 2022-12-06 15:18:02 59f5aed9244b4679b5aed9244b56795e java:8 cold 361ms success e7417dfb-0...49c2b39/helloJava:0.0.2 2022-12-06 15:17:29 9ed06fe9ec50416e906fe9ec50e16e6a java:8 cold 841ms success e7417dfb-0...49c2b39/helloJava:0.0.2 2022-12-06 15:14:45 ebe6953905454b1da695390545bb1d3f java:8 cold 793ms success e7417dfb-0...49c2b39/webHello:0.0.1 2022-12-06 14:59:19 eaf1c26406874a2fb1c26406877a2f02 java:8 warm 3ms success e7417dfb-0...49c2b39/helloJava:0.0.1 2022-12-06 14:59:10 055c80db6be547e19c80db6be5c7e1a3 java:8 cold 681ms success e7417dfb-0...49c2b39/helloJava:0.0.1 2022-12-06 14:58:39 305b334700dc43309b334700dc9330ec nodejs:12 cold 32ms success e7417dfb-0...49c2b39/echo:0.0.515Take a look at the startup times for the cold starts of both the
helloQuarkusandhelloJavaaction -- you might have to scroll a bit to the right -- and notice the difference...
TIP: If you want to experiment yourself with developing Java functions using Quarkus, then check out this excellent blog on Serverless Java Functions with Quarkus and OpenWhisk by Niklas Heidloff.
CONGRATULATIONS!! π π
You successfully completed the lab!! If you want, you can continue with the optional steps below.
To explore the possibilities when creating cloud functions via the UI, click the Create button. On the next page, you can either create new triggers and/or sequences, but also new functions via quick templates or from scratch. Select the Quickstart Templates to continue and choose Hello World. You should see a screen similar to:
Now select a favourite language using the dropdown (1). We've chosen for NodeJS 10 in the screenshot above. Click Deploy (2) to create the new function written in NodeJS. It outputs practically the same as our helloJava function. When no input is given, clicking Invoke returns:
{
"greeting": "Hello stranger!"
}
When there is input, the result is the same as for the helloJava function. Please see for yourself by invoking the hello-world function with some input as well.
We only scratched the surface of all that is possible with serverless functions. Want to learn how to chain the execution of functions together? Or how to configure a trigger to have a function executed?
Then check out the following repo: https://github.com/eciggaar/go-serverless-with-java



