编辑此页面

Google Cloud Functions(无服务器)

quarkus-google-cloud-functions 扩展允许您使用 Quarkus 构建 Google Cloud Functions。您的函数可以使用来自 CDI 或 Spring 的注入注解和其他 Quarkus 功能,根据您的需要。

此技术被认为是预览版。

预览版中,不保证向后兼容性和在生态系统中的存在。具体的改进可能需要更改配置或 API,成为稳定版的计划正在进行中。欢迎在我们的邮件列表或我们的 GitHub 问题跟踪器中提供反馈。

有关可能的完整状态列表,请查看我们的常见问题解答条目

先决条件

要完成本指南,您需要

解决方案

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

如果您不想遵循所有这些步骤,可以直接转到已完成的示例。

克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载 压缩包

解决方案位于 google-cloud-functions-quickstart 目录中。

创建 Maven 部署项目

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

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

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

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

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数包裹在双引号中,例如 "-DprojectArtifactId=google-cloud-functions"

登录 Google Cloud

登录 Google Cloud 对于部署应用程序是必要的。 可以按如下方式完成

gcloud auth login

创建函数

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

选择您的函数

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 {
}

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,这是可选的。
2 该函数必须是一个 CDI Bean
3 这是一个常规的 Google Cloud Function 实现,因此它需要实现 com.google.cloud.functions.HttpFunction
4 注入在您的函数内部有效。
5 这是标准的 Google Cloud Function 实现,没有什么特别之处。

BackgroundFunction

BackgroundFunction 由 Storage 事件触发,您可以改用 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,这是可选的。
2 该函数必须是一个 CDI Bean
3 这是一个常规的 Google Cloud Function 实现,因此它需要实现 com.google.cloud.functions.BackgroundFunction
4 注入在您的函数内部有效。
5 这是标准的 Google Cloud Function 实现,没有什么特别之处。
6 这是事件将被反序列化为的类。

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,这是可选的。
2 该函数必须是一个 CDI Bean
3 这是一个常规的 Google Cloud Function 实现,因此它需要实现 com.google.cloud.functions.RawBackgroundFunction
4 注入在您的函数内部有效。
5 这是标准的 Google Cloud Function 实现,没有什么特别之处。

CloudEventsFunction

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,这是可选的。
2 该函数必须是一个 CDI Bean
3 这是一个常规的 Google Cloud Function 实现,因此它需要实现 com.google.cloud.functions.CloudEventsFunction
4 注入在您的函数内部有效。
5 这是标准的 Google Cloud Function 实现,没有什么特别之处,只是它接收一个 io.cloudevents.CloudEvent
6 这是 Cloud Events 内部的存储事件。

构建和部署到 Google Cloud

要构建您的应用程序,您可以使用标准命令打包它

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

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

然后,您将能够使用 gcloud 将您的函数部署到 Google Cloud。gcloud 命令将因触发您函数的事件而异。

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

第一次启动此命令时,您可能会收到以下错误消息

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 尚未激活。 要克服此错误,请打开错误中显示的 URL,按照说明进行操作,然后等待几分钟再重试该命令。

HttpFunction

这是一个将您的 HttpFunction 部署到 Google Cloud 的示例命令

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

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

此命令将为您提供一个指向您函数的 httpsTrigger.url 作为输出。

BackgroundFunction

在部署您的函数之前,您需要创建一个存储桶。

gsutil mb gs://quarkus-hello

这是一个将您的 BackgroundFunction 部署到 Google Cloud 的示例命令,由于该函数由 Storage 事件触发,因此需要使用 --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=java21 --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 文档,其中包含添加到存储桶的文件的名称。

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

CloudEventsFunction

这是一个将您的 CloudEventsFunction 部署到 Google Cloud 的示例命令,由于该函数由 Storage 事件触发,因此需要使用带有先前创建的存储桶名称的 --trigger-bucket 参数

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

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

要触发该事件,您可以将文件发送到 GCS example-cloud-event 存储桶。

本地运行

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

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

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

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

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

HttpFunction

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

java -jar java-function-invoker-1.4.1.jar \
  --classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
  --target io.quarkus.gcp.functions.QuarkusHttpFunction
--classpath 参数需要设置为先前打包的 JAR,其中包含您的函数类和所有 Quarkus 相关类。

您的端点将在 https://:8080 上可用。

BackgroundFunction

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

java -jar java-function-invoker-1.4.1.jar \
  --classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
  --target io.quarkus.gcp.functions.QuarkusBackgroundFunction
--classpath 参数需要设置为先前打包的 JAR,其中包含您的函数类和所有 Quarkus 相关类。

然后,您可以通过包含事件负载的 HTTP 调用来调用您的后台函数

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

这将使用事件 {"name":"hello.txt"} 调用您的 Storage 后台函数,因此是 hello.txt 文件上的事件。

RawBackgroundFunction

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

java -jar java-function-invoker-1.4.1.jar \
  --classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
  --target io.quarkus.gcp.functions.QuarkusBackgroundFunction
--classpath 参数需要设置为先前打包的 JAR,其中包含您的函数类和所有 Quarkus 相关类。

然后,您可以通过包含事件负载的 HTTP 调用来调用您的后台函数

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

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

CloudEventsFunction

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

java -jar java-function-invoker-1.4.1.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 文件上的云事件函数。

测试您的函数

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>
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

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

默认的 Quarkus 测试端口配置 (quarkus.http.test-port) 将被遵循,如果您将其设置为 0,则将为函数 invoker 分配一个随机端口。

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. 这是一个标准的 Quarkus 测试,必须由 @QuarkusTest 注解。

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

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

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. 这是一个标准的 Quarkus 测试,必须由 @QuarkusTest 注解。

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

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

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. 这是一个标准的 Quarkus 测试,必须由 @QuarkusTest 注解。

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

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

CloudEventsFunction

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. 这是一个标准的 Quarkus 测试,必须由 @QuarkusTest 注解。

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

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

  4. 云事件标头必须通过 HTTP 标头发送。

下一步是什么?

如果您正在寻找 Google Cloud Functions 的 Jakarta REST、Servlet 或 Vert.x 支持,我们通过我们的 Google Cloud Functions HTTP 绑定提供了它。

如果您正在寻找与提供程序无关的 Google Cloud Functions 实现,我们通过我们的 Funqy Google Cloud Functions 扩展提供了它。

相关内容