Google Cloud Functions(无服务器)
quarkus-google-cloud-functions
扩展允许您使用 Quarkus 构建 Google Cloud Functions。您的函数可以使用来自 CDI 或 Spring 的注入注解和其他 Quarkus 功能,根据您的需要。
此技术被认为是预览版。 在预览版中,不保证向后兼容性和在生态系统中的存在。具体的改进可能需要更改配置或 API,成为稳定版的计划正在进行中。欢迎在我们的邮件列表或我们的 GitHub 问题跟踪器中提供反馈。 有关可能的完整状态列表,请查看我们的常见问题解答条目。 |
先决条件
要完成本指南,您需要
-
大约 15 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
如果您想使用它,可以选择 Quarkus CLI
-
Google Cloud 帐户。 免费帐户也可以。
解决方案
本指南将引导您完成生成示例项目,然后创建多个函数,展示如何在 Quarkus 中实现 HttpFunction
、BackgroundFunction
和 RawBackgroundFunction
。构建完成后,您就可以将项目部署到 Google Cloud。
如果您不想遵循所有这些步骤,可以直接转到已完成的示例。
克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载 压缩包。
解决方案位于 google-cloud-functions-quickstart
目录中。
创建 Maven 部署项目
使用 quarkus-google-cloud-functions
扩展创建一个应用程序。您可以使用以下 Maven 命令创建它
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数包裹在双引号中,例如"-DprojectArtifactId=google-cloud-functions"
创建函数
对于此示例项目,我们将创建四个函数:一个 HttpFunction
、一个 BackgroundFunction
(Storage 事件)、一个 RawBackgroundFunction
(PubSub 事件)和一个 CloudEventsFunction
(使用 Cloud Events 规范的存储事件)。
选择您的函数
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 {
}
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
要构建您的应用程序,您可以使用标准命令打包它
quarkus build
./mvnw install
./gradlew build
前一个命令的结果是一个 JAR 文件,位于 target/deployment
存储库中,其中包含项目的类和依赖项。
然后,您将能够使用 gcloud
将您的函数部署到 Google Cloud。gcloud
命令将因触发您函数的事件而异。
我们将使用 Java 21 运行时,但您可以通过在部署命令上使用 --runtime=java17 而不是 --runtime=java21 来切换到 Java 17 运行时。 |
第一次启动此命令时,您可能会收到以下错误消息
这意味着 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
入口点必须始终设置为 |
此命令将为您提供一个指向您函数的 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
入口点必须始终设置为 |
要触发该事件,您可以将文件发送到 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
入口点必须始终设置为 |
要触发该事件,您可以将文件发送到 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
入口点必须始终设置为 |
要触发该事件,您可以将文件发送到 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 之前,您首先需要通过以下方式构建您的函数
quarkus build
./mvnw install
./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)
}
}
-
这是一个标准的 Quarkus 测试,必须由
@QuarkusTest
注解。 -
@WithFunction(FunctionType.HTTP)
表示将该函数作为 HTTP 函数启动。如果同一应用程序中存在多个函数,则必须使用functionName
属性来表示应启动哪个函数。 -
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);
}
}
-
这是一个标准的 Quarkus 测试,必须由
@QuarkusTest
注解。 -
@WithFunction(FunctionType.BACKGROUND)
表示将该函数作为后台函数启动。如果同一应用程序中存在多个函数,则必须使用functionName
属性来表示应启动哪个函数。 -
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);
}
}
-
这是一个标准的 Quarkus 测试,必须由
@QuarkusTest
注解。 -
@WithFunction(FunctionType.RAW_BACKGROUND)
表示将该函数作为原始后台函数启动。如果同一应用程序中存在多个函数,则必须使用functionName
属性来表示应启动哪个函数。 -
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);
}
}
-
这是一个标准的 Quarkus 测试,必须由
@QuarkusTest
注解。 -
@WithFunction(FunctionType.CLOUD_EVENTS)
表示将该函数作为云事件函数启动。如果同一应用程序中存在多个函数,则必须使用functionName
属性来表示应启动哪个函数。 -
REST-assured 用于测试该函数,描述存储事件的此负载将通过 invoker 发送到它。
-
云事件标头必须通过 HTTP 标头发送。
下一步是什么?
如果您正在寻找 Google Cloud Functions 的 Jakarta REST、Servlet 或 Vert.x 支持,我们通过我们的 Google Cloud Functions HTTP 绑定提供了它。
如果您正在寻找与提供程序无关的 Google Cloud Functions 实现,我们通过我们的 Funqy Google Cloud Functions 扩展提供了它。