Convert a REST API to AWS Lambda with minimal effort using Quarkus

-

In this blog post, you learn how to convert a REST API to an aws Lambda without any refactoring. The application is already using the Quarkus framework, but with an API based on RestEASY Reactive. Keep reading to find out why you might need the conversion , how I utilized AWS Lambda with AWS’s http gateway and learn about the benefits of saving costs and development time.

Playing darts at the office

We have a nice side-project at the office, a simple app that we use when playing dart games. Recently, I took upon myself to add a leaderboard to the app. In short, the back end application would be used to save game data and serve high score lists. Because I already loved the Quarkus framework and wanted to show its power to my colleagues, I decided to use Quarkus for this task.

Darts application Luminis - Convert a REST API to AWS Lambda

The idea was simple: Accept different types of game data (for different game modes that the web app provides) and store it in a DynamoDB table. Have some endpoints to query data for different types of games or for specific players, and there you have it.
I already wrote a functioning application when a colleague asked: “Why not make it in the form of Lambdas?” Since the application would only be used sporadically, it would be a waste of money to have an EC2 instance running continuously. Response time is not of much importance in this case, and cold start response times could be mitigated by compiling a native executable using GraalVM native image, so a serverless solution seemed ideal.

Which Quarkus extension to use?

There are multiple Quarkus extensions available for AWS Lambda development, each with their own advantages and drawbacks:

  1. quarkus-amazon-lambda: the bare-bones Lambda development kit;
  2. quarkus-funqy-amazon-lambda: Lambda binding for the cloud agnostic funqy library;
  3. quarkus-amazon-lambda-http: develop Lambdas to deploy behind an aws http gateway;
  4. quarkus-amazon-lambda-rest: develop Lambdas to deploy behind an aws API gateway.

Since I had already written the application, extensions number 1 and 2 would require lots of refactoring because each function needs their own Quarkus project. The extensions use a shared code imported as a dependency (unless you have no problem with hacking the framework to change this behavior).
Extensions 3 and 4 allowed me to keep my application code as it was. My resource (or controller) classes looked like any other resource class.  However, it would, with a simple command, be deployed as a single lambda, along with a gateway that handles incoming requests. This doesn’t only work for RestEASY Reactive, but for any http framework offered by Quarkus such as UndertowReactive RoutesFunqy-HTTP or Spring Web API.  For this case, I chose the quarkus-amazon-lambda-http extension because it didn’t need the extra functionality provided by the API gateway. Additionally, the HTTP gateway comes with lower latency at a lower price point.

How to convert a REST API to AWS Lambda?

When converting a REST API to AWS Lambda, I recommend to consider the following methods. This is an example of an API built on RestEASY, which is Quarkus’ default extension for http servers. There are small differences between the classic and the reactive versions of RestEASY. However, the code below is valid for both. If you know Jakarta, this code might also look familiar, because the annotations (as well as the MediaType and Response classes) all come from the jakarta.ws.rs package.

// 1
    @GET
    @Path("hello")
    public String hello() {
        return "hello, world";
    }
// 2
    @GET
    @Path("hello/{input}")
    public String hello(String input) {
        return "hello, " + input;
    }
// 3
    @POST
    @Path("hello")
    public String hello(MyClass myObject) {
        return "hello, " + myObject.getValue();
    }

When you include the quarkus-amazon-lambda-http extension in your POM file and deploy your application as a Lambda, you can just send http requests to your gateway url. Then you can use paths and pass json objects like you normally would for any REST API. As a result, all of the methods above are still accessible. For instance, method number 2 can be invoked with a GET request to https://{restapi_id}.execute-api.{region}.amazonaws.com/{stage_name}/hello/Luminis and should return “hello, Luminis”.

If you want to test your Lambda directly via the aws Lambda test console, you should wrap your http request in a Lambda request like so:

{
  "body": "{ \"myValue\" : \"lambda\"}",
  "resource": "/{proxy+}",
  "path": "/hello",
  "httpMethod": "POST",
  "isBase64Encoded": false
}
// Output: "hello, lambda"

This is how the gateway passes requests to your Lambda. Notice that the value of the body parameter has quotes around it and that the inner quotes escaped. This is because, even though we are including a json object, the body must be provided as a string.

Wait, that’s it? All we did was add a dependency and it just works?

Short answer: Yes!

Longer answer: Yes, but you need to have your AWS credentials configured correctly and you need the SAM cli to test and deploy your lambda.

Deploying and testing

To build our application, we can use the maven or gradle wrapper that comes with your Quarkus project. However, we’re going to use the Quarkus cli. Run the following command in the root directory of your project:

quarkus build --native --no-tests -Dquarkus.native.container-build=true

the --native flag tells the cli to build a native image using GraalVM. When you use the -Dquarkus.native.container-build=true flag, you don’t need to have GraalVM installed. However, you need to have Docker or Podman running. Your machine automatically pulls a Docker image and spins up a container, which then builds your Lambda! This is great because GraalVM native image normally only allows you to build binaries for your OS. Instead, the Docker image is always Linux, which is what we need. If you’re on Linux and have GraalVM configured correctly, you can just run quarkus build --native. If you’re wondering why we’ve added an --no-tests argument, it’s because the test code is not included in the build. You can have the tests run separately in your pipeline, but that’s a story for another time.

Next, we can use the SAM cli to deploy on a local environment on docker for testing:

sam local start-api --template target/sam.native.yaml

Or we can deploy on AWS Lambda:

sam deploy -t target/sam.native.yaml -g

Considerations

While it’s convient to convert a REST API to AWS Lambda, it’s possibly considered a better practice to split your application into multiple Lambdas for the sake of atomicity and performance. However, for simple applications, the pros might outweigh the cons. Especially because deploying a native image reduces startup time drastically.

One more thing to consider is that, apart from getting billed for the lambda’s invocations and computing time, you’ll also get billed for the gateway. Luckily, the pricing for the http gateway is only $1 per million requests for the first 300 million requests. It get even cheaper if you go over that threshold.

Conclusion on how to convert a REST API to AWS Lambda

Any Quarkus-based http-server can be converted to a single AWS Lambda by simply adding the quarkus-amazon-lambda-rest or quarkus-amazon-lambda-http extension to the POM file. Just make sure you have the required aws tools installed and configured on your machine and build a native image using GraalVM if you don’t want unacceptable startup times. It can save you, your company, or your customer serious money if it means getting rid of a mostly idle EC2 instance and requires no refactoring on your server code at all.