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 中实现 HttpFunction
、BackgroundFunction
和 RawBackgroundFunction
。构建完成后,您将能够将项目部署到 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[]
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,您必须添加 |
Choose Your Function
quarkus-google-cloud-functions
扩展将在您的项目中扫描直接实现 Google Cloud HttpFunction
、BackgroundFunction
、RawBackgroundFunction
或 CloudEventsFunction
接口的类。它必须在您的项目中找到实现这些接口之一的类,否则将会抛出构建时故障。如果它找到多个函数类,也将抛出构建时异常。
不过,有时您可能有一些共享代码的相关函数,而创建多个 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 运行时,但你可以通过在部署命令中使用 |
当你第一次启动此命令时,会出现以下错误信息:
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)
}
}
-
这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。
-
`@WithFunction(FunctionType.HTTP)`指示以 HTTP 函数的形式启动该函数。如果同一个应用程序中存在多个函数,则必须使用 `functionName`属性来表示应启动哪个函数。
-
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);
}
}
-
这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。
-
`@WithFunction(FunctionType.BACKGROUND)`指示将函数作为后台函数启动。如果同一应用程序中存在多个函数,则必须使用`functionName`属性来表示应启动哪个函数。
-
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);
}
}
-
这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。
-
`@WithFunction(FunctionType.RAW_BACKGROUND)`指示将函数作为原始后台函数启动。如果同一应用程序中存在多个函数,则必须使用`functionName`属性来表示应启动哪个函数。
-
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);
}
}
-
这是必须由 `@QuarkusTest`注释的标准 Quarkus 测试。
-
`@WithFunction(FunctionType.CLOUD_EVENTS)`指示将函数作为 Cloud Events 函数启动。如果同一应用程序中存在多个函数,则必须使用`functionName`属性来表示应启动哪个函数。
-
REST-assured 用于测试函数,通过调用器将描述存储事件的此有效负载发送到该函数。
-
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实现了该实现。