WEB事業部の西村です。
唐突ですが皆さんはLambda関数を作成する際にどのような言語を用いていますでしょうか?
用途に合った言語や自分で書きやすいと思う言語などの要因で決めていると思われます
私はKotlinという言語が好きでKotlinで書こうと思ったのですが、Kotlinで書かれている記事はそのほとんどがJavaで実行することを前提とした元なっています
しかし、Javaではコールドスタートした際に実行時間がかかってしまうというデメリットがあります
KotlinはJavaだけでなくJavaScriptでも動作させることができるので今回はNode.jsで実行させる関数の作成を行いたいと思います
目次
この記事の目標
Kotlin/JSを用いたLambda関数の作成とそのレスポンス確認
この記事で取り扱わないこと
この記事では AWS Lambda と Amazon API Gateway については詳しく解説いたしません
あらかじめLambda関数とその関数を呼び出すためのAPIの作成を行っておいてください
開発環境
この記事では下記の環境で開発を行っています
- AdoptOpenJDK 1.8.0_222-b10
- IntelliJ IDEA Community 2019.2.2
- Kotlin 1.3.50
プロジェクトの作成
プロジェクトの作成を行います
任意のディレクトリにプロジェクト用のフォルダを作成します(この記事では KotlinLambda としています)
その後そのディレクトリ内に build.gradle.kts と settings.gradle.kts の2つのファイルを作成します
1KotlinLambda
2├─build.gradle.kts
3└─settings.gradle.kts
作成したらIntelliJ IDEAでプロジェクトのフォルダを開きます
続いて、 build.gradle.kts と settings.gradle.kts を開き下記の内容を入力します
build.gradle.kts
1plugins {
2 kotlin("js") version "1.3.50"
3}
4repositories {
5 mavenCentral()
6}
7kotlin {
8 target {
9 nodejs()
10 useCommonJs()
11 }
12 sourceSets["main"].dependencies {
13 implementation(kotlin("stdlib-js"))
14 }
15}
settings.gradle.kts
1rootProject.name = "kotlin_lambda"
その後画面右下に出ている Gradle projects need to be imported の Import Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら準備完了です
ハンドラソースコードの追加
次にソースディレクトリを作成します
プロジェクトのディレクトリに src/main/kotlin でディレクトリを作成します
またパッケージ名を追加する場合、パッケージを作成します(この記事では jp.co.seeds_std.lambda.kotlin とします)
作成したディレクトリに handler.kt というファイルを作成し下記の内容を記述します
handler.kt
1package jp.co.seeds_std.lambda.kotlin
2
3@JsExport
4@JsName("handler")
5fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit) {
6 val response: dynamic = object {}
7 response["statusCode"] = 200
8 response.body = "Hello from Kotlin Lambda!"
9 callback(null, response)
10}
- @JsExport JavaScriptのexportを行うアノテーションです
- @JsName(name: String) 記述したクラスや関数をトランスパイルした際 name に指定した名前となるよう変換してくれるようになります
- dynamic JavaScriptのオブジェクトをそのまま取り扱います(. [] でその中の関数や変数にアクセスできます)
デプロイ
デプロイパッケージの作成を行います
圧縮を自動化するためのタスクを追加します
build.gradle.kts を再度開き下記の内容を追加します
build.gradle.kts
1import com.google.gson.JsonParser
2import java.io.OutputStream
3import java.util.zip.ZipOutputStream
4import java.util.zip.ZipEntry
5
6plugins { ... }
7repositories { ... }
8kotlin { ... }
9
10open class CompressTask : DefaultTask() {
11 lateinit var buildDirectory: File
12 lateinit var projectName: String
13 var rootProjectName: String? = null
14 private val blackLists = setOf("kotlin-test-nodejs-runner", "kotlin-test")
15
16 @TaskAction
17 fun compress() {
18 val projectPrefix = if(rootProjectName != null && rootProjectName != projectName) "$rootProjectName-" else ""
19 val zipFile = File(buildDirectory, "../compress.zip")
20 val outputStream = ZipOutputStream(zipFile.outputStream(), Charsets.UTF_8).apply {
21 setLevel(9)
22 }
23 val jsDir = File(buildDirectory, "js")
24 val projectDir = File(jsDir, "packages/$projectPrefix$projectName")
25 val nodeModuleDir = File(jsDir, "node_modules")
26 addDependencies(outputStream, File(projectDir, "package.json"), nodeModuleDir, mutableSetOf(*blackLists.toTypedArray()))
27 addZipEntry(outputStream, File(projectDir, "kotlin/$projectPrefix$projectName.js"))
28 outputStream.close()
29 }
30 private fun addZipEntry(zipOutputStream: ZipOutputStream, file: File, addDirectory: String = "") {
31 val name = if(addDirectory.isEmpty()) file.name else "$addDirectory/${file.name}"
32 if(file.isDirectory) {
33 file.listFiles()?.forEach {
34 addZipEntry(zipOutputStream, it, name)
35 }
36 } else {
37 val zipEntry = ZipEntry(name)
38 zipOutputStream.addZipEntry(zipEntry) {
39 it.writeFile(file)
40 }
41 }
42 }
43 private fun addDependencies(zipOutputStream: ZipOutputStream, packageJsonFile: File, nodeModuleDir: File, addedDependencies: MutableSet<String> = mutableSetOf()) {
44 val packageJson = JsonParser().parse(packageJsonFile.reader()).asJsonObject
45 if(!packageJson.has("dependencies")) return
46 val dependencies = packageJson.getAsJsonObject("dependencies")
47 dependencies.keySet().forEach {
48 if(!addedDependencies.contains(it)) {
49 addedDependencies.add(it)
50 val dependenciesDir = File(nodeModuleDir, it)
51 addZipEntry(zipOutputStream, dependenciesDir, "node_modules")
52 addDependencies(zipOutputStream, File(dependenciesDir, "package.json"), nodeModuleDir, addedDependencies)
53 }
54 }
55 }
56 private inline fun ZipOutputStream.addZipEntry(entry: ZipEntry, crossinline block: (OutputStream) -> Unit) {
57 try {
58 putNextEntry(entry)
59 block(this)
60 } catch (e: Exception) {
61 e.printStackTrace()
62 } finally {
63 closeEntry()
64 }
65 }
66 @Suppress("NOTHING_TO_INLINE")
67 private inline fun OutputStream.writeFile(file: File) {
68 file.inputStream().use {
69 it.copyTo(this)
70 }
71 }
72}
73tasks.register("noMainCall") {
74 doFirst {
75 kotlin.target.compilations.all { compileKotlinTask.kotlinOptions.main = "noCall" }
76 }
77}
78tasks.register<CompressTask>("compress") {
79 dependsOn("compileKotlinJs", "noMainCall")
80 this.buildDirectory = rootProject.buildDir
81 this.projectName = project.name
82 this.rootProjectName = rootProject.name
83}
84tasks["compileKotlinJs"].mustRunAfter("noMainCall")
85
追加後、画面右下に出ている Gradle projects need to be imported の Import Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら画面右側の Gradle タブを開きます
kotlin_lambda -> Tasks -> other の順に開き、その中の compress をダブルクリックして実行します
するとプロジェクトのディレクトリに compress.zip というファイルが生成されます
この生成されたファイルをコンソールからアップロードします
その後の ハンドラ を kotlin_lambda.{パッケージ名}.handler に変更します (この記事では kotlin_lambda.jp.co.seeds_std.lambda.kotlin.handler となります)
アップロードと変更が完了したら保存を行います
動作確認
あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Lambda! と表示されれば成功です
最後に
いかがでしたでしょうか
Javaの時より動作も軽くなったと感じるのではないかと思います
次回はもう少し使いやすくなるような記事も書きたいと思います
ここまで読んでいただきありございました