[A-00178]JavaでCloudFunctionsを作成する
java使ってCloudfunctionsを作成・デプロイ・実行するまでの方法を記載しておく
・プロジェクトの準備
mvnコマンドで下記を実行し、CloudFunctions用のプロジェクトを作成します。
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false -DgroupId=cloudfn -DartifactId=cloudfntest1
pom.xmlを下記の通りに書き換えます。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cloudfn</groupId>
<artifactId>cloudfntest1</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>cloudfntest1</name>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Required for Function primitives -->
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--
Google Cloud Functions Framework Maven plugin
This plugin allows you to run Cloud Functions Java code
locally. Use the following terminal command to run a
given function locally:
mvn function:run -Drun.functionTarget=your.package.yourFunction
-->
<groupId>com.google.cloud.functions</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>0.11.0</version>
<configuration>
<functionTarget>cloudfn.HelloWorld</functionTarget>
</configuration>
</plugin>
</plugins>
</build>
</project>
defaultだとApp.javaが作成されてますが、そこと同じ階層にテスト用クラスを作成します。
package cloudfn;
import java.io.BufferedWriter;
import java.io.IOException;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
public class HelloWorld implements HttpFunction {
@Override
public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws IOException {
BufferedWriter writer = httpResponse.getWriter();
writer.write("Hello World!");
}
}
次にcloudtest1直下に移動してターミナルから下記のコマンドを実行し、ソースをコンパイルします。
mvn clean install -DskipTests=true
cloudfunctionsをローカルで実行するため、下記のコマンドを実行し、HttpRequestを受付できる状態にします。
mvn function:run
下記のようなログが出力されれば実行できた状態です。
[INFO] Started @4434ms
1月 29, 2024 4:12:46 午前 com.google.cloud.functions.invoker.runner.Invoker logServerInfo
情報: Serving function...
1月 29, 2024 4:12:46 午前 com.google.cloud.functions.invoker.runner.Invoker logServerInfo
情報: Function: cloudfn.HelloWorld
1月 29, 2024 4:12:46 午前 com.google.cloud.functions.invoker.runner.Invoker logServerInfo
情報: URL: http://localhost:8080/
ブラウザでアクセスすると下記の通りになります。

・GCP環境にデプロイする
Google Cloud環境に関数をデプロイします。
下記のgcloudコマンドを実行します。実行する場所はpom.xmlが存在する階層でなければエラーとなりますので注意。
gcloud functions deploy java-http-function \
--gen2 \
--entry-point=cloudfn.HelloWorld \
--runtime=java17 \
--region=asia-northeast1 \
--source=. \
--trigger-http \
--allow-unauthenticated
下記のログが出力されればデプロイ完了です。
state: ACTIVE
updateTime: '2024-01-28T19:21:59.420682870Z'
url: https://asia-northeast1-<your-project>.cloudfunctions.net/java-http-function
Updates are available for some Google Cloud CLI components. To install them,
please run:
$ gcloud components update
To take a quick anonymous survey, run:
$ gcloud survey
上記のurlに記載されたendpointにアクセスしてみます。
curl https://asia-northeast1-<your-project>.cloudfunctions.net/java-http-function
上記を実行してHello Worldと返ってきたら完了です。
・Cloud Storageのイベントドリブン関数を作成してみる
CloudStorageからイベントを受信してCloudFunctionsを実行します。
ストレージバケットを作成します。
gsutil mb -c standard -l asia-northeast1 gs://testcfneventjp20240129
次にpom.xmlに依存関係を追加します。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cloudfn</groupId>
<artifactId>cloudfntest1</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>cloudfntest1</name>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.20.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Required for Function primitives -->
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>2.32.1</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloudevent-types</artifactId>
<version>0.14.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--
Google Cloud Functions Framework Maven plugin
This plugin allows you to run Cloud Functions Java code
locally. Use the following terminal command to run a
given function locally:
mvn function:run -Drun.functionTarget=your.package.yourFunction
-->
<groupId>com.google.cloud.functions</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>0.11.0</version>
<configuration>
<functionTarget>cloudfn.HelloWorld</functionTarget>
</configuration>
</plugin>
</plugins>
</build>
</project>
次にjavaソースです。こちらは公式ドキュメントのサンプルソースを真似た内容なのでログしか出ません。
package cloudfn;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
import com.google.events.cloud.storage.v1.StorageObjectData;
import com.google.protobuf.util.JsonFormat;
import com.google.cloud.functions.CloudEventsFunction;
import io.cloudevents.CloudEvent;
public class GCSEventFunction implements CloudEventsFunction {
private static final Logger logger = Logger.getLogger(GCSEventFunction.class.getName());
@Override
public void accept(CloudEvent event) throws Exception {
logger.info("Event" + event.getId());
logger.info("Event Type:" + event.getType());
if (event.getData() == null) {
logger.warning("No data found in cloud event payload.");
}
String cloudEventData = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
StorageObjectData.Builder builder = StorageObjectData.newBuilder();
JsonFormat.parser().merge(cloudEventData, builder);
StorageObjectData data = builder.build();
logger.info("Bucket: " + data.getBucket());
logger.info("File: " + data.getName());
logger.info("Metageneration: " + data.getMetageneration());
logger.info("Created: " + data.getTimeCreated());
logger.info("Updated: " + data.getUpdated());
}
}
GCPサービスはサービスアカウントを介して利用するので必要な権限を付与します。
export PROJECT_ID=<your-project-id>
export SERVICE_ACCOUNT=<your-service-account>
各サービス権限を追加
gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:$SERVICE_ACCOUNT --role roles/eventarc.serviceAgent
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member serviceAccount:$SERVICE_ACCOUNT \
--role roles/pubsub.publisher
cloud storageサービスアカウントにpubsub権限を付与します。

gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:<cloud-storage-sa> --role roles/pubsub.publisher
下記コマンドを実行してデプロイします。
gcloud functions deploy java-finalize-function --gen2 --runtime=java17 --region=asia-northeast1 --source=. --entry-point=cloudfn.GCSEventFunction --memory=512MB --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" --trigger-event-filters="bucket=testcfneventjp20240129"
gcloudコマンド実行ログにエンドポイントのurlが表示されます。今回の場合はcloud storageにオブジェクトを作成することで実行されるので先ほど作成したバケットに適当なファイルをアップロードして確認してください。
うまく実行されれば下記のようにログが出力されます。

・JsonファイルをCSVに変換するイベント関数
下記がアーキテクチャ図になります

アップロードされたJsonファイルをCSVファイルに変換するイベント関数を作成します。
CloudStorageから取得したJSONファイルをCSVに変換してアップロードするという内容です。
pom.xmlにライブラリを追加します。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cloudfn</groupId>
<artifactId>cloudfntest1</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>cloudfntest1</name>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.20.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Required for Function primitives -->
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>2.32.1</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloudevent-types</artifactId>
<version>0.14.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.16.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-csv -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.16.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--
Google Cloud Functions Framework Maven plugin
This plugin allows you to run Cloud Functions Java code
locally. Use the following terminal command to run a
given function locally:
mvn function:run -Drun.functionTarget=your.package.yourFunction
-->
<groupId>com.google.cloud.functions</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>0.11.0</version>
<configuration>
<functionTarget>cloudfn.HelloWorld</functionTarget>
</configuration>
</plugin>
</plugins>
</build>
</project>
アップロード用バケットを作成します。
gsutil mb -c standard -l asia-northeast1 gs://testuploadcfneventjp20240130
ディレクトリ構成、ソースコードは下記です。

package cloudfn;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import com.google.events.cloud.storage.v1.StorageObjectData;
import com.google.protobuf.util.JsonFormat;
import cloudfn.elements.StudentProfile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.csv.CsvGenerator;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import io.cloudevents.CloudEvent;
import lombok.Data;
public class JsonEventConverterFn implements CloudEventsFunction {
private static final Logger logger = Logger.getLogger(GCSEventFunction.class.getName());
private static final String HEADER_RECORD = "\"name\",\"age\",\"sex\",\"address\",\"school\"";
private static final String EXT_JSON = ".json";
private static final String EXT_CSV = ".csv";
private static final char QUOTE_CHAR = '"';
@Override
public void accept(CloudEvent event) throws Exception {
logger.info("Event" + event.getId());
logger.info("Event Type:" + event.getType());
if (event.getData() == null) {
logger.warning("No data found in cloud event payload.");
return;
}
String cloudEventData = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
StorageObjectData.Builder builder = StorageObjectData.newBuilder();
JsonFormat.parser().merge(cloudEventData, builder);
StorageObjectData data = builder.build();
logger.info("Bucket: " + data.getBucket());
logger.info("File: " + data.getName());
logger.info("Metageneration: " + data.getMetageneration());
logger.info("Created: " + data.getTimeCreated());
logger.info("Updated: " + data.getUpdated());
// ファイル名を作成
String uploadFileName = StringUtils.replace(data.getName(), EXT_JSON, EXT_CSV);
// Storageオブジェクト作成
Storage storage = StorageOptions.newBuilder()
.setProjectId(System.getenv("PROJECTID"))
.build().getService();
// 対象ファイルダウンロード
byte[] fileContent = storage.readAllBytes(data.getBucket(), data.getName());
// ファイルはLF改行されているため、BufferedReaderで読み取り
BufferedReader reader = new BufferedReader(new StringReader(new String(fileContent)));
// CSVファイル構築用StringBuilder(ヘッダレコード追加)
StringBuilder outputFileBuilder = new StringBuilder();
outputFileBuilder.append(HEADER_RECORD);
// ObjectMapper作成
ObjectMapper objectMapper = new ObjectMapper();
// CsvMapper作成(囲い文字設定有効化)
CsvMapper csvMapper = new CsvMapper();
csvMapper.configure(CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS, true);
// CsvSchema作成
CsvSchema csvSchema = csvMapper.schemaFor(StudentProfile.class)
.sortedBy("name", "age","sex","address","school")
.withQuoteChar(QUOTE_CHAR)
.withoutHeader();
// JSON→CSV変換処理
StudentProfile lineObject = new StudentProfile();
String line;
while ((line = reader.readLine()) != null) {
try {
// 行データをオブジェクトに変換
lineObject = objectMapper.readValue(line, StudentProfile.class);
// オブジェクトをCSV行に変換してStringBuilderにCSV行を追加
outputFileBuilder.append(csvMapper.writer(csvSchema).writeValueAsString(lineObject));
} catch (Exception e) {
logger.warning("Error happen when convert json to csv.");
throw e;
}
}
// ファイルを指定のバケットにアップロード
BlobId blobId = BlobId.of(System.getenv("UPLOADBKT"), uploadFileName);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
storage.createFrom(blobInfo, new ByteArrayInputStream(outputFileBuilder.toString().getBytes()));
logger.info("Success convert json to csv.");
}
}
package cloudfn.elements;
import lombok.Data;
@Data
public class StudentProfile {
public StudentProfile() {
}
private String name;
private String age;
private String sex;
private String address;
private String school;
}
下記のgcloudコマンドでデプロイします。
gcloud functions deploy json-conv-csv-fn \
--gen2 \
--runtime=java17 \
--region=asia-northeast1 \
--service-account=437530287615-compute@developer.gserviceaccount.com \
--source=. \
--entry-point=cloudfn.JsonEventConverterFn \
--memory=512MB \
--trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
--trigger-event-filters="bucket=testcfneventjp20240129" \
--set-env-vars="UPLOADBKT=testuploadcfneventjp20240130" \
--set-env-vars="PROJECTID=<your-project-id>"
・Appendix
公式ドキュメントはこちら
https://cloud.google.com/functions/docs/create-deploy-http-java?hl=ja
https://cloud.google.com/eventarc/docs/workflows/cloudevents?hl=ja
https://cloud.google.com/functions/docs/create-deploy-http-java?hl=ja
https://cloud.google.com/functions/docs/securing/function-identity?hl=ja
https://firebase.google.com/docs/functions/config-env?hl=ja&gen=2nd
https://cloud.google.com/functions/docs/samples/functions-env-vars?hl=ja#functions_env_vars-java
参考ドキュメントはこちら
https://www.baeldung.com/java-converting-json-to-csv
https://qiita.com/koguren/items/f7685e38ff2bfa60152c
https://boul.tech/gcf-env-var/
https://stackoverflow.com/questions/10308452/how-to-convert-the-following-json-string-to-java-object
https://sight-r.sts-inc.co.jp/tech/java-lombok/
https://qiita.com/SYM_simu/items/c9d7897538195e105f38
https://www.sejuku.net/blog/49981
https://stackoverflow.com/questions/33752670/generating-a-csv-with-jackson-no-quoted-char
https://qiita.com/neras_1215/items/c9df631b55aaeaba275b
https://morioh.com/a/2db9e115beb4/converting-json-to-csv-in-java
https://blog.groupdocs.cloud/ja/conversion/convert-csv-to-json-and-json-to-csv-in-java/
コメントを残す