AWS Lambda to Quarkus Migration Patterns

Overview

Introduction

Recently, we were tasked with the migration of a set of AWS Java Lambda applications to containers on Red Hat's Kubernetes Platform of OpenShift on the Azure cloud. AWS Lambdas are compute services that let you run code without provisioning or managing servers and they can do so on-demand, on the other hand containers offer richer functionality with the ability to run long-running, on-demand, in-memory multithreaded services whilst they demand your attention to the configuration of the underlying container framework.

Red Hat's Quarkus Java runtime was selected to form the basis for the migration of the lambdas to containers as it is a Java framework which would ease the migration of the business logic of the Java lambda and in addition it is designed exclusively for cloud native development, with reduced memory consumption and startup time, support for container functionality (e.g. health checks, metrics, configuration, secrets & serverless functions) and finally it delivers enhanced developer experience with dynamic reload and local developer services using test containers.

In this article we walk through the four types of AWS lambda service patterns involved in the use case and how one can migrate the AWS lambda handlerRequest() code into a Quarkus method and project.

The four types of AWS lambda service patterns identified in the use case included:

  • long-running REST API triggered lambdas,
  • long-running scheduled lambdas,
  • event message based triggered lambdas and
  • one-off job based lambdas.

Furthermore, in a follow-up article we will look at the set of cookbook activities to migrate and configure external integrations, configure the Quarkus project for them and set up container building and testing.

AWS Lambda to Quarkus application code migration activities

In order to ensure all lambda code migrations to Quarkus containers follow a standard set of activities they have been documented and are available via an interactive diagram of the migration path. The following diagram is a static view of the activities path defined for a single AWS Lambda migration and it contains the two main paths the migration involved:

  • prepare for deployment
  • migrate the business code (the focus of this article)

AWS Lambda to Quarkus Application Code Migration Activities

Quarkus quickstart application project selection

The initial action in the migration path involves selecting the appropriate quickstart project and generating a new project based on this skeleton. The cookbook repository aws-lambdas-to-azure-quarkus-containers contains one quickstart application project for each of the four AWS Lambda service patterns and the selection is based on the Lambda charactetistics. The quickstart projects available are:

Quickstart App Characteristics
Rest API (Deployment Based OCP app) Quarkus Quickstart A REST API application resulting in a CosmosDB Connection and entry creation
Job Main (CronJob Based OCP app) Quarkus Quickstart A Job based application, it demonstrates setting up the job and a CosmosDB connection
Scheduled Job (Deployment Based OCP app) Quarkus Quickstart A scheduled based application runs at pre-defined intervals. Demonstrates, connecting and authenticating with Kafka, publishing and subscribing to messages
Message Initiated microservice (Deployment Based OCP app) Quarkus Quickstart An event or message driven application, demonstrates connecting and authenticating with Kafka, publishing and subscribing to messages

Lambda handlerRequest() code migration to Quarkus method

The handerRequest is the Java method which contains the lambda business code and is invoked in each of the lambda implemented service patterns. Here we will look at how this code can be migrated to a Quarkus based method that delivers the same pattern.

Code Migrations of a Job based Lambda

For a lambda which starts as the result of a single run job the code migration begins by calling or placing the handlerRequest() lambda code in a method simply annotated with @ApplicationScoped. The Quarkus application does not need to configure anything additional as long as the Kubernetes deployment for the POD created for this application is of CronJob type.

 0```
 1@ApplicationScoped
 2public class JobMain {
 3  @Override
 4  public int run(String... args) {
 5    Log.info("Running JobMain .... ");
 6      this.httpClient = HttpClient.newBuilder()
 7          .connectTimeout(Duration.ofSeconds(10))
 8          .version(Version.HTTP_1_1)
 9          .build();
10      this.mapper = new ObjectMapper();
11      //Lambda business method below
12      this.handleRequest();     
13      return 0;
14  }
15```  

An example for this service exists in JobMain.java#run.

Code Migrations of a REST API Triggered based Lambda

For a lambda which starts as the result of a REST API call the code migration begins by calling or placing the handlerRequest() lambda code in a method annotated with @Path("/hello"). Furthermore, depending on the URI path the code should be invoked at the annotation can be appropriately adjusted.

 0```
 1  @ApplicationScoped
 2  @Path("/hello")
 3  public class HelloCosmosResource {
 4  
 5      @Inject
 6      MeterRegistry registry;
 7         
 8      @Inject
 9      protected CosmosConnection connection;
10         
11      @GET
12      @Produces(MediaType.TEXT_PLAIN)
13      @Path("{country}")
14      public String hello(String country) {
15  
16          // Code call to Lambda handler method
17          this.handleRequest(); 
18      }
19  
20      @POST
21      @Produces(MediaType.TEXT_PLAIN)
22      @Consumes(MediaType.APPLICATION_JSON)
23      @Path("/create")
24      public String helloPost(HelloCountry hc) {
25  
26          // Code call to Lambda handler method
27          this.handleRequest(); 
28      }
29  }
30```

In HelloCosmosResource.java#hello you can find the above example whilst the Quarkus project requires the following extensions in order to offer the REST functionality.

 1<dependency>
 2  <groupId>io.quarkus</groupId>
 3  <artifactId>quarkus-resteasy-reactive</artifactId>
 4</dependency>
 5<dependency>
 6  <groupId>io.quarkus</groupId>
 7  <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
 8</dependency>
 9<dependency>
10  <groupId>io.quarkus</groupId>
11  <artifactId>quarkus-smallrye-openapi</artifactId>
12</dependency>

Code Migrations of a Scheduled Triggered based Lambda

For a lambda which starts as the result of a scheduled timer based event the code migration begins by calling or placing the handlerRequest() lambda code in a method with @Scheduled() annotation adjusting accordingly the intervals the scheduling will occur. The following example exists in PollOrchestrator.java#dmdScheduler.

 0```
 1@ApplicationScoped
 2public class PollOrchestrator {
 3
 4  // The Lambda handle method containing class
 5  @Inject
 6  LambdaFunction dmdPoller;
 7
 8  @Inject
 9  LastFetchedEventRepository lastFetchedEventRepository;
10
11  public PollOrchestrator() {
12  }
13
14  @Scheduled(every = "10s")
15  public void dmdScheduler() {
16    try {
17
18      // Call the Lambda handlerRequest method
19      dmdPoller.handleEvent(UUID.randomUUID().toString());
20
21      registry.counter("country_counter", Tags.of("name", "dmdScheduled")).increment();
22
23    }  catch (Exception e) {
24      logger.error("Unknown exception for DMD Scheduler: {}", e.getMessage(), e);
25    }
26  }
27}
28```

In order for Quarkus to provide the scheduling functionality the quarkus-scheduler extension is added to the project.

1<dependency>
2  <groupId>io.quarkus</groupId>
3  <artifactId>quarkus-scheduler</artifactId>
4</dependency>

Code Migrations of an Event or Message Triggered based Lambda

For a lambda which starts as the result of an event the code migration begin by calling or placing the handlerRequest() lambda code in a method annotated with @Incoming("quickstart-kafka-in") which determines the Kafka Topic Quarkus will be integrating and listening to for new events.

 0```
 1  @Incoming("quickstart-kafka-in")
 2  public CompletionStage<Void> consumeQuickstartKafkaIn(Message<Event> msg) {
 3
 4      try{
 5          Log.info("consumeQuickstartKafkaIn : Event received from Kafka");
 6
 7          registry.counter("events", Tags.of("event_type", msg.getPayload().getType())).increment();
 8
 9          Event event = msg.getPayload();
10          Log.info("Payload : "+event);
11
12          // Code call to Lambda handlerRequest method
13          this.handleRequest(); 
14      }
15      catch (Exception e) {
16          Log.error(e.getMessage());    
17      }
18      finally {
19          return msg.ack();
20      }
21    }
22```

An example implementation exists in EventResource.java#consumeQuickstartKafkaIn method whilst the quarkus-smallrye-reactive-messaging-kafka extension is added to the Quarkus project to provide the integration functionality to Kafka.

1<dependency>
2  <groupId>io.quarkus</groupId>
3  <artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
4</dependency>

Summary

In this article we have seen how one can start migrating Java lambda application code into container based Quarkus projects. In the A cookbook on migrating AWS Lambda applications to Quarkus we will delve more on the activities to migrate Lambda external integrations configuring the Quarkus project for them whilst also setting them up for building and testing of the containerized application.