Write an AWS Lambda Function with Kotlin and Micronaut
In this Kotlin tutorial, you’ll learn how to create a “Talk like a pirate” translator and deploy it to AWS Lambda as a function. By Sergio del Amo.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Write an AWS Lambda Function with Kotlin and Micronaut
20 mins
- Prerequisites
- Writing the Function
- Create a Kotlin App with Gradle
- Code a Pirate Translator
- Test the Pirate Translator
- Create an AWS Lambda RequestHandler
- Gradle Shadow Plugin
- Deploying to AWS Lambda
- Create a Function
- Test Your Function
- Add an API-Gateway Trigger
- Writing the Function with Micronaut
- Where to Go From Here?
Gradle Shadow Plugin
Before deploying to AWS, apply the Gradle Shadow Plugin – a Gradle plugin for collapsing all dependencies and project code into a single Jar file.
Modify the file build.gradle.kts again, this time to add to the plugins
block:
id("com.github.johnrengelman.shadow") version "5.1.0"
Go ahead and sync the project Gradle files one more time for fun! :]
Deploying to AWS Lambda
Now that you have your Kotlin pirate translator project set up, it’s time to deploy it to AWS Lambda! :]
Login to your AWS Console and go to https://console.aws.amazon.com/lambda.
Create a Function
You’ll be taken to the Functions screen:
Click on Create function, and you’ll see the Create function screen:
On this screen:
- Select Author from scratch.
- As Function name enter pirate-translator.
- For Runtime, select Java 8.
- Select Create a new Role with Basic Lambda permissions
- Click on Create Function.
You’ll be taken to your new AWS Lambda function pirate-translator:
Locally in a Terminal, from the project root folder run ./gradlew shadowJar
.
That generates a JAR file build/libs/pirate-translator-all.jar.
Back in AWS, on the pirate-translator screen:
- Upload the JAR file as function package.
- For the Handler, enter:
com.raywenderlich.App::handleRequest
. - Click Save in the upper right.
Test Your Function
Next you’ll create a test event for your function. Select Configure test events from the dropdown:
That will show the Configure test event dialog:
On the dialog:
- For Event name enter
PirateHello
- As content enter:
{"message":"Hello"}
- Click Create.
Now make sure the PirateHello event is selected and click Test. You should then see a successful run of the test:
Congratulations! Your Kotlin Pirate Translator works with AWS Lambda!
Add an API-Gateway Trigger
The next step is to create an AWS API Gateway Trigger. Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale.
You will create an API in front of your AWS Lambda making it transparent to the consumers of the API which technology you used to power your pirate translator.
In the AWS Lambda console, on your pirate-translator screen, click the Add trigger button:
You’ll be taken to the Add trigger screen:
On this screen:
- Choose API Gateway from the first dropdown.
- Select Create a new API.
- Select Open for Security.
- For the API name, leave pirate-translator-API.
- For Deployment stage, leave default.
- Leave the rest with the default values as well and click Add.
You’ll be taken back to the screen for your function:
Click the API name to go to the Amazon API Gateway UI. From here, select the method ANY and in the Actions dropdown select Delete Method. Then confirm the delete.
Open the Actions dropdown and select Create Method:
Select POST
and click the checkmark:
Choose Integration type Lambda Function. Enter pirate-translator for the function name. Type p
and you should be able to select using auto-completion. Then click Save in the lower right, and confirm on the dialog that appears.
Then click Test:
Enter the Headers:
Accept:application/json Content-Type:application/json
And for the Request Body use:
{"message": "hello"}
Then click Test and you will see the pirate translation as the Response Body. Aye!
Now open the Actions dropdown again and choose Deploy API.
Select default as the Deployment stage, and click Deploy:
Once, it is deployed you will get a Invoke URL which you can exercise with a cURL command in Terminal:
$ curl -X "POST" "https://cd9z9e3dgi.execute-api.us-east-1.amazonaws.com/default/pirate-translator" -H 'Accept: application/json' -H 'Content-Type: application/json' -d $'{"message": "Hello"}'
(Be sure to substitute in your Invoke URL.)
You will get a response like this:
{"message":"Hello","pirateMessage":"Ahoy!"}
Congratulations! You have created an API in front of an AWS Lambda function written with Kotlin!
Writing the Function with Micronaut
Micronaut is a a modern, JVM-based, full-stack framework for building modular, easily testable microservice and serverless applications. In this section, you’ll see how to create the same pirate translator using Micronaut.
The easiest way to install Micronaut is via SDKMan:
$ sdk install micronaut
Or instead by using Homebrew:
$ brew install micronaut
Create a new directory named pirate-translator-mn. In that directory run:
$ mn create-function com.raywenderlich.app --inplace --lang=kotlin --test=spek
Like before, you can open the project in IntelliJ IDEA 2019.2 or later.
Copy the files PirateTranslator.kt, DefaultPirateTranslator.kt and HandlerOutput.kt, HandlerInput.kt from the previous project to src/main/kotlin/com/raywenderlich in the Micronaut project.
For the Micronaut version of the project, the HandlerInput
can be a data class. Replace the content of HandlerInput.kt with:
package com.raywenderlich
data class HandlerInput(val message: String)
Replace the contents of src/main/kotlin/com/raywenderlich/AppFunction.kt with :
package com.raywenderlich;
import io.micronaut.function.executor.FunctionInitializer
import io.micronaut.function.FunctionBean;
import java.util.function.Function;
@FunctionBean("app")
// 1
class AppFunction : FunctionInitializer(), Function<HandlerInput, HandlerOutput> {
val translator : PirateTranslator = DefaultPirateTranslator()
override fun apply(input: HandlerInput): HandlerOutput {
return HandlerOutput(input.message, translator.translate(input.message))
}
}
/**
* This main method allows running the function as a CLI application using: echo '{}' | java -jar function.jar
* where the argument to echo is the JSON to be parsed.
*/
fun main(args : Array<String>) {
val function = AppFunction()
function.run(args, { context -> function.apply(context.get(HandlerInput::class.java))})
}
The code is almost identical to the previous project, but instead of implementing com.amazonaws.services.lambda.runtime.RequestHandler
, you implement java.util.function.Function
. That will allow you to swap a serverless provider such as AWS Lambda with another serverless provider easily.
Delete src/main/kotlin/com/raywenderlich/App.kt from the project, since you use HandlerInput
and HandlerOutput
.
Replace the contents of src/main/test/kotlin/com/raywenderlich/AppClient.kt with:
package com.raywenderlich
import io.micronaut.function.client.FunctionClient
import io.micronaut.http.annotation.Body
import io.reactivex.Single
import javax.inject.Named
// 1
@FunctionClient
interface AppClient {
// 2
@Named("app")
// 3
fun apply(@Body body : HandlerInput): Single<HandlerOutput>
}
This code does the following:
- Applying the
@FunctionClient
annotation means that methods defined by the interface become invokers of remote or local functions configured by the application. - The value of
@Named
must match the value of the@FunctionBean
annotation in theAppFunction
class. - You define a method returning a ReactiveX
Single
type. Micronaut implements the method for you.
Replace the contents of src/main/test/kotlin/com/raywenderlich/AppFunctionTest.kt with:
package com.raywenderlich
import io.micronaut.context.ApplicationContext
import io.micronaut.runtime.server.EmbeddedServer
import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.describe
import org.jetbrains.spek.api.dsl.it
import org.junit.jupiter.api.Assertions.assertEquals
// 1
class AppFunctionTest: Spek({
describe("app function") {
// 2
val server = ApplicationContext.run(EmbeddedServer::class.java)
// 3
val client = server.applicationContext.getBean(AppClient::class.java)
it("should return 'Ahoy!'") {
val body = HandlerInput("Hello")
// 4
assertEquals("Ahoy!", client.apply(body).blockingGet().pirateMessage
)
}
afterGroup {
server.stop()
}
}
})
Here you do the following:
- Test using Spek– a specification framework that allows you to easily define specifications in a clear, understandable, human readable way.
- Start a Netty embedded server inside your test.
- Retrieve the declarative client to consume the function.
- Since the client returns a blocking type, you call
blockingGet
to obtain an answer.
Generate another shadow JAR with ./gradlew shadowJar
from the pirate-translator-mn root.
Now you need to update your AWS Lambda function to use the Micronaut version of the project. Go back to the AWS Lambda console, and on the screen for your pirate-translator function:
- Upload the JAR
- Change the Handler to io.micronaut.function.aws.MicronautRequestStreamHandler.
- Click Save.
Go ahead and run the same cURL command you ran before as a test.
Congratulations! You have deployed your first Micronaut function written with Kotlin to AWS Lambda.
Micronaut offers a lot of features such as dependency injection, declarative HTTP clients, bean validation, etc., which you have not used in this tutorial but which can greatly improve your performance and code quality when developing real-world serverless functions.