Kotlin/Native for AWS Lambda

Posted on 2019-04-05
7 mins to read

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)!

But first, let’s figure out what should be done.
How do runtimes work?

A runtime’s job is to:

Execute the function’s initialization logic.
In the case of Java, it means starting the JVM, loading the classes and running static initializers.

Locate the handler passed through the “Handler” configuration parameter.
For Java-based Lambdas, it’s either an FCDN of a class, like some.package.Handler, or a reference to a method, like some.package.Handler::method.

Execute the handler for each incoming event.

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

Basically, when you author a Lambda function using one of the supported runtimes, like Java, Nodejs or Go, you are concentrated on the event processing loop in the center.
The runtime handles the initialization and passes the events directly to your handler in the form of objects or structs (the naming depends on the programming language).

In the case of a custom runtime, it’s all your job.

A custom runtime is just an executable file named bootstrap in your function’s deployment package that is used as an entry point.
The file can be included in your the deployment package directly, or in a layer.
AWS Lambda executes it with the configuration passed via environment variables.
The bootstrap should initialize the required resources and enter the event processing loop.
AWS Lambda provides an HTTP API for custom runtimes to receive the events and send the responses back.
Your custom runtime should call this API in a loop and fetch the events.
For each event, it could either invoke a handler or processes it on its own.

Receive next event and dump it’s headers to a temporary file created on the previous line.

Parse the headers to find the request id.

Log the event to STDOUT.

Respond to the event using the request id.

There is no initialization or clean-up here and there is no external handler: it’s a very basic function and the events are processed right in bootstrap script.
Note the while true loop that polls for events: once this function is started by AWS Lambda it will stay alive and process the requests until Lambda decides to recycle it.
Also note the way how the function interacts with the AWS Lambda: by calling HTTP API with curl.

Now, when you know the basics of custom runtimes, let’s implement echo Lambda function in Kotlin/Native.

First, set up the project.
Latest IntelliJ IDEA supports Kotlin/Native… natively, so it can be done with “File” → “New” → “Project…” → “Kotlin” → “Kotlin/Native”:

IDEA will create a Gradle project with Kotlin Multiplatform plugin and native target.

In order for Kotlin/Native dependencies to work properly in Gradle, you need to enable GRADLE_METADATA feature.
Furthermore, kotlinx.serialization’s plugin is not published on Gradle Plugin Portal (yet), so let’s use some black magic to tell it how to find the plugin.
Both tweaks should go to the settings.gradle.kts, so here is it:

Use org.jetbrains.kotlin:kotlin-serialization:1.3.21 as Gradle plugin.
The resolution rule comes from pluginManagement.resolutionStrategy in settings.gradle.kts configured previously.
One day, the plugin will be published officially and these lines could be removed.
I recommend applying plugins via plugins block versus apply so that Kotlin extension functions are made available to configure them.

kotlinx.serialization is not published in JCenter (yet), so we need that additional repository.

Kotlin Multiplatform Project configuration block.

We configure single Linux x64 binary as it is the platform used by AWS Lambda.
Other supported platforms include Android ARM, iOS ARM and x64, other Linux variants, Windows, Mac OS and Web Assembly.

As long as we’re building an executable file, we need to say that to Gradle.
Other options are dynamic and static libraries and Objective-C frameworks.

As with good old Gradle’s application plugin, we need to specify the entry point.

Just a trick to tell Gradle about the existing configuration and avoid using string literals within the dependencies block.

Finally, the libraries we need: Ktor HTTP client based on cURL and Ktor JSON facilities.

Almost done.
We’ll be using two data classes to model input and output of a Lambda function for AWS Lambda Proxy Integration: