# Funqy Google Cloud Functions

The guide walks through quickstart code to show you how you can deploy Funqy functions to Google Cloud Functions.

| This technology is considered preview. In preview, backward compatibility and presence in the ecosystem is not guaranteed. Specific improvements might require changing configuration or APIs, and plans to become stable are under way. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker. For a full list of possible statuses, check our FAQ entry.

## Prerequisites

To complete this guide, you need:

- Roughly 30 minutes

- An IDE

- JDK 17+ installed with `JAVA_HOME` configured appropriately

- Apache Maven 3.9.9

- Optionally the Quarkus CLI if you want to use it

- A Google Cloud Account. Free accounts work.

- Cloud SDK CLI Installed

## Login to Google Cloud

Login to Google Cloud is necessary for deploying the application. It can be done as follows:

    gcloud auth login

## The Quickstart

Clone the Git repository: `git clone https://github.com/quarkusio/quarkus-quickstarts.git`, or download an archive.

The solution is located in the `funqy-google-cloud-functions-quickstart` directory.

## Creating the Maven Deployment Project

Create an application with the `quarkus-funqy-google-cloud-functions` extension. You can use the following Maven command to create it:

CLI

    quarkus create app org.acme:funqy-google-cloud-functions \
        --extension='funqy-google-cloud-functions' \
        --no-code
    cd funqy-google-cloud-functions

To create a Gradle project, add the `--gradle` or `--gradle-kotlin-dsl` option.

For more information about how to install and use the Quarkus CLI, see the Quarkus CLI guide.

Maven

    mvn io.quarkus.platform:quarkus-maven-plugin:3.26.0.CR1:create \
        -DprojectGroupId=org.acme \
        -DprojectArtifactId=funqy-google-cloud-functions \
        -Dextensions='funqy-google-cloud-functions' \
        -DnoCode
    cd funqy-google-cloud-functions

To create a Gradle project, add the `-DbuildTool=gradle` or `-DbuildTool=gradle-kotlin-dsl` option.

For Windows users:

- If using cmd, (don’t use backward slash `\` and put everything on the same line)

- If using Powershell, wrap `-D` parameters in double quotes e.g. `"-DprojectArtifactId=funqy-google-cloud-functions"`

## The Code

There is nothing special about the code and more importantly nothing Google Cloud specific. Funqy functions can be deployed to many environments and Google Cloud Functions is one of them.

## Choose Your Function

Only one Funqy function can be exported per Google Cloud Functions deployment. If you only have one method annotated with `@Funq` in your project, then there is no worries. If you have multiple functions defined within your project, then you will need to choose the function within your Quarkus `application.properties`:

    quarkus.funqy.export=greet

Alternatively, you can set the `QUARKUS_FUNQY_EXPORT` environment variable when you create the Google Cloud Function using the `gcloud` cli.

## Build and Deploy

Build the project:

CLI

    quarkus build

Maven

    ./mvnw install

Gradle

    ./gradlew build

This will compile and package your code.

## Creating the functions

In this example, we will create two background functions and a cloud events function. Background functions allow you to react to Google Cloud events like PubSub messages, Cloud Storage events, Firestore events, …​ Cloud events functions allow you to react to supported events using the Cloud Events specification.

    import jakarta.inject.Inject;

    import io.cloudevents.CloudEvent;
    import io.quarkus.funqy.Funq;
    import io.quarkus.funqy.gcp.functions.event.PubsubMessage;
    import io.quarkus.funqy.gcp.functions.event.StorageEvent;

    public class GreetingFunctions {

        @Inject GreetingService service; (1)

        @Funq (2)
        public void helloPubSubWorld(PubsubMessage pubSubEvent) {
            String message = service.hello(pubSubEvent.data);
            System.out.println(pubSubEvent.messageId + " - " + message);
        }

        @Funq (3)
        public void helloGCSWorld(StorageEvent storageEvent) {
            String message = service.hello("world");
            System.out.println(storageEvent.name + " - " + message);
        }

        @Funq (4)
        public void helloCloudEvent(CloudEvent cloudEvent) {
            System.out.println("Receive event Id: " + cloudEvent.getId());
            System.out.println("Receive event Subject: " + cloudEvent.getSubject());
            System.out.println("Receive event Type: " + cloudEvent.getType());
            System.out.println("Receive event Data: " + new String(cloudEvent.getData().toBytes()));
            System.out.println("Be polite, say " + service.hello("world"));
        }
    }

| Function return type can also be Mutiny reactive types.

1. Injection works inside your function.

2. This is a background function that takes as parameter a `io.quarkus.funqy.gcp.functions.event.PubsubMessage`, this is a convenient class to deserialize a PubSub message.

3. This is a background function that takes as parameter a `io.quarkus.funqy.gcp.functions.event.StorageEvent`, this is a convenient class to deserialize a Google Storage event.

4. This is a cloud events function, that takes as parameter a `io.cloudevents.CloudEvent`, inside it the `getData()` method will return the event content, a storage event in this case.

| we provide convenience class to deserialize common Google Cloud events inside the `io.quarkus.funqy.gcp.functions.event` package. They are not mandatory to use, you can use any object you want.

As our project contains multiple functions, we need to specify which function needs to be deployed via the following property inside our `application.properties` :

    quarkus.funqy.export=helloPubSubWorld

## Build and Deploy to Google Cloud

To build your application, you can package your application via `mvn clean package`. You will have a single JAR inside the `target/deployment` repository that contains your classes and all your dependencies in it.

Then you will be able to use `gcloud` to deploy your function to Google Cloud. The `gcloud` command will be different depending on which event triggers your function.

| We will use the Java 21 runtime, but you can switch to the Java 17 runtime by using `--runtime=java17` instead of `--runtime=java21` on the deploy commands.

| The first time you launch the `gcloud functions deploy`, you can have the following error message:
    ERROR: (gcloud.functions.deploy) OperationError: code=7, message=Build Failed: Cloud Build has not been used in project <project_name> before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudbuild.googleapis.com/overview?project=<my-project> then retry.
This means that Cloud Build is not activated yet. To overcome this error, open the URL shown in the error, follow the instructions and then wait a few minutes before retrying the command.

### Background Functions - PubSub

Use this command to deploy to Google Cloud Functions:

    gcloud functions deploy quarkus-example-funky-pubsub \
      --entry-point=io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction \
      --runtime=java21 --trigger-resource hello_topic --trigger-event google.pubsub.topic.publish \
      --source=target/deployment

| The entry point always needs to be `io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction` as it will be this class that will bootstrap Quarkus.

The `--trigger-resource` option defines the name of the PubSub topic, and the `--trigger-event google.pubsub.topic.publish` option define that this function will be triggered by all message publication inside the topic.

To trigger an event to this function, you can use the `gcloud functions call` command:

    gcloud functions call quarkus-example-funky-pubsub --data '{"data":"Pub/Sub"}'

The `--data '{"data":"Hello, Pub/Sub"}'` option allows you to specify the message to be sent to PubSub.

### Background Functions - Cloud Storage

Before deploying your function, you need to create a bucket.

    gsutil mb gs://quarkus-hello

Then, use this command to deploy to Google Cloud Functions:

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

| The entry point always needs to be `io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction` as it will be this class that will bootstrap Quarkus.

The `--trigger-resource` option defines the name of the Cloud Storage bucket, and the `--trigger-event google.storage.object.finalize` option define that this function will be triggered by all new file inside this bucket.

To trigger an event to this function, you can use the `gcloud functions call` command:

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

The `--data '{"name":"test.txt"}'` option allow to specify a fake file name, a fake Cloud Storage event will be created for this name.

You can also simply add a file to Cloud Storage using the command line of the web console.

### Cloud Events Functions - Cloud Storage

Before deploying your function, you need to create a bucket.

    gsutil mb gs://quarkus-hello

Then, use this command to deploy to Google Cloud Functions:

    gcloud functions deploy quarkus-example-cloud-event \
      --entry-point=io.quarkus.funqy.gcp.functions.FunqyCloudEventsFunction \
      --runtime=java21 --trigger-bucket=example-cloud-event --source=target/deployment

| The entry point always needs to be `io.quarkus.funqy.gcp.functions.FunqyCloudEventsFunction` as it will be this class that will bootstrap Quarkus.

The `--trigger-bucket=` option defines the name of the Cloud Storage bucket.

To trigger the event, you can send a file to the GCS `example-cloud-event` bucket.

## Running locally

The easiest way to locally run your function is using the Cloud Function invoker JAR.

You can download it via Maven using the following command:

    mvn dependency:copy \
        -Dartifact='com.google.cloud.functions.invoker:java-function-invoker:1.4.1' \
        -DoutputDirectory=.

Before using the invoker, you first need to build your function via:

CLI

    quarkus build

Maven

    ./mvnw install

Gradle

    ./gradlew build

Then you can use it to launch your function locally, again, the command depends on the type of function and the type of events.

### Background Functions - PubSub

For background functions, you launch the invoker with a target class of `io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction`.

    java -jar java-function-invoker-1.4.1.jar \
      --classpath target/funqy-google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
      --target io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction

| The `--classpath` parameter needs to be set to the previously packaged JAR that contains your function class and all Quarkus related classes.

Then you can call your background function via an HTTP call with a payload containing the event:

    curl localhost:8080 -d '{"data":{"data":"world"}}'

This will call your PubSub background function with a PubSubMessage `{"data":"hello"}`.

### Background Functions - Cloud Storage

For background functions, you launch the invoker with a target class of `io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction`.

    java -jar java-function-invoker-1.4.1.jar \
      --classpath target/funqy-google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
      --target io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction

| The `--classpath` parameter needs to be set to the previously packaged JAR that contains your function class and all Quarkus related classes.

Then you can call your background function via an HTTP call with a payload containing the event:

    curl localhost:8080 -d '{"data":{"name":"text"}}'

This will call your PubSub background function with a Cloud Storage event `{"name":"file.txt"}`, so an event on the `file.txt` file.

### Cloud Events Functions - Cloud Storage

For cloud events functions, you launch the invoker with a target class of `io.quarkus.funqy.gcp.functions.FunqyCloudEventsFunction``.

    java -jar java-function-invoker-1.4.1.jar \
      --classpath target/funqy-google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
      --target io.quarkus.funqy.gcp.functions.FunqyCloudEventsFunction

| The `--classpath` parameter needs to be set to the previously packaged JAR that contains your function class and all Quarkus related classes.

Then you can call your background function via an HTTP call with a payload containing the event:

    curl localhost:8080 \
      -X POST \
      -H "Content-Type: application/json" \
      -H "ce-id: 123451234512345" \
      -H "ce-specversion: 1.0" \
      -H "ce-time: 2020-01-02T12:34:56.789Z" \
      -H "ce-type: google.cloud.storage.object.v1.finalized" \
      -H "ce-source: //storage.googleapis.com/projects/_/buckets/MY-BUCKET-NAME" \
      -H "ce-subject: objects/MY_FILE.txt" \
      -d '{
            "bucket": "MY_BUCKET",
            "contentType": "text/plain",
            "kind": "storage#object",
            "md5Hash": "...",
            "metageneration": "1",
            "name": "MY_FILE.txt",
            "size": "352",
            "storageClass": "MULTI_REGIONAL",
            "timeCreated": "2020-04-23T07:38:57.230Z",
            "timeStorageClassUpdated": "2020-04-23T07:38:57.230Z",
            "updated": "2020-04-23T07:38:57.230Z"
          }'

This will call your cloud events function with an event on the `"MY_FILE.txt` file.

## Testing your function

Quarkus provides built-in support for testing your Funqy Google Cloud functions via the `quarkus-test-google-cloud-functions` dependency.

To use it, you must add the following test dependencies in your `pom.xml`.

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-test-google-cloud-functions</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <scope>test</scope>
    </dependency>

This extension provides a `@WithFunction` annotation that can be used to annotate `@QuarkusTest` test cases to start a Cloud Function invoker before you test cases and stop it at the end. This annotation must be configured with the type of the function you want to launch, and optionally the name of the function in case you have multiple functions inside your application.

The default Quarkus test port configuration (`quarkus.http.test-port`) will be honored and if you set it to 0 a random port will be assigned to the function invoker.

### Background Functions - PubSub

    import static io.restassured.RestAssured.given;

    import org.junit.jupiter.api.Test;

    import io.quarkus.google.cloud.functions.test.FunctionType;
    import io.quarkus.google.cloud.functions.test.WithFunction;
    import io.quarkus.test.junit.QuarkusTest;

    @QuarkusTest (1)
    @WithFunction(FunctionType.FUNQY_BACKGROUND) (2)
    class GreetingFunctionsPubsubTest {
        @Test
        public void test() {
            given()
                    .body("{\"data\":{\"data\":\"world\"}}") (3)
                    .when()
                    .post()
                    .then()
                    .statusCode(200);
        }
    }

1. This is a standard Quarkus test that must be annotated by `@QuarkusTest`.

2. `@WithFunction(FunctionType.FUNQY_BACKGROUND)` indicates to launch the function as a Funqy background function. If multiple functions exist in the same application, the `functionName` attribute must be used to denote which one should be launched.

3. REST-assured is used to test the function, `{"data":"world"}` will be sent to it via the invoker.

### Background Functions - Cloud Storage

    import static io.restassured.RestAssured.given;

    import org.junit.jupiter.api.Test;

    import io.quarkus.google.cloud.functions.test.FunctionType;
    import io.quarkus.google.cloud.functions.test.WithFunction;
    import io.quarkus.test.junit.QuarkusTest;

    @QuarkusTest (1)
    @WithFunction(FunctionType.FUNQY_BACKGROUND) (2)
    class GreetingFunctionsStorageTest {
        @Test
        public void test() {
            given()
                    .body("{\"data\":{\"name\":\"hello.txt\"}}") (2)
                    .when()
                    .post()
                    .then()
                    .statusCode(200);
        }
    }

1. This is a standard Quarkus test that must be annotated by `@QuarkusTest`.

2. `@WithFunction(FunctionType.FUNQY_BACKGROUND)` indicates to launch the function as a Funqy background function. If multiple functions exist in the same application, the `functionName` attribute must be used to denote which one should be launched.

3. REST-assured is used to test the function, `{"name":"hello.txt"}` will be sent to it via the invoker.

### Cloud Events Functions - Cloud Storage

    import static io.restassured.RestAssured.given;

    import org.junit.jupiter.api.Test;

    import io.quarkus.google.cloud.functions.test.FunctionType;
    import io.quarkus.google.cloud.functions.test.WithFunction;
    import io.quarkus.test.junit.QuarkusTest;

    @QuarkusTest (1)
    @WithFunction(FunctionType.FUNQY_CLOUD_EVENTS) (2)
    class GreetingFunctionsCloudEventTest {
        @Test
        public void test() {
            given()
                    .body("{\n" + (3)
                            "        \"bucket\": \"MY_BUCKET\",\n" +
                            "        \"contentType\": \"text/plain\",\n" +
                            "        \"kind\": \"storage#object\",\n" +
                            "        \"md5Hash\": \"...\",\n" +
                            "        \"metageneration\": \"1\",\n" +
                            "        \"name\": \"MY_FILE.txt\",\n" +
                            "        \"size\": \"352\",\n" +
                            "        \"storageClass\": \"MULTI_REGIONAL\",\n" +
                            "        \"timeCreated\": \"2020-04-23T07:38:57.230Z\",\n" +
                            "        \"timeStorageClassUpdated\": \"2020-04-23T07:38:57.230Z\",\n" +
                            "        \"updated\": \"2020-04-23T07:38:57.230Z\"\n" +
                            "      }")
                    .header("ce-id", "123451234512345") (4)
                    .header("ce-specversion", "1.0")
                    .header("ce-time", "2020-01-02T12:34:56.789Z")
                    .header("ce-type", "google.cloud.storage.object.v1.finalized")
                    .header("ce-source", "//storage.googleapis.com/projects/_/buckets/MY-BUCKET-NAME")
                    .header("ce-subject", "objects/MY_FILE.txt")
                    .when()
                    .post()
                    .then()
                    .statusCode(200);
        }
    }

1. This is a standard Quarkus test that must be annotated by `@QuarkusTest`.

2. `@WithFunction(FunctionType.FUNQY_CLOUD_EVENTS)` indicates to launch the function as a Funqy cloud events function. If multiple functions exist in the same application, the `functionName` attribute must be used to denote which one should be launched.

3. REST-assured is used to test the function, this payload that describe a storage event will be sent to it via the invoker.

4. The cloud events headers must be sent via HTTP headers.

## What’s next?

If you are looking for Jakarta REST, Servlet or Vert.x support for Google Cloud Functions, we have it thanks to our Google Cloud Functions HTTP binding.