Google Cloud Functions (Serverless)

quarkus-google-cloud-functions 扩展允许您使用 Quarkus 构建 Google Cloud Functions。您的功能可以使用来自 CDI 或 Spring 以及其他您需要的 Quarkus 工具的注入批注。 Unresolved directive in gcp-functions.adoc - include::{includes}/extension-status.adoc[]

Prerequisites

Unresolved directive in gcp-functions.adoc - include::{includes}/prerequisites.adoc[]* A Google Cloud Account免费帐号有用。* Cloud SDK CLI Installed

Solution

本指南将引导您生成一个示例项目,然后创建多个功能,展示如何在 Quarkus 中实现 HttpFunctionBackgroundFunctionRawBackgroundFunction。构建完成后,您将能够将项目部署到 Google Cloud。

如果您不想执行上述所有步骤,您可以直接转到完成的示例。

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

解决方案位于 google-cloud-functions-quickstart directory

Creating the Maven Deployment Project

使用 quarkus-google-cloud-functions 扩展创建一个应用程序。您可以使用以下 Maven 命令来创建它:

Unresolved directive in gcp-functions.adoc - include::{includes}/devtools/create-app.adoc[]

Login to Google Cloud

部署应用程序需要登录 Google Cloud。可以按以下方式进行操作:

gcloud auth login

Creating the functions

对于此示例项目,我们将创建四个函数,一个 HttpFunction、一个 BackgroundFunction(Storage 事件)、一个 RawBackgroundFunction(PubSub 事件)和一个 CloudEventsFunction(使用 Cloud Events 规范的 Storage 事件)。

Quarkus 支持 Cloud Functions gen 1 和 gen 2。有关 Cloud Functions gen 2 的概述,请参阅 Google Cloud Functions 文档上的 this page。要使用 gen 2,您必须添加 --gen2 参数。

Choose Your Function

quarkus-google-cloud-functions 扩展将在您的项目中扫描直接实现 Google Cloud HttpFunctionBackgroundFunctionRawBackgroundFunctionCloudEventsFunction 接口的类。它必须在您的项目中找到实现这些接口之一的类,否则将会抛出构建时故障。如果它找到多个函数类,也将抛出构建时异常。

不过,有时您可能有一些共享代码的相关函数,而创建多个 Maven 模块只是您不想进行的额外开销。此扩展允许您在一个项目中捆绑多个函数,并使用配置或环境变量来选择您要部署的函数。

要配置函数的名称,可以使用以下配置属性:

quarkus.google-cloud-functions.function=test

quarkus.google-cloud-functions.function 属性告诉 Quarkus 要部署哪个函数。它还可以被环境变量覆盖。

函数类的 CDI 名称必须与 quarkus.google-cloud-functions.function 属性中指定的值匹配。必须使用 @Named 注释完成此操作。

@Named("test")
public class TestHttpFunction implements HttpFunction {
}

The HttpFunction

import java.io.Writer;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import io.quarkus.gcp.function.test.service.GreetingService;

@Named("httpFunction") (1)
@ApplicationScoped (2)
public class HttpFunctionTest implements HttpFunction { (3)
    @Inject GreetingService greetingService; (4)

    @Override
    public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception { (5)
        Writer writer = httpResponse.getWriter();
        writer.write(greetingService.hello());
    }
}
<1>  `@Named` 注释允许为 `quarkus.google-cloud-functions.function` 属性使用的 CDI Bean 命名,这是可选的。
<1>  函数必须是 CDI Bean
<1>  这是一个常规 Google Cloud Function 实现,所以它需要实现 `com.google.cloud.functions.HttpFunction`。
<1>  注入在您的函数内部运作。
<1>  这是标准的 Google Cloud Function 实现,这里没什么特别的。

The BackgroundFunction

这个 BackgroundFunction 由存储事件触发,您可以改为使用 Google Cloud 支持的任何事件。

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import io.quarkus.gcp.function.test.service.GreetingService;


@Named("storageTest") (1)
@ApplicationScoped (2)
public class BackgroundFunctionStorageTest implements BackgroundFunction<BackgroundFunctionStorageTest.StorageEvent> { (3)
    @Inject GreetingService greetingService;  (4)

    @Override
    public void accept(StorageEvent event, Context context) throws Exception { (5)
        System.out.println("Receive event: " + event);
        System.out.println("Be polite, say " + greetingService.hello());
    }

    //
    public static class StorageEvent { (6)
        public String name;
    }
}
<1>  `@Named` 注释允许为 `quarkus.google-cloud-functions.function` 属性使用的 CDI Bean 命名,这是可选的。
<1>  函数必须是 CDI Bean
<1>  这是一个常规 Google Cloud Function 实现,所以它需要实现 `com.google.cloud.functions.BackgroundFunction`。
<1>  注入在您的函数内部运作。
<1>  这是标准的 Google Cloud Function 实现,这里没什么特别的。
<1>  这是事件将被反序列化的类。

The RawBackgroundFunction

这个 RawBackgroundFunction 由 PubSub 事件触发,您可以改为使用 Google Cloud 支持的任何事件。

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import io.quarkus.gcp.function.test.service.GreetingService;

@Named("rawPubSubTest") (1)
@ApplicationScoped (2)
public class RawBackgroundFunctionPubSubTest implements RawBackgroundFunction { (3)
    @Inject GreetingService greetingService; (4)

    @Override
    public void accept(String event, Context context) throws Exception { (5)
        System.out.println("PubSub event: " + event);
        System.out.println("Be polite, say " + greetingService.hello());
    }
}
<1>  `@Named` 注释允许为 `quarkus.google-cloud-functions.function` 属性使用的 CDI Bean 命名,这是可选的。
<1>  函数必须是 CDI Bean
<1>  这是一个常规 Google Cloud Function 实现,所以它需要实现 `com.google.cloud.functions.RawBackgroundFunction`。
<1>  注入在您的函数内部运作。
<1>  这是标准的 Google Cloud Function 实现,这里没什么特别的。

The CloudEventsFunction

CloudEventsFunction 仅是 Cloud Functions Gen 2 的功能。

这个 CloudEventsFunction 由 Cloud Events Storage 事件触发,您可以改为使用 Google Cloud 支持的任何 Cloud Events。

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;

import com.google.cloud.functions.CloudEventsFunction;

import io.cloudevents.CloudEvent;
import io.quarkus.gcp.function.test.service.GreetingService;

@Named("cloudEventTest") (1)
@ApplicationScoped (2)
public class CloudEventStorageTest implements CloudEventsFunction { (3)
    @Inject
    GreetingService greetingService;  (4)

    @Override
    public void accept(CloudEvent cloudEvent) throws Exception { (5)
        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())); (6)
        System.out.println("Be polite, say " + greetingService.hello());
    }
}
<1>  `@Named` 注释允许为 `quarkus.google-cloud-functions.function` 属性使用的 CDI Bean 命名,这是可选的。
<1>  函数必须是 CDI Bean
<1>  这是一个常规 Google Cloud Function 实现,所以它需要实现 `com.google.cloud.functions.CloudEventsFunction`。
<1>  注入在您的函数内部运作。
<1>  这是标准的 Google Cloud Function 实现,这里没什么特别的,除了它收到一个 `io.cloudevents.CloudEvent`。
<1>  这是 Cloud Events 中的存储事件。

Build and Deploy to Google Cloud

若要构建您的应用程序,您可以使用标准命令对其进行打包:

Unresolved directive in gcp-functions.adoc - include::{includes}/devtools/build.adoc[]

前一个命令的结果是 target/deployment 存储库中的单个 JAR 文件,其中包含项目类和依赖性。

然后,您将可以使用 gcloud 将您的函数部署到 Google Cloud。gcloud 命令将根据触发函数的事件而有所不同。

我们将使用 Java 17 运行时,但你可以通过在部署命令中使用 --runtime=java21`来切换到 Java 21 运行时,而不是使用 `--runtime=java17

当你第一次启动此命令时,会出现以下错误信息:

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.

这意味着 Cloud Build 尚未激活。要解决此错误,请打开错误中显示的网址,按照说明操作,然后等待几分钟再重试该命令。

The HttpFunction

以下是一个将 `HttpFunction`部署到 Google Cloud 的示例命令:

gcloud functions deploy quarkus-example-http \
  --entry-point=io.quarkus.gcp.functions.QuarkusHttpFunction \
  --runtime=java17 --trigger-http --allow-unauthenticated --source=target/deployment

入口点必须始终设置为 io.quarkus.gcp.functions.QuarkusHttpFunction,因为它是将 Cloud Functions 与 Quarkus 集成的类。

此命令将生成一个 httpsTrigger.url,指向你的函数。

The BackgroundFunction

在部署函数之前,你需要创建一个存储存储区。

gsutil mb gs://quarkus-hello

以下是一个将 `BackgroundFunction`部署到 Google Cloud 的示例命令,因为该函数是由存储事件触发的,因此需要使用 `--trigger-event google.storage.object.finalize`和 `--trigger-resource`参数,其中包含之前创建的存储存储区的名称:

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

入口点必须始终设置为 io.quarkus.gcp.functions.QuarkusBackgroundFunction,因为它是将 Cloud Functions 与 Quarkus 集成的类。

要触发事件,你可以向 GCS `quarkus-hello`存储存储区发送一个文件,或者你可以使用 gcloud 模拟一个:

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

`--data`包含 GCS 事件,它是一个 JSON 文档,其中包含添加到存储存储区的的文件的名称。

The RawBackgroundFunction

以下是一个将 `RawBackgroundFunction`部署到 Google Cloud 的示例命令,因为该函数是由 PubSub 事件触发的,因此需要使用 `--trigger-event google.pubsub.topic.publish`和 `--trigger-resource`参数,其中包含之前创建的主题的名称:

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

入口点必须始终设置为 io.quarkus.gcp.functions.QuarkusBackgroundFunction,因为它是将 Cloud Functions 与 Quarkus 集成的类。

要触发事件,你可以向 `hello_topic`主题发送一个文件,或者你可以使用 gcloud 模拟一个:

gcloud functions call quarkus-example-pubsub --data '{"data":{"greeting":"world"}}'

The CloudEventsFunction

CloudEventsFunction 仅是 Cloud Functions Gen 2 的功能。

以下是一个将 `CloudEventsFunction`部署到 Google Cloud 的示例命令,因为该函数是由存储事件触发的,因此需要使用 `--trigger-bucket`参数,其中包含之前创建的存储存储区的名称:

gcloud functions deploy quarkus-example-cloud-event --gen2 \
  --entry-point=io.quarkus.gcp.functions.QuarkusCloudEventsFunction \
  --runtime=java17 --trigger-bucket=example-cloud-event --source=target/deployment

入口点必须始终设置为 io.quarkus.gcp.functions.QuarkusCloudEventsFunction,因为它是将 Cloud Functions 与 Quarkus 集成的类。

要触发事件,你可以向 GCS `example-cloud-event`存储存储区发送一个文件。

Running locally

本地运行函数的最简单方法是使用 Cloud Function Invoker JAR。

你可以使用以下命令通过 Maven 下载它:

mvn dependency:copy \
  -Dartifact='com.google.cloud.functions.invoker:java-function-invoker:{gcf-invoker-version}' \
  -DoutputDirectory=.

使用 invoker 之前,您首先需要通过以下方式构建您的函数:

Unresolved directive in gcp-functions.adoc - include::{includes}/devtools/build.adoc[]

The HttpFunction

对于 HttpFunction,您可以使用此命令在本地启动您的函数。

java -jar java-function-invoker-{gcf-invoker-version}.jar \
  --classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
  --target io.quarkus.gcp.functions.QuarkusHttpFunction

`--classpath`参数需要设置为先前打包的 JAR,其中包含您的函数类和所有 Quarkus 相关类。

您的端点将在 [role="bare"][role="bare"]http://localhost:8080上提供。

The BackgroundFunction

对于后台函数,您可以使用目标类 `io.quarkus.gcp.functions.BackgroundFunction`启动 invoker。

java -jar java-function-invoker-{gcf-invoker-version}.jar \
  --classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
  --target io.quarkus.gcp.functions.QuarkusBackgroundFunction

`--classpath`参数需要设置为先前打包的 JAR,其中包含您的函数类和所有 Quarkus 相关类。

然后,您可以通过 HTTP 调用(其包含事件的有效负载)来调用您的 background 函数:

curl localhost:8080 -d '{"data":{"name":"hello.txt"}}'

这将使用事件 {"name":"hello.txt"}(即 `hello.txt`文件上的事件)调用您的 Storage background 函数。

The RawBackgroundFunction

对于后台函数,您可以使用目标类 `io.quarkus.gcp.functions.BackgroundFunction`启动 invoker。

java -jar java-function-invoker-{gcf-invoker-version}.jar \
  --classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
  --target io.quarkus.gcp.functions.QuarkusBackgroundFunction

`--classpath`参数需要设置为先前打包的 JAR,其中包含您的函数类和所有 Quarkus 相关类。

然后,您可以通过 HTTP 调用(其包含事件的有效负载)来调用您的 background 函数:

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

这将使用 PubSubMessage `{"greeting":"world"}`调用您的 PubSub background 函数。

The CloudEventsFunction

`CloudEventsFunction`是仅适用于 Cloud Function 第 2 代的一项功能。

对于云事件函数,您可以使用目标类 `io.quarkus.gcp.functions.QuarkusCloudEventsFunction`启动 invoker。

java -jar java-function-invoker-{gcf-invoker-version}.jar \
  --classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
  --target io.quarkus.gcp.functions.QuarkusCloudEventsFunction

`--classpath`参数需要设置为先前打包的 JAR,其中包含您的函数类和所有 Quarkus 相关类。

然后,您可以通过 HTTP 调用(其包含事件的有效负载)来调用您的云事件函数:

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"
      }'

这将使用 `"MY_FILE.txt`文件上的事件调用您的云事件函数。

Testing your function

Quarkus 提供了内置支持,您可以通过 `quarkus-test-google-cloud-functions`依赖项来测试您的 Google Cloud 函数。

要使用它,您必须在 `pom.xml`中添加以下测试依赖项。

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

此扩展提供了 `@WithFunction`注释,该注释可用于注释 `@QuarkusTest`测试用例,以便在测试用例之前启动 Cloud Function invoker 并在此类结束之后停止它。必须使用您想要启动的函数的类型来配置此注释,还可以选择函数的名称(如果您在应用程序中有多个函数)。

将尊重默认 Quarkus 测试端口配置 (quarkus.http.test-port),如果您将它设置为 0,则会将一个随机端口分配给函数 invoker。

The HttpFunction

import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.is;

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.HTTP) (2)
class HttpFunctionTestCase {
    @Test
    public void test() {
        when()
                .get()
                .then()
                .statusCode(200)
                .body(is("Hello World!")); (3)
    }
}
  1. 这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。

  2. `@WithFunction(FunctionType.HTTP)`指示以 HTTP 函数的形式启动该函数。如果同一个应用程序中存在多个函数,则必须使用 `functionName`属性来表示应启动哪个函数。

  3. REST-assured 用于测试该函数,`Hello World!`将通过 invoker 发送给它。

The BackgroundFunction

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.BACKGROUND) (2)
class BackgroundFunctionStorageTestCase {
    @Test
    public void test() {
        given()
                .body("{\"data\":{\"name\":\"hello.txt\"}}") (3)
                .when()
                .post()
                .then()
                .statusCode(200);
    }
}
  1. 这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。

  2. `@WithFunction(FunctionType.BACKGROUND)`指示将函数作为后台函数启动。如果同一应用程序中存在多个函数,则必须使用`functionName`属性来表示应启动哪个函数。

  3. REST-assured 用于测试函数,`{"name":"hello.txt"}`将通过调用器发送到该函数。

The RawBackgroundFunction

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.RAW_BACKGROUND) (2)
class RawBackgroundFunctionPubSubTestCase {
    @Test
    public void test() {
        given()
                .body("{\"data\":{\"name\":\"hello.txt\"}}") (3)
                .when()
                .post()
                .then()
                .statusCode(200);
    }
}
  1. 这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。

  2. `@WithFunction(FunctionType.RAW_BACKGROUND)`指示将函数作为原始后台函数启动。如果同一应用程序中存在多个函数,则必须使用`functionName`属性来表示应启动哪个函数。

  3. REST-assured 用于测试函数,`{"name":"hello.txt"}`将通过调用器发送到该函数。

The CloudEventsFunction

Cloud Events 函数仅是 Cloud Functions gen 2 的一项功能。

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.CLOUD_EVENTS) (2)
class CloudEventStorageTestCase {
    @Test
    public void test() {
        // test the function using RestAssured
        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-specversion", "1.0") (4)
                .header("ce-id", "1234567890")
                .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. 这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。

  2. `@WithFunction(FunctionType.CLOUD_EVENTS)`指示将函数作为 Cloud Events 函数启动。如果同一应用程序中存在多个函数,则必须使用`functionName`属性来表示应启动哪个函数。

  3. REST-assured 用于测试函数,通过调用器将描述存储事件的此有效负载发送到该函数。

  4. Cloud Events 标头必须通过 HTTP 标头发送。

What’s next?

如果您正在为 Google Cloud Functions 寻找 Jakarta REST、Servlet 或 Vert.x 支持,我们已经使用Google Cloud Functions HTTP binding实现了该支持。

如果您正在为 Google Cloud 函数寻找供应商无关的实现,我们已使用Funqy Google Cloud Functions extension实现了该实现。