hero_picture
Cover Image for Kotlin/JSのAWS Lambda関数を便利にしてみる

Kotlin/JSのAWS Lambda関数を便利にしてみる

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

私の前回の記事 ではKotlin/JSを用いてLambda関数を書いてみました

しかし, dynamic を利用していたため使いにくい部分もあったと思います

今回はその点を改良しより使いやすくなるよう変更したいと思います

目次

過去の記事

開発環境

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

  • 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

Jsonを使って便利にしてみる

dynamicをなくす

Kotlin/JSには dynamic に似た Json があります

dynamic はJavaScriptのデータであったり, オブジェクトを格納することができますが, Json はKey-Value形式でのみ格納できるものとなっています

handler関数の引数, event と context はどちらもJavaScriptのJsonオブジェクトですのでKotlinの Json に変換できます

また, Callbackの第二引数もJsonオブジェクトを設定するためこちらも変更できます

handler.kt

1fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit)

↑ が ↓ に変更できます

1fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit)

このJsonオブジェクトへのアクセスはMapのように利用してアクセスすることができます

1event["body"] // こちらか
2event.get("body") // こちらでデータの取得ができます
3

Callbackに渡すのJsonを関数で作れるようにする

API Gatewayでは下記のJsonを処理するようになっています

1{
2    "isBase64Encoded": true|false,
3    "statusCode": httpStatusCode,
4    "headers": { "headerName": "headerValue", ... },
5    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
6"body": "..."
7}

こちらをもとに引数が5つの関数を作成したいと思います

記述する場所はhandler関数の下になります

handler.kt

1fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
2    "statusCode" to statusCode,
3    "body" to body,
4    "isBase64Encoded" to isBase64Encoded,
5    "headers" to headers,
6    "multiValueHeaders" to multiValueHeaders
7)
  • json() Jsonを作成する関数です/引数は vararg Pair<String, Any?> となっており json() と書くと空のJsonを作ることができます

Callbackに渡すよう変更する

現在handler関数は下記のようになっていると思います

handler.kt

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
4    val response: dynamic = object {}
5    response["statusCode"] = 200
6    response.body = "Hello from Kotlin Lambda!"
7    callback(null, response)
8}

この関数を先ほど作成した関数を用いた形に変更します

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
4    val response = responseJson(body = "Hello from Kotlin Json Lambda!")
5    callback(null, response)
6}

関数をデプロイする

前回と同様の手順を用いて関数をデプロイします

画面右側の Gradle タブを開き, kotlin_lambda -> Tasks -> other の順に開き, その中の compress をダブルクリックして実行します

ビルドに成功するとプロジェクトのディレクトリに compress.zip というファイルが更新されます

この生成されたファイルをコンソールからアップロードし, 保存します

動作確認

あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Json Lambda! と表示されれば成功です

レスポンスをクラスに変更する

レスポンスはクラスを用いても影響が少ないためクラスに置き換えます

リクエストにクラスを利用しない理由については蛇足をご覧ください

まずは responseJson関数 を変更します

handler.kt

1fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
2    "statusCode" to statusCode,
3    "body" to body,
4    "isBase64Encoded" to isBase64Encoded,
5    "headers" to headers,
6    "multiValueHeaders" to multiValueHeaders
7)

1data class Response(
2    val statusCode: Int = 200,
3    val body: String = "{}",
4    val isBase64Encoded: Boolean = false,
5    val headers: Json = json(),
6    val multiValueHeaders: Json = json()
7)

続いて, Callbackの引数を変更します

1fun handler(event: dynamic, context: dynamic, callback: (Error?, Json?) -> Unit)
2

1fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit)
2

最後にレスポンスを変更します

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
4    val response = responseJson(body = "Hello from Kotlin Json Lambda!")
5    callback(null, response)
6}

1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
4    val response = Response(body = "Hello from Kotlin Class Lambda!")
5    callback(null, response)
6}

この変更ができたらデプロイし動作を確認します

Hello from Kotlin Class Lambda! と表示されれば成功です

最後に

いかがでしたでしょうか

Kotlinを用いていると dynamic では少し取り扱いにくく感じてしまいます

しかし Json にしてしまうことで,Mapのように利用でき, より使いやすいのではないかと思います

次回は非同期処理にする記事を書きたいと思います

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

Kotlin/JSのAWS Lambda関数でPromiseを使うようにする


蛇足

リクエストのほうもクラス使ったらだめなの?

注意事項はありますが、クラスを利用しても問題はないです

handler.kt

1package jp.co.seeds_std.lambda.kotlin
2
3import kotlin.js.Json
4import kotlin.js.json
5
6@JsExport
7@JsName("handler")
8fun handler(event: Event, context: Json, callback: (Error?, Response?) -> Unit) {
9    println("body: ${event.body} isBase64Encoded: ${event.isBase64Encoded}")
10    // event.copy() // Error!
11    val response = Response(body = "Hello from Kotlin Json Lambda!")
12    callback(null, response)
13}
14
15data class Event(val body: String?, val isBase64Encoded: Boolean)
16
17data class Response(
18    val statusCode: Int = 200,
19    val body: String = "{}",
20    val isBase64Encoded: Boolean = false,
21    val headers: Json = json(),
22    val multiValueHeaders: Json = json()
23)

この時ログに body と isBase64Encoded の出力が行われます

その後 copy() を実行しようとした場合, 変数 event は Eventクラスではなく Json となっていますので, 関数が見つからず実行時にエラーが出てしまいます

また, 注意点として private な変数はトランスパイルした際に変数名が変更されてしまうためうまく利用できません

そのため, リクエストの event と context はJsonで, レスポンスはKotlinのクラスを利用していくことがいいのかなと私は思います

蛇足も読んでいただきありございました