こんにちは, 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のクラスを利用していくことがいいのかなと私は思います
蛇足も読んでいただきありございました