[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://cloud.google.com/storage/docs/uploading-objects-from-memory?hl=ja#storage-upload-object-from-memory-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/

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

*