DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Building Scalable Data Lake Using AWS
  • Building a Scalable ML Pipeline and API in AWS
  • Breaking AWS Lambda: Chaos Engineering for Serverless Devs
  • AWS Step Functions Local: Mocking Services, HTTP Endpoints Limitations

Trending

  • Event-Driven Architectures: Designing Scalable and Resilient Cloud Solutions
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Issue and Present Verifiable Credentials With Spring Boot and Android
  • The 4 R’s of Pipeline Reliability: Designing Data Systems That Last
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. Serverless with AWS Lambda and Scala

Serverless with AWS Lambda and Scala

We take a look at one developer's efforts to make working with AWS Lambda using Scala even less effort. Check it out!

By 
Rob Hinds user avatar
Rob Hinds
·
May. 23, 18 · Presentation
Likes (4)
Comment
Save
Tweet
Share
11.2K Views

Join the DZone community and get the full member experience.

Join For Free

About a year ago, I started looking at AWS's serverless offering, AWS Lambda. The premise is relatively simple: rather than a full server running that you manage and deploy your Docker/web servers to, you just define a single function endpoint and map that to the API gateway and you have an infinitely* scaleable endpoint.

The appeal is fairly obvious — no maintenance or upgrading servers, fully scalable, and pay-per-second of usage (so no cost for AWS Lambda functions that you define whilst not being called). I haven't looked into the performance of using the JVM-based Lambda functions, but my assumption is that there will be potential performance costs if your function isn't frequently used, as AWS will have to start up the function, load its dependencies, etc., so depending on your use case, it would be advisable to look at performance benchmarking before putting into production use.

When I first looked into AWS Lambda a year ago, it was less mature than it is today, and dealing with input/output JSON objects required annotating POJOs, so I decided to start putting together a small library to make it easier to work with AWS Lambda in a more idiomatic Scala way — using Circe and it's automatic encoder/decoder generation with Shapeless. The code is all available on GitHub.

Getting Started

To deploy on AWS I used a framework called Serverless — this is a really easy framework to set up serverless functions on a range of cloud providers. Once you have followed the pre-requisite install steps, you can simply run:

serverless create --template aws-java-gradle


This will generate you a Java (JVM)-based Gradle project template, with a YML configuration file in the root that defines your endpoints and function call. If you look in the src folder as well, you will also see the classes for a very simple function that you can deploy and check your Lambda works as expected. You should also take the time at this point to log in to your AWS console and have a look at what has been created in the Lambda and API Gateway sections. You should now be able to curl your API endpoint (or use the serverless CLI with a command like  serverless invoke -f YOUR_FUNCTION__NAME -l).

ScaLambda — AWS Lambda with Idiomatic Scala

Ok, so we have a nice simple Java-based AWS Lambda function deployed and working, let's looking at moving it to Scala. As you try to build an API in this way you will need to be able to define endpoints that can receive inbound JSON being posted as well as return fixed JSON structures — AWS provides its inbuilt de/serialisation support, but inevitably you will have a type that might need further customization of how it is de/serialized (UUIDs maybe, custom date formats, etc.) and there are a few nice libraries that can handle this stuff and Scala has some nice ways that can simplify this.

We can simply upgrade our new Java project to a Scala one (either convert the build.gradle to an sbt file, or just add Scala dependency/plugins to the build file as is) and then add the dependency:

repositories {
    maven {
        url  "https://dl.bintray.com/robhinds/snapshots" 
    }
}

dependencies {
    compile 'io.github.robhinds:ScaLambda:0.0.1'
}
view raw


We can now update the input/output classes so they are just normal Scala case classes:

case class TestInput(value: String)
case class TestOutput(value: String)


Not a huge change from the POJOs we had, but is both more idiomatic and also means you can use case classes that you have in other existing Scala projects/libraries elsewhere in your tech stack.

Next we can update the request handler — this will also result in quite similar looking code to the original generated Java code, but will be in Scala and will be backed by Circe and it's automatic JSON encoder/decoder derivation.

class TestFunction extends Controller[TestInput, TestOutput] 
  with DefaultResponseSerializerComponent 
  with DefaultExceptionHandler {
    
  override def handleRequest(in: TestInput): ApiResponse[TestOutput] = 
    success(TestOutput(s"OUTPUT:${in.value}"))
        
}


You will see that similar to the AWS Java class we define generic parameter types for the class that represents the input case class and the output case class and then you simply implement the handleRequest method which expects the input class and returns the output response.

You might notice the return type is wrapped in the ApiResponse  class; this is simply an alias for a Scala  Either[ Exception , T ] , which means if you need to respond with an error from your function you can just return an exception rather than the TestOutput . To simplify this, there is an ApiResponse  companion object that provides a success  and failure  method:

object ApiResponse {
  def failure[T](e :Exception): ApiResponse[T] = Left(e)
  def success[T](t: T): ApiResponse[T] = Right(t)
}


All the JSON serialization/de-serialization will use Circe's auto-derived code which relies on Shapeless — if you use custom types that cannot be automatically derived, then you can just define implicit encoder/decoders for your type and they will be used.

Error Handling

The library also has support for error handling — as the ApiResponse class supports returning exceptions, we need to map those exceptions back to something that can be returned by our API. To support this, the Controller class that we have implemented for our Lambda function expects (via self type annotations) to be provided an implementation of the ExceptionHandlerComponent trait and of the ResponseSerializerComponent trait.

Out of the box, the library provides a default implementation of each of these that can be used, but they can easily be replaced with custom implementations to handle any custom exception handling required:

trait DefaultExceptionHandler extends ExceptionHandlerComponent {
  private val l = Logger(classOf[DefaultExceptionHandler])
  override def exceptionHandler: ExceptionHandler = new ExceptionHandler {
    override def handle[B](e: ApiResponse[B]): Either[ErrorResponse, B] = e match {
      case Left(x: JsonError) => errorResponse("400", s"Error de-serialising JSON: ${x.getMessage}")
      case Left(x: NotFound) => errorResponse("404", x.message)
      case Left(x: BadRequest) => errorResponse("400", x.message)
      case Left(x: InternalServerError) => errorResponse("500", x.message)
      case Left(x) => errorResponse("500", x.getMessage)
      case Right(x) => Right(x)
    }
  }
}


Custom Response Envelopes

We mentioned above that we also need to provide an implementation of the ResponseSerializerComponent  trait. A common pattern in building APIs is the need to wrap all response messages in a custom envelope or response wrapper — we might want to include status codes or additional metadata (paging, rate limiting, etc.) — this is the job of the ResponseSerializerComponent. The default implementation simply wraps the response inside a basic response message with a status code included, but this could easily be extended/changed as needed.

trait DefaultResponseSerializerComponent extends ResponseSerializerComponent {
  override def responseSerializer: ResponseSerializer = new ResponseSerializer {
    override def serialiseResponse[B: Encoder](e: Either[ErrorResponse, B]): Json = e match {
      case Left(x) => x.asJson
      case Right(x) =>
        Json.fromFields(List(
          ("status", Json.fromString("200")),
          ("data", x.asJson)
      ))
    }
  }
}


Conclusion

The project is still in early stages of exploring the AWS Lambda stuff, but hopefully is starting to provide a useful approach to idiomatic Scala with AWS Lambda functions, allowing re-use of error handling and serialization so you can just focus on the business logic required for the function.

AWS AWS Lambda Scala (programming language)

Published at DZone with permission of Rob Hinds, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Building Scalable Data Lake Using AWS
  • Building a Scalable ML Pipeline and API in AWS
  • Breaking AWS Lambda: Chaos Engineering for Serverless Devs
  • AWS Step Functions Local: Mocking Services, HTTP Endpoints Limitations

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!