Write an AWS Lambda Function with Kotlin and Micronaut [FREE]
source link: https://www.tuicool.com/articles/qMzUzeY
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Serverless solutions such as AWS Lambda allow developers to run code without thinking about servers. Moreover, they offer you the ability to pay only for the compute time you consume instead of a fixed monthly rate. They are becoming especially suited for scenarios such as mobile phone application back-ends.
In this tutorial, you’ll take the following steps to create an AWS Lambda function using Kotlin:
- Create a Kotlin project using Gradle.
- Add a “Talk like a pirate” translator to the project.
- Build an AWS Lambda RequestHandler.
- Deploy your function to AWS.
- Add an API-Gateway Trigger.
- Re-do the project using the Micronaut framework, and re-deploy.
Prerequisites
In order to follow along with this tutorial step-by-step, you’ll need the following:
- Basic knowledge of Kotlin. If you are new to Kotlin, check out ourKotlin tutorials or the bookKotlin Apprentice.
- An Amazon Web Services (AWS) account.
- JDK 8 installed in your computer.
-
Gradle 5.6.2 or greater
. The easiest way to install Gradle in your computer is to use SDKMan.io
. Once you install SDKMan you can open a Terminal and run
sdk install gradle
. You can also use Homebrew and the commandbrew install gradle
.
You’ll be creating the tutorial project from scratch, and you can download the final project using the Download Materials button at the top or bottom of the tutorial.
Writing the Function
Time to get started creating your AWS Lambda function with Kotlin!
Create a Kotlin App with Gradle
Open a Terminal
, make a new directory named pirate-translator
, cd
into the new empty directory, and type gradle init
. You’ll see the following output:
$ gradle init Select type of project to generate: 1: basic 2: application 3: library 4: Gradle plugin Enter selection (default: basic) [1..4]
Select application, type 2
, and press return
.
Then, gradle will prompt you to choose an implementation language:
Select implementation language: 1: C++ 2: Groovy 3: Java 4: Kotlin 5: Swift
Select Kotlin, type 4
, and press return
.
Then, you’ll be prompted to choose a Build script DSL:
Select build script DSL: 1: Groovy 2: Kotlin Enter selection (default: Kotlin) [1..2] 2
Select Kotlin DSL, type 2
, and press return
.
As project name enter pirate-translator
As source package enter com.raywenderlich
.
Congratulations! You have created a Kotlin app which builds using Gradle.
Code a Pirate Translator
In this section, you are going to create Kotlin classes. You can use any text editor, but I recommend you use IntelliJ IDEA 2019.2 or later (Community or Ultimate) by JetBrains – the company behind Kotlin.
If you’re using IntelliJ IDEA, click File ➤ Open and open the pirate-translator directory you made before.
Create a new Kotlin file named PirateTranslator.kt
and place in the directory src/main/kotlin/com/raywenderlich
. Add an interface as contract for the Talk like a pirate
translator.
// src/main/kotlin/com/raywenderlich/PirateTranslator.kt package com.raywenderlich interface PirateTranslator { // 1 fun translate(message: String): String }
In the interface, you specify a translate()
method which takes a String
and returns a String
.
Next you’ll write a Pirate translator brain :]
It should have the following behavior:
- For input Yes , it returns Aye .
- For input Hello , it returns Ahoy .
- For input Yes, Captain! , it returns Aye Aye! .
For example, for input,
“Hello, I am Captain Jack Sparrow”
the pirate translator returns:
“Ahoy!, I am Captain Jack Sparrow”
For your implementation of PirateTranslator
, create a new file named DefaultPirateTranslator.kt
and place it under src/main/kotlin/com/raywenderlich
. Add the class DefaultPirateTranslator
:
// src/main/kotlin/com/raywenderlich/DefaultPirateTranslator.kt package com.raywenderlich class DefaultPirateTranslator : PirateTranslator { // 1 val replacements = mapOf("Hello" to "Ahoy!", "Yes" to "Aye!", "Yes, Captain!" to "Aye Aye!") override fun translate(message: String): String { var result = message // 2 replacements.forEach { k, v -> result = result.replace(k, v) } return result } }
In this code:
Map
Test the PirateTranslator
Next you’ll add a test for the PirateTranslator
. Create a file under src/test/kotlin/com/raywenderlich
and name it PirateTranslatorTest.kt
.
Add your test class:
//src/test/kotlin/com/raywenderlich/PirateTranslatorTest.kt package com.raywenderlich import kotlin.test.Test import kotlin.test.assertEquals class PirateTranslatorTest { // 1 @Test fun testPirateTranslator() { // 2 val classUnderTest : PirateTranslator = DefaultPirateTranslator() // 3 assertEquals("Ahoy!, I am Captain Jack Sparrow", classUnderTest.translate("Hello, I am Captain Jack Sparrow")) // 3 assertEquals("Aye!", classUnderTest.translate("Yes")) } }
Here you:
@Test
To run the test, in Terminal in the project root directory, run the following command:
$ ./gradlew test --tests=com.raywenderlich.PirateTranslatorTest
You should see a BUILD SUCCESSFUL message if the test passes. If the test failed, you’d see an exception. You can also run the test in IntelliJ IDEA, by pressing the green run button next to the test method.
Create an AWS Lambda RequestHandler
Your project needs to accept input, pass it to the pirate translator, and respond with the translated output. You’ll do this with an AWS Lambda
RequestHandler
.
Create a class named HandlerInput
under src/main/kotlin/com/raywenderlich
. This class will encapsulate the message you are going to input into your Talk like a pirate
serverless function. Set up the class like this:
// src/main/kotlin/com/raywenderlich/HandlerInput.kt package com.raywenderlich class HandlerInput { var message: String = "" }
Note that you are not using a Kotlin data class for the input, as that would cause issues when using it to bind JSON in the AWS Lambda RequestHandler
.
Now create a Kotlin data class named HandlerOutput
under src/main/kotlin/com/raywenderlich
. This class will encapsulate the translation you will receive from the Talk like a pirate
serverless function. The data class should look like this:
// src/main/kotlin/com/raywenderlich/HandlerOutput.kt package com.raywenderlich data class HandlerOutput(val message: String, val pirateMessage: String)
Now you need to add the AWS Lambda dependency. Modify the file build.gradle.kts
, by adding the following dependency in the dependencies
block:
implementation("com.amazonaws:aws-lambda-java-core:1.2.0")
The aws-lambda-java-core dependency is a minimal set of interface definitions for Java support in AWS Lambda.
Open the Gradle tab in IntelliJ IDEA and hit the sync button to synchronize the dependencies.
Replace the contents of src/main/kotlin/com/raywenderlich/App.kt with the following:
//src/main/kotlin/com/raywenderlich/App.kt package com.raywenderlich import com.amazonaws.services.lambda.runtime.Context import com.amazonaws.services.lambda.runtime.RequestHandler // 1 class App : RequestHandler<HandlerInput, HandlerOutput> { // 2 val translator : PirateTranslator = DefaultPirateTranslator() // 3 override fun handleRequest(input: HandlerInput?, context: Context?): HandlerOutput { input?.let { // 4 return HandlerOutput(it.message, translator.translate(it.message)) } return HandlerOutput("", ""); } }
Here you do the following:
-
Create a
RequestHandler
. Lambda stream request handlers implement AWS Lambda Function application logic using input and output streams. -
Instantiate a
PirateTranslator
. -
Override
handleRequest
which takes aHandlerInput
and responds with aHandlerOutput
. -
Use the translator to populate
pirateMessage
, the second argument of theHandlerOutput
constructor.
Replace the contents of src/test/kotlin/com/raywenderlich/AppTest.kt with:
//src/test/kotlin/com/raywenderlich/AppTest.kt package com.raywenderlich import kotlin.test.Test import kotlin.test.assertEquals class AppTest { @Test fun testAppHasAGreeting() { val classUnderTest = App() val input = HandlerInput() input.message = "Hello" val expected = HandlerOutput("Hello", "Ahoy!") var output = classUnderTest.handleRequest(input, null) assertEquals(expected, output) } }
The above test verifies that given a HandlerInput
with a message
of value “Hello”, the response (the HandlerOutput
) contains a pirateMessage
with value “Ahoy!”.
Go ahead and run the test the same way as before, either from a Terminal
using gradlew
, or right in IntelliJ IDEA.
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 aReactiveX
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.
Where to Go From Here?
Congratulations again on deploying two versions of an AWS Lambda function written in Kotlin, and making the serverless function available in an API! :]
You can download both final projects using the Download Materials button at the top or bottom of the tutorial.
For further reading:
- Make sure to check Micronaut’s documentation on serverless functions .
- Also be sure to check AWS Lambda resources .
You have just scratched the surface of what is possible with AWS Lambda and serverless functions, but I hope it opens a world of possibilities for your mobile app API back-ends.
If you have any questions or comments, please don’t hesitate to join the forum discussion below.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK