Writing a handler for AWS lambda in Scala can be as easy as...
import io.circe.generic.auto._
import io.github.mkotsur.aws.handler.Lambda._
import io.github.mkotsur.aws.handler.Lambda
import com.amazonaws.services.lambda.runtime.Context
case class Ping(inputMsg: String)
case class Pong(outputMsg: String)
class PingPongHandler extends Lambda[Ping, Pong] {
override def handle(ping: Ping, context: Context) = Right(Pong(ping.inputMsg.reverse))
}
The input JSON will be automatically de-serialized into Ping
, and the output into Pong
. The handle()
method is supposed to return Either[Throwable, Pong]
: Right
if the input was handled correctly, and Left
otherwise.
This handler can be used in AWS Lambda as: io.github.mkotsur.example::handle
.
Features:
- Return Futures right from the handler!
- JSON (de)serialization of case classes;
- Plain strings are supported too;
- AWS API Gateway proxy integration;
- Uncaught errors are logged with SLF4J and re-thrown.
import io.circe.generic.auto._
import io.github.mkotsur.aws.handler.Lambda._
import io.github.mkotsur.aws.handler.Lambda
import com.amazonaws.services.lambda.runtime.Context
import scala.concurrent.Future
case class Ping(inputMsg: String)
class PingFuturePongHandler extends Lambda[Ping, Future[Int]] {
override def handle(ping: Ping, context: Context) =
Right(Future.successful(ping.inputMsg.length))
}
This lambda will accept an empty string, or string with null
as an input.
import io.circe.generic.auto._
import io.github.mkotsur.aws.handler.Lambda._
import io.github.mkotsur.aws.handler.Lambda
import com.amazonaws.services.lambda.runtime.Context
class NothingToNothingHandler extends Lambda[None.type, None.type] {
override protected def handle(i: None.type, c: Context) = {
println("Only side effects")
Right(None)
}
}
You can write less boilerplate when implementing a handler for API Gateway proxy events by extending Lambda.ApiProxy[I, C, O]
. There are three type parameters there. The first one (I
) corresponds to the body
field of the API Gateway proxy event
, the second one (C
) corresponds to requestContext
field, and the third one – to the body
in the response object. More info about how the even looks like here.
import io.circe.generic.auto._
import io.circe.Json
import io.github.mkotsur.aws.handler.Lambda._
import io.github.mkotsur.aws.proxy._
import io.github.mkotsur.aws.handler.Lambda
import com.amazonaws.services.lambda.runtime.Context
import MyProxy._
object MyProxy {
case class MyRequestBody(name: String)
case class MyResponseBody(score: Int)
}
class MyProxy extends Lambda.ApiProxy[MyRequestBody, Json, MyResponseBody] {
override def handle(
i: ApiProxyRequest[MyRequestBody, Json],
c: Context
): Either[Throwable, ApiProxyResponse[MyResponseBody]] =
i.body match {
case Some(MyRequestBody("Bob")) =>
Right(ApiProxyResponse.success(Some(MyResponseBody(100))))
case Some(MyRequestBody("Alice")) =>
Right(ApiProxyResponse.success(Some(MyResponseBody(50))))
case Some(_) =>
Right(ApiProxyResponse(404))
case None =>
Left(new IllegalArgumentException)
}
}
Tip 1: of course, you can also pass a type defined by a case class into the second type parameter. Please check #24 and src/test/scala/io/github/mkotsur/aws/proxy/ProxyRequestTest.scala
for an example.
Tip 2: Don't forget that Lambda.ApiProxy
is a very thin wrapper around Lambda
, so if something in ApiProxyRequest
or ApiProxyResponse
doesn't work for you - feel free to define your own case classes (and if you believe that the use case is generic enough – consider contributing back to the library).
Feel free to look at src/test/scala
for more examples. And of course, contributions to the docs are welcome!
Scala versions supported: 2.11.x, 2.12.x, 2.13.x.
libraryDependencies += "io.github.mkotsur" %% "aws-lambda-scala" % {latest-version}
Short answer: they complement each other. Long answer: read this blog post.