AWS Compute Blog

Writing AWS Lambda Functions in Scala

by Tim Wagner | on | in AWS Lambda | | Comments

Tim Wagner Tim Wagner, AWS Lambda General Manager


Sean Reque Sean Reque, AWS Lambda Software Developer


AWS Lambda’s Java support also makes it easy to write Lambda functions in other jvm-based languages. Let’s take a look at how you can do that for Scala.

Getting Started with Scala

If you’re an old hand at Scala, skip ahead…otherwise: We’ll step you through the process to get up and running with Scala on a Windows machine; other platforms will be similar.

First, you’ll need to download Scala’s simple build tool (sbt):
sbt install

Next, open a command line prompt where you want to do your development (I chose “C:/tmp” to keep it simple) and run ‘sbt’, which will auto-update itself. (Depending on your settings, you might need admin privileges for this to succeed.) Then create the following directory structure:

C:\tmp\lambda-demo
  project
  src
    main
      scala
        example

Inside the ‘project’ subdirectory, create a file named ‘plugins.sbt’ with the following content:


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

Inside ‘/tmp/lambda-demo’ (as a peer to ‘src’ and ‘project’), add a file named ‘build.sbt’ with the following content:


javacOptions ++= Seq("-source", "1.8", "-target", "1.8", "-Xlint")

lazy val root = (project in file(".")).
  settings(
    name := "lambda-demo",
    version := "1.0",
    scalaVersion := "2.11.4",
    retrieveManaged := true,
    libraryDependencies += "com.amazonaws" % "aws-lambda-java-core" % "1.0.0",
    libraryDependencies += "com.amazonaws" % "aws-lambda-java-events" % "1.0.0"
  )

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

You’re now ready to start doing Scala in Lambda!

Writing Your First Lambda-in-Scala Function

Let’s start simple with a function that can extract the object (key) name from an Amazon S3 event. We’ll return it, to make the function easy to debug, then wire it up to an S3 bucket to see event processing working end-to-end. At that point you’ll be able to start modifying the code to do whatever additional analysis or transformation you like on the actual content of the object, or extend the example to process other argument types or event sources.

First, in ‘C:\tmp\lambda-demo\src\main\scala\exampleadd\Main.scala’, add the following sample code:


package example;

import scala.collection.JavaConverters._
import java.net.URLDecoder
import com.amazonaws.services.lambda.runtime.events.S3Event

class Main {
  def decodeS3Key(key: String): String = URLDecoder.decode(key.replace("+", " "), "utf-8")

  def getSourceBuckets(event: S3Event): java.util.List[String] = {
    val result = event.getRecords.asScala.map(record => decodeS3Key(record.getS3.getObject.getKey)).asJava
    println(result)
    return result
  }
}

Next, fire up sbt in ‘lambda-demo’ and execute the ‘compile’ command followed by the ‘assembly’ command. You should end up with a Jar file ‘C:\tmp\lambda-demo\target\scala-2.11\lambda-demo-assembly-1.0.jar’. (Your version may differ depending on when you try this example.)

We’ll discuss the programming model below, but first let’s complete the process of creating and testing a Lambda function. You can do this via the CLI or the console; below I’ve illustrated what it looks like in the console:

Scala Upload in the AWS Lambda Console

I’m using the default suggestions for memory, duration, and role (basic execution). Note the handler: ‘example.Main::getSourceBuckets’. Click ‘Create Lambda function’ and you should be ready to test.

Testing Your Scala Function

At the point your Lambda function should be working, and behaves like any other Lambda function. You can use a test invoke with the “S3 Put” sample event and you should see [“HappyFace.jpg”] (the S3 key name in the PUT event sample) as the result. To test your function end to end, click on “Go to function list”, add an S3 bucket as an event source, and upload a sample file. You can go to the Amazon CloudWatch Logs page to check on the result and should see a similar output:

Amazon CloudWatch Log result of triggering the Scala function with an S3 upload

From here, you can start extending your code to retrieve and transform the content of the file, add other event sources, etc. Be sure to keep your execution role permissions consistent with the AWS operations you perform in your Scala code.

Scala Programming Model

Writing Lambda functions in Scala requires dealing with some “Javaisms” at the points of entry and exit. You can notice this in code above: the use of java.util.List and the .asScala and .asJava converters. These are all necessary because the built-in Lambda serializer doesn’t understand native Scala types. Since Java and Scala share primitives, Lambda function parameter and return types like ints and strings work fine in both languages without any explicit conversion. Java collections and POJOs (more specifically, their [de]serialization) require a little more work to coexist in Scala. You can still employ Scala in your Lambda function by using Lambda’s byte stream interface and your own serialization library, such as the Jackson Scala module. To add this library to your project, in your build.sbt file add the following line in the dependencies section:


    libraryDependencies += "com.fasterxml.jackson.module" % "jackson-module-scala_2.11" % "2.5.2"

Here’s an example that uses the Scala Jackson module: it defines a Scala class, NameInfo, and uses the byte stream interface to deserialize the argument passed to the Lambda function as this Scala class. It then outputs a greeting message as a result.


package example;

case class NameInfo(firstName: String, lastName: String)

class Main {
  import java.io.{InputStream, OutputStream, PrintStream}

  val scalaMapper = {
    import com.fasterxml.jackson.databind.ObjectMapper
    import com.fasterxml.jackson.module.scala.DefaultScalaModule
    new ObjectMapper().registerModule(new DefaultScalaModule)
  }

  def greeting(input: InputStream, output: OutputStream): Unit = {
    val name = scalaMapper.readValue(input, classOf[NameInfo])
    val result = s"Greetings ${name.firstName} ${name.lastName}." 
    output.write(result.getBytes("UTF-8"))
  }
}

Invoking this function with input like:


{
    "firstName": "Robert",
    "lastName": "Dole"
}

produces "Greetings Robert Dole" as a result.

 
We hope this article helps fans of both Scala and Lambda enjoy them together. Happy Lambda (and Scala) coding!

-Tim and Sean

TAGS: ,