# Guide to Super Fast Kotlin on AWS Lambda

### Overview
  * In cloud computing, serverless functions have become an integral part of the backend architecture. One downside in the **JVM** ecosystem has been the slow **Cold Start** issue, which has led to its exclusion from the choice of cloud serverless function execution, including **AWS Lambda**. However, in recent years, there have been significant changes and developments to dramatically improve this. **Azul** released the `CRaC API`, which, in collaboration with **AWS**, eventually evolved into the `AWS Lambda SnapStart` feature. Also, the emergence of lightweight and fast toolkits like `http4k`, a robust alternative to the traditionally heavy **Spring Boot** for function deployment, along with the advent of `GraalVM` images, has begun to fundamentally address the issue of **Cold Start**.
  * This article is inspired by [Faster Kotlin APIs on AWS Lambda](https://betterprogramming.pub/faster-kotlin-apis-on-aws-lambda-8694649bf9dd) by **Andrew O'Hara** (a contributor to **http4k**), and summarizes how to write and deploy a super-fast **AWS Lambda Function** using **Kotlin** based on **JVM 21** runtime.

### Strategies
  * Apply every possible technology and tuning to reduce the `Cold Start` delay, a chronic issue of the **JVM**, without compromising on development convenience.
  * Build using the ultra-lightweight `http4k` toolkit, optimized for serverless computing, instead of the heavy **Spring Boot** framework.
  * Activate `SnapStart` feature and enable the **C1** compiler by adding the `JAVA_TOOL_OPTIONS=-XX:+TieredCompilation -XX:TieredStopAtLevel=1` option.
  * Find a compromise between billing and minimal latency to set an appropriate memory size.

### Project Creation
  * Launch **IntelliJ IDEA** and create a new **Kotlin** project as follows.

```kotlin
Launch IntelliJ IDEA
→ New → Project
→ Name: hello-lambda (enter any project name)
→ Language: [Kotlin] selected
→ Build system: [Gradle] selected
→ Gradle DSL: [Groovy] selected
→ [Create]
```

### build.gradle
  * Replace the **build.gradle** file in the project root with the following content.

```kotlin
buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22"
        classpath "com.github.johnrengelman:shadow:8.1.1"
    }
}

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.9.22'
    id 'application'
    id "com.github.johnrengelman.shadow" version "8.1.1"
}

mainClassName = "com.example.HelloWorldKt"
group = 'org.example'
version = '1.0-SNAPSHOT'
compileKotlin.kotlinOptions.jvmTarget = "21"

tasks.register('buildLambdaZip', Zip) {
    from compileKotlin
    from processResources
    into('lib') {
        from configurations.compileClasspath
    }
}

shadowJar {
    manifest.attributes["Main-Class"] = mainClassName
    archiveBaseName.set(project.name)
    archiveClassifier.set(null)
    archiveVersion.set(null)
    mergeServiceFiles()
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
    implementation("org.http4k:http4k-aws:5.12.1.0")
    implementation("org.http4k:http4k-core:5.12.1.0")
    implementation("org.http4k:http4k-format-jackson:5.12.1.0")
    implementation("org.http4k:http4k-serverless-core:5.12.1.0")
    implementation("org.http4k:http4k-serverless-lambda:5.12.1.0")
    implementation("org.http4k:http4k-serverless-lambda-runtime:5.12.1.0")
    implementation("com.amazonaws:aws-lambda-java-events:3.11.4")
}

test {
    useJUnitPlatform()
}

kotlin {
    jvmToolchain(21)
}
```

### Implementing AwsLambdaEventFunction
  * Below is an example of implementing an **AwsLambdaEventFunction** that receives and processes events triggered from **Amazon S3**. (**http4k** also provides an `ApiGatewayV1LambdaFunction` interface capable of handling requests from **Amazon API Gateway**.)

```kotlin
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.events.S3Event
import org.http4k.client.JavaHttpClient
import org.http4k.core.HttpHandler
import org.http4k.core.Response
import org.http4k.core.Status
import org.http4k.server.SunHttp
import org.http4k.server.asServer
import org.http4k.serverless.AwsLambdaEventFunction
import org.http4k.serverless.FnHandler
import org.http4k.serverless.FnLoader

fun eventFnHandler(http: HttpHandler) =

    // Handler to process events triggered from Amazon S3
    FnHandler { s3Event: S3Event, context: Context ->

        // [1] Analyze the S3Event object passed as the input of the Function and write the necessary work
        s3Event.records.forEach {
            // [Example] Sending received S3 event information to a specific webhook URL
            http(
                Request(Method.GET, "{webhook-url}")
                    .query("eventSource", it.eventSource) // aws:s3
                    .query("eventName", it.eventName) // ObjectCreated:Put
                    .query("eventTime", it.eventTime.toString()) // 1970-01-01T00:00:00.000Z
                    .query("bucket", it.s3.bucket.name) // foo
                    .query("key", it.s3.`object`.key) // bar
                    .query("size", it.s3.`object`.sizeAsLong.toString()) // 65535
            )
        }

        // [2] Return the output of the Function after completing the work
        "Hello World"
    }

fun eventFnLoader(http: HttpHandler) = FnLoader { _: Map<String, String> ->
    eventFnHandler(http)
}

// Not meaningful for execution in a local environment
val httpServer: HttpHandler = {
    Response(Status.OK).body("Hello World")
}

// Not meaningful for execution in a local environment
fun main() {
    httpServer
        .asServer(SunHttp(8080))
        .start()
        .block()
}

// [0] Entry point when the event is triggered
class HelloAwsLambdaEventFunction : AwsLambdaEventFunction(eventFnLoader(JavaHttpClient()))
```

### Building AWS Lambda Function
  * Build the written project into a **.zip** file as follows.

```bash
$ ./gradlew buildLambdaZip
```

### Creating and Deploying AWS Lambda Function
  * Create and deploy a lambda function based on **Java 21** runtime using the previously built **.zip** file.

```bash
# Create a new Lambda Function and deploy the .zip file
$ aws lambda create-function \
  --function-name helloLambda \
  --runtime "java21" \
  --environment "Variables={JAVA_TOOL_OPTIONS=-XX:+TieredCompilation -XX:TieredStopAtLevel=1}" \
  --zip-file fileb://build/distributions/hello-lambda-1.0-SNAPSHOT.zip \
  --handler com.example.HelloAwsLambdaEventFunction::handleRequest \
  --role {role-arn} \
  --memory-size 2048 \
  --snap-start ApplyOn=PublishedVersions \
  --publish

# Redeploy only the .zip file to an existing Lambda Function
$ aws lambda update-function-code \
  --function-name helloLambda \
  --zip-file fileb://build/distributions/hello-lambda-1.0-SNAPSHOT.zip \
  --publish
```

### Invoking AWS Lambda Function
  * Invoke the deployed **Lambda Function** as below to confirm its proper operation.

```bash
$ aws lambda invoke --function-name helloLambda --cli-binary-format raw-in-base64-out --payload file://input.json output.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
 
$ cat output.json
Hello World
```

### Final Thoughts
  * The final cold start time for a dummy function, after using the lightweight module for **AWS Lambda** from the **http4k** toolkit, activating **SnapStart**, optimizing **JVM C1** environment variables, and setting the memory to **2048 MB**, is **100.36ms**, and the warm start time is **1.43ms**. This is a significant improvement compared to the initial startup time of traditional **Spring Boot**-based applications. Now, I feel confident in writing **Kotlin**-based **AWS Lambda Functions**.

### References and Further Reading
  * [Faster Kotlin APIs on AWS Lambda](https://betterprogramming.pub/faster-kotlin-apis-on-aws-lambda-8694649bf9dd)
  * [My Reddit Post - What are the best practices for writing AWS Lambda functions in Kotlin?](https://www.reddit.com/r/Kotlin/comments/193vf1q/what_are_the_best_practices_for_writing_aws/)
  * [Kotlin on AWS Lambda](https://medium.com/postnl-engineering/kotlin-on-aws-lambda-ae5753937ae2)
  * [http4k - Serverless backend Installation (Gradle)](https://www.http4k.org/guide/reference/serverless/)
  * [http4k - Deploying an http4k app to AWS Lambda](https://www.http4k.org/guide/tutorials/serverless_http4k_with_aws_lambda/)
  * [AWS - Optimizing AWS Lambda function performance for Java](https://aws.amazon.com/ko/blogs/compute/optimizing-aws-lambda-function-performance-for-java/)
  * [AWS - Activating and managing Lambda SnapStart](https://docs.aws.amazon.com/lambda/latest/dg/snapstart-activate.html)

