AWS
Kotlin
sam
lambda
APIGateway

sam-cliを使ってKotlinでlambdaを書いた話


概要

最近流行りのsam-cliを使って、Kotlinでlambdaを書いてみました。

今回書いたのは簡単なGETとPOSTの処理になります。

完成物はこちら


環境


  • Kotlin1.3.20

  • Docker for Mac

  • SAM-CLI


開発


開発環境構築


sam-cliのインストール〜パッケージ作成


  1. brew tap aws/tap

  2. brew install aws-sam-cli

  3. sam init --runtime java8


mavenにKotlinの依存関係等の追記


  1. dependenciesに以下のライブラリを追加します。


    • kotlin-stdlib-jdk8

    • kotlin-test-junit



  2. pluginに以下のプラグインを追記します。


    • maven-shade-plugin

    • kotlin-maven-plugin

    • maven-compiler-plugin



  3. sourceDirectory, testSourceDirectoryのpathをsrc/main/kotlin, src/test/kotlinに変更する。

完成品はこちら


javaのソースをKotlinに変換

こちらはintellijを使っていれば、自動でjavaからKotlinに変更することができます。


最初の動作確認

$ mvn package

$ sam local start-api

これでlocal環境でlambdaが立ち上がり、localhostでアクセスできるようになるのでまずは動くことを確認します。

最初は localhost:3000/helloHello World になるはずです。


POST用のlambdaの作成

今回は新たにリクエストボディに人の名前を詰めると、レスポンスにそれを含めてHello Worldを返すAPIを作成します。


受け取るRequestのclassの作成

今回はbodyに以下のようなJsonを渡します。



{
"firstName": "太郎",
"lastName": "山田"
}

次にリクエストを受け取るためのクラスを作成します。

ターミナルにて、以下のコマンドを実行するとlambdaが受け取るeventのjsonが取得できるので、それに沿ってクラスを作ります。

$ sam local generate-event apigateway aws-proxy --method POST



{
"body": "eyJ0ZXN0IjoiYm9keSJ9",
"resource": "/{proxy+}",
"path": "/path/to/resource",
"httpMethod": "POST",
"isBase64Encoded": true,
"queryStringParameters": {
"foo": "bar"
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"requestTime": "09/Apr/2015:12:34:56 +0000",
"requestTimeEpoch": 1428582896000,
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"accessKey": null,
"sourceIp": "127.0.0.1",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Custom User Agent String",
"user": null
},
"path": "/prod/path/to/resource",
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "1234567890",
"protocol": "HTTP/1.1"
}
}

続いてこのJsonに対応するクラスを作っていきます。

class Request {

lateinit var body: String |
}

data class Person(val firstName: String, val lastName: String)

Requestクラスの方は、 RequestHandler を継承したクラスでlambdaの関数を作成する場合、空のコンストラクタがないとパースできないので、今回は通常のclassにしています。

また、リクエストボディに関しては、Jsonではなく文字列で受け取るので、RequestクラスではString型で定義します。

今回はbody以外使わないので、classにはbody以外持たせていません。


lambdaの関数の実装

まずはJsonをパースするために jackson-module-kotlin をpom.xmlのdependencyに追記します。

続いていよいよ関数を実装していきます。

基本的な流れは自動生成されるgetのものと同様ですが、最初にリクエストボディをパースします。

実際のコードは以下の通りです。


class PostApp : RequestHandler<Request, GatewayResponse> {

override fun handleRequest(input: Request, context: Context?): GatewayResponse {
val headers = HashMap<String, String>()
headers["Content-Type"] = "application/json"
headers["X-Custom-Header"] = "application/json"

val mapper = jacksonObjectMapper()
val person = mapper.readValue<Person>(input.body)
return try {
val pageContents = this.getPageContents("https://checkip.amazonaws.com")
val output = String.format("{ \"message\": \"hello %s\", \"location\": \"%s\" }", person.lastName, pageContents)
GatewayResponse(output, headers, 200)
} catch (e: IOException) {
GatewayResponse("{}", headers, 500)
}
}

@Throws(IOException::class)
private fun getPageContents(address: String): String {
val url = URL(address)
BufferedReader(InputStreamReader(url.openStream())).use { br -> return br.lines().collect(Collectors.joining(System.lineSeparator())) }
}
}


動作確認

$ mvn package

$ sam local start-api

これらを実行し、今回自分で作成したlambdaのエンドポイントを叩いてみます。

$ curl -X POST -d '{"firstName": "山田", lastName: "太郎"}' https://127.0.0.1:3000

これで期待したレスポンスが返ってくれば成功です!


最後に

「javaでかけるんだからKotlinでもかけるっしょ」の精神で書いてみました。

ただ書いてみて思うのは、あえてKotlinで書く必要性がそこまでなかったのではないだろうか。。。というものでした。

個人の趣味とか、会社で使うlambdaの一つをお試しで、とかなら良いかもしれません。

以上でsam-cliを使ってKotlinでlambdaを書いた話は終わりになります。

ありがとうございました。