この記事は AWS LambdaとServerless Advent Calendar 2021 の14日目の記事です。

Kotless

サーバーレスAPIの開発をKotlinで実現するフレームワークです。JetbrainsのIncubator Projectとして開発がされています。
今回はAWS環境を前提としていますが、Azureの対応もされています。
また、Gradleのプラグインが提供されており、デプロイやローカル実行が可能になっています。デプロイは内部的にTerraformを利用しています。

開始前の準備

Gradleは7.2以降が推奨となっています。
またAWSの各サービスについての解説は本記事では解説しませんのでご容赦ください。
Kotlessを使って初めてAWS環境を使う場合はJetBrainsのHadiが解説しているブログがありますので、そちらを参照ください。

kotlessのはじめかた

まずはGradleのプロジェクトを作成し、必要な情報を設定します。

build.gradle.kts に以下を追加します。
repositories {
    mavenCentral()
    maven(url = uri("https://packages.jetbrains.team/maven/p/ktls/maven"))
}

dependencies {
    implementation(kotlin("stdlib"))
    implementation("io.kotless", "kotless-lang", "0.2.0")
    implementation("io.kotless", "kotless-lang-aws", "0.2.0")
    implementation("com.amazonaws", "aws-java-sdk-dynamodb", "1.11.650")
}
settings.gradle.kts にgradle pluginの設定をします。
pluginManagement {
    resolutionStrategy {
        this.eachPlugin {
            if (requested.id.id == "io.kotless") {
                useModule("io.kotless:gradle:${this.requested.version}")
            }
        }
    }

    repositories {
        maven(url = uri("https://packages.jetbrains.team/maven/p/ktls/maven"))
        gradlePluginPortal()
        mavenCentral()
    }
}
また、ローカル実行用にlocalstack, testcontainers/ryukのインストールが必要です。
#localstack
docker pull localstack/localstack
# ryuk
docker pull testcontainers/ryuk:0.3.0
これで準備ができました。

APIの開発

まずはシンプルなレスポンスを返すエンドポイントを作成していきます。
Kotlessでは関数を作成し、Annocationを設定することで処理関数とルーティングの設定を行います。
@Get("/welcome")
fun welcomeMessage(): String {
    return "Welcome to Kotless API:D"
}
関数のマッピング
welcomeMessage関数がAWS Lambdaへデプロイされる関数となり, @Get のAnnocationがAPI Gatewayのエンドポイントに /welcome でGetリクエストを受ける設定を行います。
実際にこれらの処理はKotlessのコードからterraformのファイルへとコンパイルされて、クラウド環境へ展開が行われます。
もう一つLambda関数を作成してみましょう。英単語のwordを引数として受けとり、日本語訳のレスポンスを返すAPIを作成してみましょう。
@Get("/translate")
fun translate(word: String): String {
    val result = vacablary.getByWord(word) // DBからデータを取得
    return result
}
関数のマッピング
Lambdaにデプロイされるtranslate関数は引数wordを受け取ります。これはクエリパラメータのwordとマッピングされて引数にマッピングされます。
関数はそれを使って処理を実行し、クライアントへレスポンスを返します。また, @Get("/translate") のAnnocationの設定に従い、API GatewayにPathが追加されます。
AWS環境ではIAMを使ってDB等のリソースアクセス制御を行います。今回のtranslate関数においてもDBにアクセスを行うための権限が必要になります。
Kotlessはこの権限設定も容易に行うことができます。今回はDynamoDBへアクセスをするVacablaryオブジェクトに設定を行います。
@DynamoDBTable(tableName, PermissionLevel.Read)
object Vocabulary {

    private val client: AmazonDynamoDB = AmazonDynamoDBClientBuilder.standard().build()

    fun getByWord(word: String): String? {
        val req = GetItemRequest().withKey(mapOf(
            "en" to AttributeValue().apply { s = word }
        )).withTableName(tableName)

        val res = client.getItem(req).item

        return res?.let { it["ja"]?.s }
    }
}
関数へのDBアクセス権限設定
@DynamoDBTable のAnnocationで指定したテーブルに対して、 PermissionLevel.Read という読み込み権限の設定をしています。
そうすることで、このオブジェクトを利用する関数に必要なIAM権限設定をKotlessが行ってくれます。今回は参照権限のコード例を書きましたが、書き込み権限のみ、読み書きの権限を設定することが可能です。
他にもS3, SQS, SystemsManagerについても権限設定をすることができます。

ローカル実行

Kotlessはlocalstachを使ってローカル環境で動かすことができます。
Gradleのタスクに local が追加されているので以下のコマンドで実行が可能です。
gradlew local
これで localhost:8080 にAPIが起動されて開発時の動作確認等を行うことができます。

まとめ

このようにSpringBoot等の広く利用されているアプリケーションフレームワークと同じ様にサーバーレスAPIを構築することができます。

今回の動作するサンプルコードはGithubに上がっていますので、興味ある方はそちらを動かして確認してみてください。