Spring Cloud FunctionでKotlinのServerlessアプリケーションを作る

# Spring Cloud Functionとは？

Spring Cloud Functionは、Springプロジェクトによって提供されている関数型プログラミングモデルを利用した**Serverless対応フレームワーク**です。
Spring BootアプリケーションをAWS LambdaやAzure Functions、Google Cloud Functionsなどに簡単にデプロイできます。

# KotlessというJetBrainsのフレームワークもあるが・・・

Kotlin対応のServerlessフレームワークとしては[Kotless](https://github.com/JetBrains/kotless)もありますが、最終commitが2022年3月28日です。
以前は私も注目していて使ってみたこともあるのですが、残念ながら更新されていないようです。

https://blog.takehata-engineer.com/entry/deploy-kotlin-applications-to-aws-lambda-using-kotless
  
そのため、新たなKotlinでのServerless開発の選択肢として、Spring Cloud Functionを紹介します。

# 導入
IntelliJ IDEAからKotlinのGradleプロジェクトを作成し、以下の実装をしていきます。

## Gradleの設定
まず、build.gradle.ktsは以下のようになります。

```kotlin
plugins {
    kotlin("jvm") version "2.1.20"
    kotlin("plugin.spring") version "2.1.20"
    id("com.github.johnrengelman.shadow") version "8.1.1"
    id("org.springframework.boot") version "3.5.0"
    id("io.spring.dependency-management") version "1.1.7"
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(kotlin("test"))
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.springframework.cloud:spring-cloud-function-adapter-aws:4.3.0")
    implementation("org.springframework.cloud:spring-cloud-starter-function-web:4.3.0")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}

tasks {
    shadowJar {
        archiveClassifier.set("aws")
        append("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")

        manifest {
            attributes(
                mapOf(
                    "Main-Class" to "org.springframework.cloud.function.adapter.aws.FunctionInvoker",
                    "Start-Class" to "com.example.Application"
                )
            )
        }
    }

    build {
        dependsOn(shadowJar)
    }
}

tasks.test {
    useJUnitPlatform()
}
kotlin {
    jvmToolchain(21)
}
```

### Spring Cloud Functionの依存関係
まずポイントとしては、Spring Cloud Functionに関する以下の依存関係の追加です。

```kotlin
implementation("org.springframework.cloud:spring-cloud-function-adapter-aws:4.3.0")
implementation("org.springframework.cloud:spring-cloud-starter-function-web:4.3.0")
```

`spring-cloud-function-adapter-aws`はAWS Lambdaでアプリケーションを動かすためのAdapterで、実装したSpringアプリケーションをデプロイ(後述)することで、Lambdaの関数として実行できるようにしてくれます。
`org.springframework.cloud:spring-cloud-starter-function-web`は通常のウェブアプリケーションとして起動するためのモジュールで、local環境でのデバッグ(後述)時に使います。

### Shadowでfat Jarを作る
KotlinやJavaのアプリケーションをLambdaにデプロイするには、fat Jar(アプリケーションとその依存ライブラリを全て1つのJarファイルにまとめたもの)を作り、アップロードする必要があります。
そのために[shadow](https://github.com/GradleUp/shadow)というプラグインを使用し、以下の設定を追加します。

```kotlin
tasks {
    shadowJar {
        archiveClassifier.set("aws")
        append("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")

        manifest {
            attributes(
                mapOf(
                    "Main-Class" to "org.springframework.cloud.function.adapter.aws.FunctionInvoker",
                    "Start-Class" to "com.example.Application",
                )
            )
        }
    }

    build {
        dependsOn(shadowJar)
    }
}
```

`Main-Class`には`FunctionInvoker`というSpring Cloud Functionのクラスを指定する必要があります。
`Start-Class`には、`@SpringBootApplication`を付けているエントリポイントとなるクラス(後述)を指定します。

## アプリケーションの実装
実行する関数として、以下のクラスを作成します。

```kotlin
package com.example

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.function.Function

data class Request(val name: String)
data class Response(val message: String)

@Configuration
class DemoHandler {
    @Bean
    fun hello(): Function<Request, Response> = Function { request ->
        Response("Hello, ${request.name}!")
    }
}
```

requestで文字列を受け取り、その値を埋め込んだ文字列をresponseとして返すだけの処理です。
関数の戻り値の型を`java.util.function.Function`で定義します。

そして以下のエントリポイントを作成します。

```kotlin
package com.example

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}
```

Lambdaの関数として実行するだけであればmain関数の引数と処理は空でも問題ありませんが、localで実行して確認するために、通常のSpring Bootのアプリケーションと同じように`runApplication`を使用して定義しています。

## local環境での動作確認
Spring Cloud Functionでは、`spring-cloud-starter-function-web` を使うことで、HTTPサーバー経由で実行が可能です。
これにより、Lambdaにデプロイせずとも関数の挙動を確認できます。

### 1. ローカルHTTPサーバー起動
前述のエントリポイントを作成していることで、`./gradlew bootRun` でアプリケーションを起動し、HTTP経由で関数呼び出しができるようになります。

### 2. curlで関数を実行
デフォルトではポート8080でサーバが起動します。
例えば `hello` 関数をテストするには以下のようにPOSTします。

```bash
curl -X POST http://localhost:8080/hello \
  -H "Content-Type: application/json" \
  -d '{ "name": "Kotlin" }'
```

#### レスポンス例：

```json
{
  "message": "Hello, Kotlin!"
}
```

## AWS Lambdaでのデプロイ
localで関数の処理は確認できたので、いよいよAWS Lambdaにデプロイします。

### 1. `./gradlew build` でビルド
`build/libs/xxx(プロジェクト名)-aws.jar`というファイルができます。
いくつかjarファイルができると思いますが、awsというsuffixがついているものが、今回のデプロイで使うjarになります。
前述のbuild.gradle.ktsの`archiveClassifier.set("aws")`という部分でこのsuffixを指定しています。

### 2. `build/libs/xxx-aws.jar` をAWS Lambda関数にアップロード
以下を参考にしてください。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-package.html

ログインし、マネジメントコンソールから関数を作成し、今回ビルドしたjarをアップロードします。

### 3. Handlerの指定
アップロード後、Runtime settingsのHandlerに以下を設定します。

```kotlin
org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest
```

設定後の参考例は以下になります。

![スクリーンショット 2025-06-22 17.51.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94005/8fb8b419-a431-450b-ab4d-e79740370d11.png)

## テストの実行
### 1. AWS Lambda コンソールを開く

マネジメントコンソールで当該ののLambda関数を選択します。

### 2. 「Test」タブを開く

関数の画面で上部にある「**Test**」タブを押します。

### 3. テストイベントを作成

初回の場合、**テストイベントの作成画面**が表示されます。

- Template：`Hello World` を選んでもOK
- Event JSONを以下に変更：

```json
{
  "name": "Kotlin"
}
```

これは `Request(name: String)` 型のリクエストに対応しています。
Testの設定の参考例は以下です。

![スクリーンショット 2025-06-22 15.37.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94005/3e5b6413-e98d-49b2-aa9a-f7da908db385.png)

### 4. テスト実行

「**Test**」ボタンを押して実行すると、Spring Cloud Function が呼び出され、結果が表示されます。

### 5. レスポンス例（成功時）

```json
{
  "message": "Hello, Kotlin!"
}
```

以下のようにログが表示されます。

![スクリーンショット 2025-06-22 17.58.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94005/9bcebad6-deeb-4782-977c-860b363b237e.png)

# まとめ

* Spring Cloud Functionを使えば、KotlinでServerlessアプリケーションの関数が簡単に書ける
* 各種Adapterのモジュールで、AWS Lambdaなどのマネジメントサービスにもかんたデプロイできる
* Spring Frameworkの実装に慣れている人には、学習コストも低く導入できる

KotlinでServerlessアプリケーションを開発したい方にとって、**Spring Cloud Functionは一つの有力な選択肢**になるのではないかと思います。