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)
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.