こんにちは, Web事業部の西村です
私の前回の記事 ではKotlin/JSを用いてLambda関数を書いてみました
しかし, dynamic
を利用していたため使いにくい部分もあったと思います
今回はその点を改良しより使いやすくなるよう変更したいと思います
目次
過去の記事
開発環境
この記事では下記の環境で開発を行っています
- AdoptOpenJDK 1.8.0_222-b10
- IntelliJ IDEA Community 2019.2.2
- Kotlin 1.3.50
プロジェクト
前回の記事 で作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています
KotlinLambda ├─.gradle/ ├─.idea/ ├─build/ ├─gradle/ ├─src/ │ └─main/ │ └─kotlin/ │ └─jp.co.seeds_std.lambda.kotlin/ │ └─handler.kt ├─build.gradle.kts ├─compress.zip ├─gradlew ├─gradlew.bat └─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
fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit)
↑ が ↓ に変更できます
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit)
このJsonオブジェクトへのアクセスはMapのように利用してアクセスすることができます
event["body"] // こちらか event.get("body") // こちらでデータの取得ができます
Callbackに渡すのJsonを関数で作れるようにする
API Gatewayでは下記のJsonを処理するようになっています *1
{ "isBase64Encoded": true|false, "statusCode": httpStatusCode, "headers": { "headerName": "headerValue", ... }, "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... }, "body": "..." }
こちらをもとに引数が5つの関数を作成したいと思います
記述する場所はhandler関数の下になります
handler.kt
fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json( "statusCode" to statusCode, "body" to body, "isBase64Encoded" to isBase64Encoded, "headers" to headers, "multiValueHeaders" to multiValueHeaders )
json()
Jsonを作成する関数です/引数はvararg Pair<String, Any?>
となっておりjson()
と書くと空のJsonを作ることができます
Callbackに渡すよう変更する
現在handler関数は下記のようになっていると思います
handler.kt
@JsExport @JsName("handler") fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) { val response: dynamic = object {} response["statusCode"] = 200 response.body = "Hello from Kotlin Lambda!" callback(null, response) }
この関数を先ほど作成した関数を用いた形に変更します
@JsExport @JsName("handler") fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) { val response = responseJson(body = "Hello from Kotlin Json Lambda!") callback(null, response) }
関数をデプロイする
前回と同様の手順を用いて関数をデプロイします
画面右側の Gradle
タブを開き, kotlin_lambda -> Tasks -> other
の順に開き, その中の compress
をダブルクリックして実行します
ビルドに成功するとプロジェクトのディレクトリに compress.zip
というファイルが更新されます
この生成されたファイルをコンソールからアップロードし, 保存します
動作確認
あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Json Lambda!
と表示されれば成功です
レスポンスをクラスに変更する
レスポンスはクラスを用いても影響が少ないためクラスに置き換えます
リクエストにクラスを利用しない理由については蛇足をご覧ください
まずは responseJson関数
を変更します
handler.kt
fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json( "statusCode" to statusCode, "body" to body, "isBase64Encoded" to isBase64Encoded, "headers" to headers, "multiValueHeaders" to multiValueHeaders )
↓
data class Response( val statusCode: Int = 200, val body: String = "{}", val isBase64Encoded: Boolean = false, val headers: Json = json(), val multiValueHeaders: Json = json() )
続いて, Callbackの引数を変更します
fun handler(event: dynamic, context: dynamic, callback: (Error?, Json?) -> Unit)
↓
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit)
最後にレスポンスを変更します
@JsExport @JsName("handler") fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) { val response = responseJson(body = "Hello from Kotlin Json Lambda!") callback(null, response) }
↓
@JsExport @JsName("handler") fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) { val response = Response(body = "Hello from Kotlin Class Lambda!") callback(null, response) }
この変更ができたらデプロイし動作を確認します
Hello from Kotlin Class Lambda!
と表示されれば成功です
最後に
いかがでしたでしょうか
Kotlinを用いていると dynamic
では少し取り扱いにくく感じてしまいます
しかし Json
にしてしまうことで,Mapのように利用でき, より使いやすいのではないかと思います
次回は非同期処理にする記事を書きたいと思います
ここまで読んでいただきありございました
Kotlin/JSのAWS Lambda関数でPromiseを使うようにする
蛇足
リクエストのほうもクラス使ったらだめなの?
注意事項はありますが、クラスを利用しても問題はないです
handler.kt
package jp.co.seeds_std.lambda.kotlin import kotlin.js.Json import kotlin.js.json @JsExport @JsName("handler") fun handler(event: Event, context: Json, callback: (Error?, Response?) -> Unit) { println("body: ${event.body} isBase64Encoded: ${event.isBase64Encoded}") // event.copy() // Error! val response = Response(body = "Hello from Kotlin Json Lambda!") callback(null, response) } data class Event(val body: String?, val isBase64Encoded: Boolean) data class Response( val statusCode: Int = 200, val body: String = "{}", val isBase64Encoded: Boolean = false, val headers: Json = json(), val multiValueHeaders: Json = json() )
この時ログに body
と isBase64Encoded
の出力が行われます
その後 copy()
を実行しようとした場合, 変数 event
は Eventクラス
ではなく Json
となっていますので, 関数が見つからず実行時にエラーが出てしまいます
また, 注意点として private
な変数はトランスパイルした際に変数名が変更されてしまうためうまく利用できません
そのため, リクエストの event
と context
はJsonで, レスポンスはKotlinのクラスを利用していくことがいいのかなと私は思います
蛇足も読んでいただきありございました