Initialize Scala Project

Assuming we are familiar with SBT and have that installed

$ sbt new scala/scala-seed.g8

A minimal Scala project.

name [Scala Seed Project]: Lambda Scala Seed

Template applied in /Users/vpanusuwan/projects/./lambda-scala-seed

Add Lambda Library

The libraries contains event classes which is a typed input for lambda. Check latest version at mvnrepository for java-core and java-events separately.

    libraryDependencies ++= Seq(
      "com.amazonaws" % "aws-lambda-java-core" % awsLambdaVersion,
      "com.amazonaws" % "aws-lambda-java-events" % awsLambdaEventsVersion,
      scalaTest % Test
    )

Create handler function

Create a class with method of any name in any package. The important thing is that the method accepts a lambda event

package example

import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.events.{APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse}

class Main  {
  def handler(apiGatewayEvent: APIGatewayV2HTTPEvent, context: Context): APIGatewayV2HTTPResponse = {
    println(s"body = ${apiGatewayEvent.getBody()}")
    return APIGatewayV2HTTPResponse.builder()
      .withStatusCode(200)
      .withBody("okay")
      .build()
  }
}

Configure Assembly Plugin

Next is to configure the assembly plugin to produce a single fat jar containing our code and all the dependencies together. See latest version of the plugin at https://github.com/sbt/sbt-assembly

In project/plugins.sbt

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")

In build.sbt, configure the fat jar merging strategies. At very minimum we should discard contents in META-INF. Also overrides a jar name.

assemblyJarName in assembly := "lambda-scala-seed.jar"

assemblyMergeStrategy in assembly := {
  case PathList("META-INF", xs @ _*) => MergeStrategy.discard
  case x => MergeStrategy.first
}

Package

sbt assembly

Then finds the packaged jar at target/scala-2.13/<jarname>

Create Function

Install aws cli tool https://aws.amazon.com/cli/. Uses following commands to deploy / update the function.

First, Lambda needs a role to execute, which should at least have the AWSLambdaBasicExecutionRole policy attached. The policy simply enables a write to CloudWatch logs. A cdk snipplet to create such role would be.

 lambda_basic_policy = iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")
 
role = iam.Role(self, "fn_lambda_role", role_name="fn_lambda_role", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"))
role.add_managed_policy(lambda_basic_policy)

Then we can deploy the jar to lambda

aws lambda create-function \
  --function-name lambda-scala-seed \
  --role "arn:aws:iam::<aws_account_no>:role/fn_lambda_role" \
  --zip fileb://target/scala-2.13/lambda-scala-seed.jar \
  --runtime java11 \
  --memory 256 \
  --handler "example.Main::handler"

Test Function

aws lambda invoke --function-name "lambda-scala-seed" /dev/stdout

Update Function

 aws lambda update-function-code \
 --function-name "lambda-scala-seed" \
 --zip fileb://target/scala-2.13/lambda-scala-seed.jar

References