Quarkus and the Google Cloud Functions

Quarkus and the Google Cloud Functions

Quarkus is a microservice framework designed for the cloud and the containers.

It is designed to have a reduced memory usage and the shortest possible startup time.

It is mainly based on standards (Jakarta EE, Eclipse MicroProfile, …) and allows the use of mature and widespread Java libraries via its extensions (Hibernate, RESTeasy, Vert.X, Kafka, …).

Quarkus has been thought for the cloud since its inception, it allows the development of Cloud Ready applications (as defined by the principle of 12 Factors applications) and Cloud Native (use of public cloud capabilities to develop your applications).

Quarkus allows among other things the creation of Google Cloud Functions for the development of your applications.

Quarkus allows you to implement a Google Cloud Function of type background or HTTP in several ways:

  • Via Google Cloud APIs.
  • Via Funqy: a Cloud provider agnostic function development framework.
  • Via one of Quarkus HTTP extensions: RESTEasy (JAX-RS), Vert.x reactive routes, Undertow (Servlet), Spring Web.

Background Function with Google Cloud API

First , create a Quarkus project with the Quarkus Maven plugin:

mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create
    -DprojectGroupId=org.acme
    -DprojectArtifactId=google-cloud-functions \
    -Dextensions="resteasy,google-cloud-functions"

This Maven command will create a project for you, containing a background function and an HTTP function.

The background function created is as follows:

@ApplicationScoped //1
public class HelloWorldBackgroundFunction 
        implements BackgroundFunction<HelloWorldBackgroundFunction.StorageEvent> { //2

    @Override
    public void accept(StorageEvent event, Context context) throws Exception { //3
        System.out.println("Receive event on file: " + event.name);
    }

    public static class StorageEvent {
        public String name;
    }
}
  1. In order for Quarkus to detect your function, it must be annotated with a CDI annotation.
  2. The function uses the Google API, here it extends BackgroundFunction.
  3. The function will be triggered by an event represented by the StorageEvent object, for simplicity I only defined its name property.

This function will be triggered by a Google Cloud Storage event, and will display in its logs the name of the object created or updated in Cloud Storage.

As the example comes with two functions, you need to delete the HTTP function that was also created, or select the function to deploy. These steps are explained in the Google Cloud Functions guide here, and omitted for simplicity.

To package the function, you need to use the following Maven command mvn clean package. This command will create an uberjar in the target/deployment directory that you can then deploy as a Cloud Functions.

To deploy this uberjar, you can use the following gcloud command:

gcloud beta functions deploy quarkus-example-storage
--entry-point=io.quarkus.gcp.functions.QuarkusBackgroundFunction
--trigger-resource quarkus-hello
--trigger-event google.storage.object.finalize
--runtime=java11 --source=target/deployment

This command uses as entry point io.quarkus.gcp.functions.QuarkusBackgroundFunction and not your function. This entry point will start Quarkus, then locate your function inside the CDI container.

To trigger the function via a Cloud Storage event, we need to define the trigger event google.storage.object.finalize and specify a bucket name, here quarkus-hello. This bucket must have been previously created via gsutil mb gs://quarkus-hello or the Google Cloud Console.

Adding an object to the quarkus-hello bucket will trigger your function.
You can test this with the gsutil command which allows you to upload files to a Cloud Storage bucket:

gsutil cp test.txt gs://quarkus-hello

Or simulate a trigger via:

gcloud functions call quarkus-example-storage
--data '{"name": "test.txt"}'.

Note that we use the default function size, which is 256MB. This is enough for the function container (Jetty) and our Quarkus application. We take advantage of the low memory consumption of Quarkus.

If we look at the logs of the start of our function, we can see that our function takes about 2s to start, of which 1s for Quarkus, and the rest (1s) for the function container (Jetty). We take advantage of the fast startup of Quarkus.

HTTP Function with Google Cloud API

In the previously created Quarkus project, an HTTP function was generated which has the following code:

@ApplicationScoped //1
public class HelloWorldHttpFunction implements HttpFunction { //2

    @Override
    public void service(HttpRequest httpRequest, HttpResponse httpResponse) //3
            throws Exception {
        Writer writer = httpResponse.getWriter();
        writer.write("Hello World");
    }
}
  1. In order for Quarkus to detect your function, it must be annotated with a CDI annotation.
  2. The function uses the Google API, here it extends HttpFunction.
  3. To implement our function, we have access to the HTTP request and response.

This function does a simple Hello World.

To package the function, you need to use the following Maven command:mvn clean package. This command will create an uberjar in the target/deployment directory that you can then deploy as a Cloud Functions.

To deploy this uberjar you can use the following gcloud command:

gcloud beta functions deploy quarkus-example-http
--entry-point=io.quarkus.gcp.functions.QuarkusHttpFunction
--runtime=java11 --trigger-http --source=target/deployment

This command uses as entry point io.quarkus.gcp.functions.QuarkusHttpFunction and not your function. This entry point will start Quarkus and then locate your function in the CDI container.

For more information on background and HTTP functions via the Google API, see the following guide: Quarkus Google Cloud Functions.

Background Function with Funqy

The background function example we saw earlier has the disadvantage of being dependent on the Google Cloud Functions API. It is therefore not portable from one cloud to another.

This is where Funqy, the cloud provider agnostic Quarkus function API, comes in. We can rewrite the previous background function via Funqy as follows:

public class HelloWorldBackgroundFunction { //1

    @Funq //2
    public void accept(StorageEvent event) throws Exception { //3
        System.out.println("Receive event on file: " + event.name);
    }

    public static class StorageEvent {
        public String name;
    }
}
  1. Your function is no longer dependent on the Google API.
  2. The @Funq annotation allows you to tell Funqy that this is the method to call when the function is triggered.
  3. The function will be triggered by an event represented by the StorageEvent object, for simplicity I only defined its name property.

To use Funqy, you need to replace the extensionquarkus-google-cloud-functions with the extension quarkus-funqy-google-cloud-functions.

After packaging your application with Maven, you can deploy it with gcloud in the same way as before, except you need to use the entry point io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction. This gives:

gcloud beta functions deploy quarkus-example-storage
--entry-point=io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction
--trigger-resource quarkus-hello
--trigger-event google.storage.object.finalize
--runtime=java11 --source=target/deployment

For more information on Funqy, see the following guide: Quarkus Funqy Google Cloud Functions.

HTTP Function with RESTEasy

The HTTP function example we saw earlier has the disadvantage of being dependent on the Google Cloud Functions API. It is therefore not portable from one cloud to another.

Moreover, the direct manipulation of the request and response objects can be tedious.

To overcome this, Quarkus allows you to use its various HTTP extensions instead of the Google API, your function then becomes agnostic of the cloud provider, and much simpler to write.

The previous example can be rewritten as follows using the RESTEasy extension:

@Path("/") //1
public class HelloWorldHttpFunction{ //2

    @GET //3
    @Produces(MediaType.TEXT_PLAIN)
    public void String() throws Exception {
        return "Hello World";
    }
}
  1. Here we use the JAX-RS @Path annotation to define the class as a REST resource.
  2. Our class no longer depends on the Google Functions API.
  3. Our function is implemented via a classic REST operation (we can even define more than one if needed).

To use RESTEasy, you need to replace the quarkus-google-cloud-functions extension with the quarkus-google-cloud-functions-http extension and add the RESTEasy extension: quarkus-resteasy-jackson.

After packaging your application with Maven, you can deploy it with gcloud in the same way as before except that you need to use the entry point io.quarkus.gcp.functions.http.QuarkusHttpFunction. This gives:

gcloud beta functions deploy quarkus-example-http
--entry-point=io.quarkus.gcp.functions.http.QuarkusHttpFunction
--runtime=java11 --trigger-http --source=target/deployment

It is also possible to write HTTP functions with the other HTTP frameworks of Quarkus: reactive routes, Undertow (servlet), Funqy HTTP or Spring Web.

For more information on using HTTP extensions via Cloud Functions, see the following guide: Quarkus Google Cloud Function HTTP bindings.

Conclusion

It is very easy to create deployable functions for Google Cloud with dedicated Quarkus extensions. Moreover Funqy (for brackground functions) and HTTP binding (for HTTP functions) allow you to do it without your code being coupled with the Google Cloud API.

Google Cloud functions are ideal for using Google Cloud services, an extension pack exists to facilitate their uses within Quarkus via the extension pack Google Cloud Services that allows to use BigQuery, BigTable, Firestore, PubSub, SecretManager, Spanner and Storage in a Quarkus application.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.