Hleb
Hleb

Reputation: 7371

How to create an AWS Lambda function on Kotlin?

Does AWS Lambda functions provide support for Kotlin language? Currently Lambda function creation wizard don't contains Kotlin option in the runtime dropdown list:

enter image description here

But there are Java 8 and different Node.js versions. Which platform is more appropriate for function written on Kotlin in context of AWS Lambda - JVM or Node.js? And which Kotlin frameworks can I use to write a Lambda function?

Upvotes: 8

Views: 9824

Answers (3)

smac2020
smac2020

Reputation: 10724

Up to today, all developers writing Kotlin code have been using the AWS SDK for Java. Today AWS has released an official SDK for Kotlin. See the details here:

https://aws.amazon.com/blogs/developer/aws-sdk-for-kotlin-alpha-release/

Upvotes: 4

madhead
madhead

Reputation: 33422

Kotlin/JVM

The most natural way to create an AWS Lambda function on Kotlin would be to use Kotlin/JVM and target Java 8 Lambda runtime. Kotlin's interop with Java is really great. JVM Lambda functions should be packed in fat JARs, i.e. JAR files with all the dependencies included. It's easy to do with Gradle:

build.gradle.kts:

plugins {
    kotlin("jvm").version("1.3.41") // (1)
    id("com.github.johnrengelman.shadow").version("5.1.0") // (2)
}

repositories {
    jcenter()
}

dependencies {
    implementation(kotlin("stdlib-jdk8")) // (3)
    implementation("com.amazonaws:aws-lambda-java-core:1.2.0") // (4)
}

  1. Standard Kotlin/JVM plugin that supports building JVM artifacts
  2. Shadow plugin for fat JARs
  3. Kotlin's stdlib is required for runtime. Here we use one compiled with JDK 8.
  4. This artifact provides some interfaces to implement in a Java Lambda functions.

Then just create a handler:

Handler.kt:

class Handler : RequestHandler<Input, Output> { // (1)
    override fun handleRequest(input: Input, context: Context): Output {
        println(input)

        return Output()
    }
}

  1. The library provides you with a generic RequestHandler class. All you have to do is to implement it's single method handleRequest. Input and Output can be your POJOs (POKOs), just make sure they can be serialized and deserialized with Jackson (Lambda's docs doesn't mention it implicitly, but if something fails you'll see Jackson mentions in logs); or primitive types; or some predefined classes.

Take a look at the complete code sample here: it contains a function that could be used with Lambda Proxy Integrations in API Gateway .

That's it for Kotlin/JVM. Very easy.

Kotlin/JS

I am not an expert with Kotlin/JS, but you could target Node.js runtime as well, though it won't be as straightforward as JVM.

The idea will be to compile (transpile) your Kotlin code into JavaScript and deploy it as a usual JavaScript Lambda function (i.e. ZIP).

The handler can look like this:

Handler.kt

import kotlin.js.json

@JsName("handle")
fun handle(input: dynamic, context: dynamic): dynamic {
    println(JSON.stringify(input))

    val result: dynamic = json(
            "statusCode" to 307,
            "headers" to json(
                    "Location" to "https://google.com"
            )
    )

    return result
}

The complete example can be found here. It does the same as Kotlin/JVM Lambda function above (responds with redirects), but it runs on Node.js 10.x runtime.

Project setup is slightly harder for Kotlin/JS: you'll need to package the transpiled JS along with dependencies (node_modules) in a ZIP file. There may be a lot of ways to do that, you can take a look at one of them here.

Bonus: Kotlin/Native!

Amazon announced Lambda Runtime API on AWS re:Invent 2018. It allows developers, among other things, to build Lambda functions using any technology they want via so-called Custom Runtimes. Yes, it’s now possible to author a function on PHP, Perl, Pascal (anybody?) or even Bash (they use it in the docs)!

Custom runtime functions work a little bit differently than usual functions. A runtime’s job is to:

  1. Execute the function’s initialization logic. In the case of Java, it means starting the JVM, loading the classes and running static initializers.
  2. Locate the handler passed through the "Handler" configuration parameter.
  3. Execute the handler for each incoming event.

Here is a picture to help you grasp a function’s lifecycle:

Custom runtime lifecycle

A very basic handler can look like this:

Handler.kt:

fun main() = runBlocking { // (1)
    val client = HttpClient(Curl) // (2)

    while (true) { // (3)
        val invocation = client.call("http://${getenv("AWS_LAMBDA_RUNTIME_API")!!.toKString()}/2018-06-01/runtime/invocation/next") {
            method = HttpMethod.Get
        } // (4)

        // (5)
        val invocationId = invocation.response.headers["Lambda-Runtime-Aws-Request-Id"]

        // (6)
        client.call("http://${getenv("AWS_LAMBDA_RUNTIME_API")!!.toKString()}/2018-06-01/runtime/invocation/$invocationId/response") {
            method = HttpMethod.Post
            body = TextContent(
                    "{\"statusCode\": 307, \"headers\": {\"Location\": \"https://google.com\"}}",
                    ContentType.Application.Json
            )
        }
    }
}
  1. As long as we’ll be using coroutines thanks to Ktor, we need a coroutine scope. The simplest way to acquire one is runBlocking.
  2. Configure the HTTP client using the Curl engine. This is the initialization phase from the picture at the beginning of this post.
  3. Enter the event loop.
  4. Fetch next event to process.
  5. Parse the event. Feel’s better than grep, isn’t it?
  6. Respond by calling AWS's REST API.

As you see, you'll need to write much more code in case of Kotlin/Native. Take a look at a complete example here.


Conclusions

You probably shouldn't look at Kotlin/JS and Kotlin/Native for Lambda functions yet.

Kotlin/JS gives you almost nothing compared to JavaScript and TypeScript but requires more lines of code to achieve the same result. Facing JS's inherent dynamism causes some pain, Kotlin/JS interop IMHO is not as good as in case of Kotlin/JVM. The build is more complex as well as it will require you to configure Gradle and NPM to work together (while JS/TS will require only NPM, and Kotlin/JVM requires only Gradle).

Kotlin/Native requires you to write more code to just handle the events. Plus, you won't be able to easily use any AWS SDKs: JVM libraries will not work with Kotlin/Native (though you could probably use C/C++ SDK which is much more low-level than JVM). The build is more complex as well.

If you're interested in comparing different languages/runtimes for AWS Lambda, take a look at this repo I've linked a few times above. It has Lambdas implemented in Java, Kotlin/JVM, Kotlin/JS, Kotlin/Native, JS, Ruby, Python and Bash. To read more about Kotlin/Native for AWS Lambdas go here. The article also has a comparison of speed of functions in different languages. TLDR:

  1. Kotlin/Native is not very performant. It’s still better that cold-started JVM functions.
  2. Golang is probably the best choice for Lambda functions if you are looking for speed.
  3. Pre-warmed JVM (both Java and Kotlin) functions perform really well.
  4. Kotlin/JS provide good cold start times but pre-warmed scripted functions concede to Golang and JVM.

Upvotes: 14

RyanWilcox
RyanWilcox

Reputation: 13972

In Javaland, if you're putting a Kotlin program on a server you should make it a fat jar, so you can execute it on any JVM without having to worry about explicit support for Kotlin.

Alternatively, if you don't want to pay the JVM startup costs for a AWS Lambda cold start you might look at using the Kotlin targeting the Node.js runtime instead of the JVM. But I'd take almost the exact same approach here: (trans)compile your Kotlin to Javascript on a build server, package up your (now) Javascripts + NPM modules and send that up to Lambda. This may or may not work depending on how much you wrote your code assuming Java packages...

So, I think the answer to your question is two fold:

  1. How much do you care about the time a cold start takes? / how often will you be doing cold starts vs warmer requests?
  2. How much do you depend on Java stuff in your app?

But in both cases you don't need explicit support for Kotlin, just the bytecode that your compile process generates (JVM, or Javascript/Node)

Upvotes: 6

Related Questions