3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

withAdvent Calendar 2021

Day 5

Cloud FunctionsをKotlin/JVMで書きたい!

Last updated at Posted at 2021-12-04

GCPにはAWS Lambdaに相当する関数実行環境であるCloud Functionsがあり、これがちょっとしたツールを作る時には非常に便利です。
このCloud Functionsはしばらく前からJava11をサポートしており、JVM環境が動くということは当然ながらKotlin/JVMでの実装も可能です!

記事にするにはちょっと旬を逃してる気はしますが、普段からKotlinを書いてる身なのでフルKotlin/JVMでCloud Functionsを動かすためのだいたい最小構成についてまとめてみます。
(ある程度Kotlin+Gradleに慣れている前提でお話します)

GCPプロジェクトを作成してCloud Build APIを有効にしておく

ここでは説明は省きますが、下記ドキュメントを参考に最低限GCPプロジェクトの作成は済ませておいてください。
https://cloud.google.com/resource-manager/docs/creating-managing-projects

また今回の流れではCloud Build APIの有効化が必要になるのでGCPのダッシュボードよりCloud Buildを選択して有効化しておいてください。(有効化してないとデプロイ時に有効化してないぞと怒られて失敗します)

スクリーンショット 2021-12-02 17.46.08.png

functions-framework-javaの導入

Cloud FunctionsをJVMで動作させるには functions-framework-javaというライブラリを利用します。
使い方は簡単で基本的には下記で導入は完了です。

build.gradle
configurations {
    invoker
}

dependencies {
    implementation("com.google.cloud.functions:functions-framework-api:1.0.4")
    invoker("com.google.cloud.functions.invoker:java-function-invoker:1.1.0")
}

処理の記述

ここまででCloud Functionsを記述するための準備は出来ているので早速実装に入ります。
HttpFunctionを継承したKotlinクラスを作成してください。

Application
class Application : HttpFunction {
    override fun service(request: HttpRequest, response: HttpResponse) {
        response.writer.write("Hello World!")
    }
}

fun service(request: HttpRequest, response: HttpResponse)というメソッドの実装を求められますが、ここがアクセスがあった際に呼ばれるエントリポイントとなります。
上記では"Hello World!"という文字列を返しているだけですが、任意の処理を記述して値を返すように変更してくれればOKです!

また、requestの引数からはアクセス時のクエリパラメータなどが取得できるので処理の分岐等も簡単です。
スクリーンショット 2021-12-02 16.14.04.png

ローカル環境での実行

コードの実装まで終わったらまずはローカル環境での実行を試してみます。
READMEでの説明の通り、runFunctionというタスクをGradleに追加していきます。

build.gradle
tasks.register("runFunction", JavaExec) {
    main = "com.google.cloud.functions.invoker.runner.Invoker"
    classpath(configurations.invoker)
    inputs.files(configurations.runtimeClasspath, sourceSets.main.output)
    args(
            "--target", project.findProperty("run.functionTarget") ?: "",
            "--port", project.findProperty("run.port") ?: 8080
    )
    doFirst {
        args("--classpath", files(configurations.runtimeClasspath, sourceSets.main.output).asPath)
    }
}

その上で、下記コマンドでローカル環境での実行が可能です。

shell
$ ./gradlew runFunction -Prun.functionTarget=com.kazakago.cloud_functions_starter.Application

com.kazakago.cloud_functions_starter.Applicationと書いている部分はご自身のエントリポイントクラスのフルパスに差し替えて指定してください。
問題なく実行されると受付状態になるので、ブラウザやcurlコマンドあたりで適当にアクセスしてもらえば"Hello World!"が表示されるはずです。

shell
$ curl localhost:8080
# Output: Hello World!

スクリーンショット 2021-12-02 16.24.36.png

IDEを利用している場合にはもちろんブレークポイントも動作するのでデバッグも捗ります。

Cloud Functionsへのデプロイ

ローカル環境で期待通りの動きが出来たら、晴れてCloud Functionsにデプロイしてみます。
まずはgoogle-cloud-sdkをPCにインストールしてください。
※この記事ではMac+Homebrew導入済みを前提に進めます。他プラットフォーム等は公式Docをご参照ください。

shell
$ brew install --cask google-cloud-sdk

上記コマンドでgoogle-cloud-sdkがインストールできるので作成したGCPプロジェクトへのアクセス権限があるアカウントでログインしてください。

shell
$ gcloud auth login

問題なくログインまで出来たら下記コマンドでデプロイを行います。
gcloud functions deployに続くcloud_functions_starterと記述している部分はこの関数の名前になります。ダッシュボード上で表示されるのでわかりやすい名前をつけてあげてください。
--project--entry-pointについてはご自身のプロジェクト名やフルパスに変更してください。
今回は--trigger-http--allow-unauthenticatedを付与して外部からの呼び出しを許可しています。
この辺は用途に合わせて変更してください!

shell
gcloud functions deploy cloud_functions_starter --project=kazakago-playground --entry-point=com.kazakago.cloud_functions_starter.Application --runtime=java11 --trigger-http --allow-unauthenticated

デプロイが完了するとアクセスできるURLが表示されるのでそれをブラウザかcurlで叩くとローカルで実行したときと同様の振る舞いがされているはずです。
URLがわからなくなった場合はこの辺のドキュメントを見てもらえば調べられると思います。

スクリーンショット 2021-12-02 18.34.37.png

build.gradleの全体像

ここまでのbuild.gradle.ktsをまとめてみます。
JavaVersion.VERSION_11kotlinOptions.jvmTarget = "11"の指定はなくても動作するかもしれませんが、Cloud Functionsの実行環境である11と同等になるように設定しておいて損はないかと思います。

build.gradle
plugins {
    id "org.jetbrains.kotlin.jvm" version "1.6.0"
}

sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

compileKotlin {
    kotlinOptions.jvmTarget = "11"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "11"
}

repositories {
    mavenCentral()
}

configurations {
    invoker
}

dependencies {
    implementation("com.google.cloud.functions:functions-framework-api:1.0.4")
    invoker("com.google.cloud.functions.invoker:java-function-invoker:1.1.0")
}

tasks.register("runFunction", JavaExec) {
    main = "com.google.cloud.functions.invoker.runner.Invoker"
    classpath(configurations.invoker)
    inputs.files(configurations.runtimeClasspath, sourceSets.main.output)
    args(
            "--target", project.findProperty("run.functionTarget") ?: "",
            "--port", project.findProperty("run.port") ?: 8080
    )
    doFirst {
        args("--classpath", files(configurations.runtimeClasspath, sourceSets.main.output).asPath)
    }
}

Githubにそのまま動かすことの出来るサンプルプロジェクトも置いてあるので良ければご参照ください。
https://github.com/KazaKago/cloud_functions_starter

余談1: Cloud Functions for Firebaseについて

Firebaseを利用している方であればご存知とは思いますが、Firebase側にもCloud Functionsという機能が提供されておりそちらはCloud Functions for Firebaseと呼ばれています。
バックエンドとしてはおそらく同一のもので、機能的にも(微妙に違う部分はあれど)だいたい一緒です。
デプロイしたコードは連携しているFirebaseとGCPのダッシュボード両方に表示されます。

違いとしてCloud Functions for FirebaseはFirebase CLIコマンドからもデプロイが可能という点があります。
しかしながらこのFirebase CLIでは現在JavascriptまたはTypescriptのみサポートしており、今回メインで話したKotlin/JVMプロジェクトのデプロイは出来ません。
なので私のようなほとんどFirebaseの機能しか使ってないモバイルエンジニアであっても、Kotlin/JVMでCloud Functionsを書きたい場合にはgoogle-cloud-sdkを利用する必要があります。ご注意くださいませ!

余談2: Gradle Kotlin DSLについて

もちろん書こうと思えばGradle Kotlin DSLでも書けるのですが
デフォルトの状態だとCloud Buildがbuild.gradle.ktsをうまくフックしてくれないのでそのままでは自動ビルドが行えませんでした。今回は追加の記述を省くためにgroovyで実装しています。
Kotlin DSLで書きたい場合はfat-jarを生成できる環境を整えた上でCloud Buildに流し込めば問題ないと思います。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?