hero_picture
Cover Image for Kotlin/JSのAWS Lambda関数でJsonを返してみる

Kotlin/JSのAWS Lambda関数でJsonを返してみる

こんにちは, Web事業部の西村です

私の前回の記事 Promiseを利用して非同期に処理するようにしてみました

今回はこのLambda関数で kotlinx.serialization を用いてJsonを返すようにしてみたいと思います

目次

過去の記事

なぜ kotlinx.serialization?

まず kotlinx.serialization を用いる理由です

ここまでの記事を読んでくださっている方々であれば “わざわざないでしょ” と思っている方もいると思います

その通りで, json() と JSON.stringifiy() 関数を用いればJsonを返すことができます

しかし, kotlinx.serialization を用いることにより, Client側でも同じクラスを利用することができ, 開発する際に便利であるといえます

またマルチプラットフォームであるため様々な環境で利用できるというのもメリットです

注意事項

この記事では kotlinx.serialization に関する詳しい解説は行いません

詳しく知りたい方は下記のリポジトリをご覧ください

github.com

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクト

前回の記事 まで作成したプロジェクトを利用します

プロジェクトの構成は下記のようになっています

1KotlinLambda
2├─.gradle/
3├─.idea/
4├─build/
5├─gradle/
6├─src/
7│ └─main/
8│   └─kotlin/
9│     └─jp.co.seeds_std.lambda.kotlin/
10│       └─handler.kt
11├─build.gradle.kts
12├─compress.zip
13├─gradlew
14├─gradlew.bat
15└─settings.gradle.kts

また, handler.ktは下記のようになっています

1package jp.co.seeds_std.lambda.kotlin
2
3import kotlinx.coroutines.GlobalScope
4import kotlinx.coroutines.async
5import kotlinx.coroutines.awaitAll
6import kotlinx.coroutines.delay
7import kotlinx.coroutines.promise
8import kotlin.js.Json
9import kotlin.js.json
10
11@JsExport
12@JsName("handler")
13fun handler(event: Json, context: Json) = GlobalScope.promise {
14    val tasks = (1..10).map {
15        async {
16            delay(1000)
17            it
18        }
19    }
20    val results = tasks.awaitAll()
21    return@promise Response(body = "Result: ${results.sum()} - Coroutines")
22}
23data class Response(
24    val statusCode: Int = 200,
25    val body: String = "{}",
26    val isBase64Encoded: Boolean = false,
27    val headers: Json = json(),
28    val multiValueHeaders: Json = json()
29)

Jsonを返すようにする

目標

今回は下記のようなJsonを返すようにしてみます

1{
2    "id": 0, // ID
3    "text": "", // 文字列
4    "random_numbers": [5, 38, 24] // 適当な数値
5}

依存関係を追加する

kotlinx.serialization は外部ライブラリですので依存関係を追加する必要があります

また, 専用のプラグインも必要であるためプラグインの追加も行います

build.gradle.kts / pluginsブロック

1plugins {
2    kotlin("js") version "1.3.50"
3    kotlin("plugin.serialization") version "1.3.50"
4}

依存関係の追加

build.gradle.kts / dependenciesブロック

1sourceSets["main"].dependencies {
2    implementation(kotlin("stdlib-js"))
3    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2")
4    implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.13.0") // 追加
5}

リポジトリに jcenter を追加する必要があるので追加します

build.gradle.kts / repositories

1repositories {
2    mavenCentral()
3    jcenter() // 追加
4}

クライアント側に返すクラスを作成する

作成するクラスが今回のJsonに変換するクラスとなるので各種アノテーションも付与します

今回は Responseクラス の下に作成します(本当は別ファイルにするほうがいいです)

handler.kt

1@Serializable
2data class ResponseBody(
3    @SerialName("id")
4    val id: Int = 0,
5    @SerialName("text")
6    val text: String = "",
7    @SerialName("random_numbers")
8    val randomNumbers: List<Int> = emptyList()
9)
  • @Serializable これがついているクラスがシリアライズ/デシリアライズすることができます
  • @SerialName(String) 引数で決められた文字列がkeyとなるようになります, ない場合は変数名が利用されるようになるのでなくても問題ないです

Jsonを返す準備をする

まずは前回までに書いていたコードの一部を削除しスッキリさせます

handler.kt

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4    return@promise Response(body = "")
5}

続いて, 先ほど作ったクラスを文字列に変換するために kotlinxのJsonオブジェクト を作成します

handler.kt

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4    val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable) // 追加
5    return@promise Response(body = "")
6}
⚠注意1 今回の記事ではすでにkotlinが実装している Jsonクラス をインポートしているため同じ名前であるkotlinxの Jsonクラス をインポートできませんkotlinのJson と kotlinxのJson とを間違わないよう注意してください
⚠注意2 JsonConfiguration には Stable 以外の設定もありますが, 実験的機能となっていますので利用はお勧めしません

Jsonを返す

まずは先ほど作成した ResponseBodyクラス のオブジェクトを作成します

handler.kt

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4    val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable)
5    val responseBody = ResponseBody(1, "Lambda Json!!!", List(3) { Random.nextInt(100) }) // 追加
6    return@promise Response(body = "")
7}

最後に stringify関数 を用いてクラスを文字列に変換し レスポンスのbody に設定します

handler.kt

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4    val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable)
5    val responseBody = ResponseBody(1, "Lambda Json!!!", List(3) { Random.nextInt(100) })
6    return@promise Response(body = json.stringify(ResponseBody.serializer(), responseBody)) // 変更
7}
  • ResponseBody.serializer() この関数は @Serializable アノテーションがついているクラスの場合コンパイラが自動的に作成してくれます

動作確認

関数をデプロイし, API GatewayのURLにアクセスし {"id":1,"text":"Lambda Json!!!","random_numbers":[ランダムな数字が3つ]} 表示されれば成功です

最後に

いかがでしたでしょうか

LambdaでJsonを返すのはありがちです

これを利用することによりKotlinで作成するマルチプラットフォームなアプリケーションではかなり強力になりうると考えます

また, ここまで4つの記事に分け Kotlin/JS をもちいて AWS Lambda関数 を作って便利にしてみました

これがベストプラクティスかどうかはプロジェクトによると思いますが、1つの参考になるとうれしい限りです

ここまで読んでいただきありがとうございました


蛇足

JSON.stringify() を使ってJsonを返してみる

JavaScriptにもとから存在する, JSON.stringify を用いてもJsonを返すことができます

handler.js

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4    val responseBody = ResponseBody(1, "Lambda Json!!!", listOf(0, 1, 2))
5    return@promise Response(body = JSON.stringify(responseBody))
6}

実行結果

1{"id":1,"text":"Lambda Json!!!","randomNumbers":[0,1,2]}

しかしこれではJsonのKeyは指定できないので変数をすべて private に変更すると結果が変わるので注意が必要です

privateに変更したときの実行結果

1{"id_0":1,"text_0":"Lambda Json!!!","randomNumbers_0":[0,1,2]}

こういうケースもあるため kotlinx.serialization を用いることをお勧めします

json() と JSON.stringify() を使ってJsonを返してみる

一番オーソドックスでわかりやすい返し方だと私は思います

handler.js

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4    val responseBody = json(
5        "id" to 0,
6        "text" to "Lambda Json!!!",
7        "random_numbers" to listOf(0, 1, 2)
8    )
9    return@promise Response(body = JSON.stringify(responseBody))
10}

実行結果

1{"id":1,"text":"Lambda Json!!!","random_numbers":[0,1,2]}

蛇足まで読んでいただきありがとうございました