Google Cloud Functions (Serverless)
The quarkus-google-cloud-functions
extension allows you to use Quarkus to build your Google Cloud Functions.
Your functions can use injection annotations from CDI or Spring and other Quarkus facilities as you need them.
这项技术被认为是preview。 在 preview(预览) 中,不保证向后兼容和在生态系统中的存在。具体的改进可能需要改变配置或API,并且正在计划变得 稳定 。欢迎在我们的 邮件列表 中提供反馈,或在我们的 GitHub问题列表 中提出问题。 For a full list of possible statuses, check our FAQ entry. |
先决条件
完成这个指南,你需要:
-
大概15分钟
-
编辑器
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.9
-
如果你愿意的话,还可以选择使用Quarkus CLI
-
A Google Cloud Account. Free accounts work.
解决方案
This guide walks you through generating a sample project followed by creating multiple functions showing how to implement HttpFunction
, BackgroundFunction
and RawBackgroundFunction
in Quarkus.
Once built, you will be able to deploy the project to Google Cloud.
If you don’t want to follow all these steps, you can go right to the completed example.
克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载一个 存档 。
The solution is located in the google-cloud-functions-quickstart
directory.
创建Maven部署项目
Create an application with the quarkus-google-cloud-functions
extension.
You can use the following Maven command to create it:
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=google-cloud-functions"
Login to Google Cloud
Login to Google Cloud is necessary for deploying the application. It can be done as follows:
gcloud auth login
Creating the functions
For this example project, we will create four functions, one HttpFunction
, one BackgroundFunction
(Storage event),
one RawBackgroundFunction
(PubSub event) and one CloudEventsFunction
(storage event using the Cloud Events specification).
Quarkus supports Cloud Functions gen 1 and gen 2. For an overview of Cloud Functions gen 2 see this page on the Google Cloud Functions documentation. To use gen 2 you must add the --gen2 parameter.
|
Choose Your Function
The quarkus-google-cloud-functions
extension scans your project for a class that directly implements the Google Cloud HttpFunction
, BackgroundFunction
, RawBackgroundFunction
or CloudEventsFunction
interface.
It must find a class in your project that implements one of these interfaces, or it will throw a build time failure.
If it finds more than one function classes, a build time exception will also be thrown.
Sometimes, though, you might have a few related functions that share code and creating multiple maven modules is just an overhead you don’t want to do. The extension allows you to bundle multiple functions in one project and use configuration or an environment variable to pick the function you want to deploy.
To configure the name of the function, you can use the following configuration property:
quarkus.google-cloud-functions.function=test
The quarkus.google-cloud-functions.function
property tells Quarkus which function to deploy. This can be overridden
with an environment variable too.
The CDI name of the function class must match the value specified within the quarkus.google-cloud-functions.function
property.
This must be done using the @Named
annotation.
@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 | The @Named annotation allows to name the CDI bean to be used by the quarkus.google-cloud-functions.function property, this is optional. |
2 | The function must be a CDI bean |
3 | This is a regular Google Cloud Function implementation, so it needs to implement com.google.cloud.functions.HttpFunction . |
4 | Injection works inside your function. |
5 | This is standard Google Cloud Function implementation, nothing fancy here. |
The BackgroundFunction
This BackgroundFunction
is triggered by a Storage event, you can use any events supported by Google Cloud instead.
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 | The @Named annotation allows to name the CDI bean to be used by the quarkus.google-cloud-functions.function property, this is optional. |
2 | The function must be a CDI bean |
3 | This is a regular Google Cloud Function implementation, so it needs to implement com.google.cloud.functions.BackgroundFunction . |
4 | Injection works inside your function. |
5 | This is standard Google Cloud Function implementation, nothing fancy here. |
6 | This is the class the event will be deserialized to. |
The RawBackgroundFunction
This RawBackgroundFunction
is triggered by a PubSub event, you can use any events supported by Google Cloud instead.
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 | The @Named annotation allows to name the CDI bean to be used by the quarkus.google-cloud-functions.function property, this is optional. |
2 | The function must be a CDI bean |
3 | This is a regular Google Cloud Function implementation, so it needs to implement com.google.cloud.functions.RawBackgroundFunction . |
4 | Injection works inside your function. |
5 | This is standard Google Cloud Function implementation, nothing fancy here. |
The CloudEventsFunction
CloudEventsFunction is a feature of Cloud Functions gen 2 only.
|
This CloudEventsFunction
is triggered by a Cloud Events Storage event, you can use any Cloud Events supported by Google Cloud instead.
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 | The @Named annotation allows to name the CDI bean to be used by the quarkus.google-cloud-functions.function property, this is optional. |
2 | The function must be a CDI bean |
3 | This is a regular Google Cloud Function implementation, so it needs to implement com.google.cloud.functions.CloudEventsFunction . |
4 | Injection works inside your function. |
5 | This is standard Google Cloud Function implementation, nothing fancy here except that it receives a io.cloudevents.CloudEvent . |
6 | This is the storage event inside the Cloud Events. |
Build and Deploy to Google Cloud
To build your application, you can package it using the standard command:
quarkus build
./mvnw install
./gradlew build
The result of the previous command is a single JAR file inside the target/deployment
repository that contains classes and dependencies of the project.
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 this command, you can have the following error message:
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. |
The HttpFunction
This is an example command to deploy your HttpFunction
to Google Cloud:
gcloud functions deploy quarkus-example-http \
--entry-point=io.quarkus.gcp.functions.QuarkusHttpFunction \
--runtime=java21 --trigger-http --allow-unauthenticated --source=target/deployment
The entry point must always be set to |
This command will give you as output a httpsTrigger.url
that points to your function.
The BackgroundFunction
Before deploying your function, you need to create a bucket.
gsutil mb gs://quarkus-hello
This is an example command to deploy your BackgroundFunction
to Google Cloud, as the function is triggered by a Storage event,
it needs to use --trigger-event google.storage.object.finalize
and the --trigger-resource
parameter with the name of a previously created bucket:
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
The entry point must always be set to |
To trigger the event, you can send a file to the GCS quarkus-hello
bucket, or you can use gcloud to simulate one:
gcloud functions call quarkus-example-storage --data '{"name":"test.txt"}'
--data contains the GCS event, it is a JSON document with the name of the file added to the bucket.
|
The RawBackgroundFunction
This is an example command to deploy your RawBackgroundFunction
to Google Cloud, as the function is triggered by a PubSub event,
it needs to use --trigger-event google.pubsub.topic.publish
and the --trigger-resource
parameter with the name of a previously created topic:
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
The entry point must always be set to |
To trigger the event, you can send a file to the hello_topic
topic, or you can use gcloud to simulate one:
gcloud functions call quarkus-example-pubsub --data '{"data":{"greeting":"world"}}'
The CloudEventsFunction
CloudEventsFunction is a feature of Cloud Functions gen 2 only.
|
This is an example command to deploy your CloudEventsFunction
to Google Cloud, as the function is triggered by a Storage event,
it needs to use --trigger-bucket
parameter with the name of a previously created bucket:
gcloud functions deploy quarkus-example-cloud-event --gen2 \
--entry-point=io.quarkus.gcp.functions.QuarkusCloudEventsFunction \
--runtime=java21 --trigger-bucket=example-cloud-event --source=target/deployment
The entry point must always be set to |
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.3.0' \
-DoutputDirectory=.
Before using the invoker, you first need to build your function via:
quarkus build
./mvnw install
./gradlew build
The HttpFunction
For an HttpFunction
, you can use this command to launch your function locally.
java -jar java-function-invoker-1.3.0.jar \
--classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
--target io.quarkus.gcp.functions.QuarkusHttpFunction
The --classpath parameter needs to be set to the previously packaged JAR that contains your function class and all Quarkus related classes.
|
Your endpoints will be available on http://localhost:8080.
The BackgroundFunction
For background functions, you launch the invoker with a target class of io.quarkus.gcp.functions.BackgroundFunction
.
java -jar java-function-invoker-1.3.0.jar \
--classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
--target io.quarkus.gcp.functions.QuarkusBackgroundFunction
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":"hello.txt"}}'
This will call your Storage background function with an event {"name":"hello.txt"}
, so an event on the hello.txt
file.
The RawBackgroundFunction
For background functions, you launch the invoker with a target class of io.quarkus.gcp.functions.BackgroundFunction
.
java -jar java-function-invoker-1.3.0.jar \
--classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
--target io.quarkus.gcp.functions.QuarkusBackgroundFunction
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":{"greeting":"world"}}'
This will call your PubSub background function with a PubSubMessage {"greeting":"world"}
.
The CloudEventsFunction
CloudEventsFunction is a feature of Cloud Function gen 2 only.
|
For cloud events functions, you launch the invoker with a target class of io.quarkus.gcp.functions.QuarkusCloudEventsFunction
.
java -jar java-function-invoker-1.3.0.jar \
--classpath target/google-cloud-functions-1.0.0-SNAPSHOT-runner.jar \
--target io.quarkus.gcp.functions.QuarkusCloudEventsFunction
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 cloud events 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 Google Cloud functions via the quarkus-test-google-cloud-functions
dependency.
To use it, you must add the following test dependency 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.
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)
}
}
-
This is a standard Quarkus test that must be annotated by
@QuarkusTest
. -
@WithFunction(FunctionType.HTTP)
indicates to launch the function as an HTTP function. If multiple functions exist in the same application, thefunctionName
attribute must be used to denote which one should be launched. -
REST-assured is used to test the function,
Hello World!
will be sent to it via the 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);
}
}
-
This is a standard Quarkus test that must be annotated by
@QuarkusTest
. -
@WithFunction(FunctionType.BACKGROUND)
indicates to launch the function as a background function. If multiple functions exist in the same application, thefunctionName
attribute must be used to denote which one should be launched. -
REST-assured is used to test the function,
{"name":"hello.txt"}
will be sent to it via the invoker.
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);
}
}
-
This is a standard Quarkus test that must be annotated by
@QuarkusTest
. -
@WithFunction(FunctionType.RAW_BACKGROUND)
indicates to launch the function as a raw background function. If multiple functions exist in the same application, thefunctionName
attribute must be used to denote which one should be launched. -
REST-assured is used to test the function,
{"name":"hello.txt"}
will be sent to it via the invoker.
The CloudEventsFunction
Cloud Events Function is a feature of Cloud Functions gen 2 only. |
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);
}
}
-
This is a standard Quarkus test that must be annotated by
@QuarkusTest
. -
@WithFunction(FunctionType.CLOUD_EVENTS)
indicates to launch the function as a cloud events function. If multiple functions exist in the same application, thefunctionName
attribute must be used to denote which one should be launched. -
REST-assured is used to test the function, this payload that describe a storage event will be sent to it via the invoker.
-
The cloud events headers must be sent via HTTP headers.
下一步做什么?
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.
If you are looking for a provider-agnostic implementation of your Google Cloud Functions, we have it thanks to our Funqy Google Cloud Functions extension.