Deploying Micronaut applications in serverless environments like AWS Lambda and Google Cloud Functions is a surefire way to boost scalability and cut costs. Let’s unpack how to do this with a straightforward, hands-on guide, peppered with practical examples and some insider tricks.
First things first, familiarize yourself with Micronaut. It’s a sleek, JVM-based framework designed for crafting modular, testable microservices and serverless apps. Its low memory usage and rapid startup times make it a fantastic fit for serverless setups.
To kick things off, ensure your development environment is ready to roll. Have JDK 11 (or later) installed and choose a robust text editor or IDE – IntelliJ IDEA comes highly recommended. Micronaut works great with Maven and Gradle for building applications, but we’ll stick with Gradle for this guide.
To create a new Micronaut app decked out with an AWS Lambda feature, you’d use the following command via the Micronaut CLI:
mn create-function-app example.micronaut.micronautguide --features=aws-lambda --build=gradle --lang=java
This command sets up a Micronaut app in a directory called micronautguide
using the default package name example.micronaut
. The app includes a class extending MicronautRequestHandler
, vital for handling AWS Lambda events.
Now, dive into writing your application. Here’s a look at what your FunctionRequestHandler
class might resemble:
package example.micronaut;
import io.micronaut.function.aws.MicronautRequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import jakarta.inject.Inject;
import io.micronaut.json.JsonMapper;
import java.io.IOException;
public class FunctionRequestHandler extends MicronautRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
@Inject
JsonMapper objectMapper;
@Override
public APIGatewayProxyResponseEvent execute(APIGatewayProxyRequestEvent input) {
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
try {
String json = new String(objectMapper.writeValueAsBytes(Collections.singletonMap("message", "Hello World")));
response.setStatusCode(200);
response.setBody(json);
} catch (IOException e) {
response.setStatusCode(500);
}
return response;
}
}
This snippet illustrates how to handle incoming APIGatewayProxyRequestEvent
and return an APIGatewayProxyResponseEvent
packed with a JSON response.
Deploying this Micronaut app to AWS Lambda requires bundling it into a FAT JAR, inclusive of all dependencies. This is done via Gradle:
./gradlew shadowJar
This command creates a JAR file fit for uploading to AWS Lambda.
Here’s what you need to do next:
- Create a Lambda Function on the AWS Lambda console. Select Java 11 (or the Java version you favor) for the runtime.
- Upload the Code - that FAT JAR you just created.
- Set the Handler to point to the class extending
MicronautRequestHandler
, likeexample.micronaut.FunctionRequestHandler
.
To test your Lambda function, whip up a JSON event. Here’s a sample:
{
"path": "/",
"httpMethod": "GET",
"headers": {
"Accept": "application/json"
}
}
When tested, expect a 200 response with the JSON message “Hello World”.
A nagging issue with Lambda functions is cold starts. They can severely dent performance. AWS Lambda’s SnapStart feature pre-initializes the runtime to chop down cold start times. Here’s the drill to deploy with SnapStart:
- Build the Application: Use a script to bundle the FAT JAR and, if applicable, a native executable with GraalVM.
- Deploy with AWS CDK: Utilize AWS Cloud Development Kit (CDK) to roll out resources. The
infra
module in your project contains the CDK code.
./release.sh
This script churns out the FAT JARs and native executable and deploys the resources to your AWS account via cdk deploy
.
Analyzing the performance of your Lambda functions gets easier with AWS CloudWatch Logs Insights. To check the maximum cold start duration, use this query:
filter @type="REPORT"
| fields greatest(@initDuration, 0) + @duration as duration
| max(duration) as max
If SnapStart is in play, modify the query a bit:
filter @message like "REPORT"
| filter @message not like "RESTORE_REPORT"
| parse @message /Restore Duration: (?<@restore_duration_ms>[0-9\.]+)/
| parse @message / Duration: (?<@invoke_duration_ms>[0-9\.]+)/
| fields
greatest(@restore_duration_ms, 0) as restore_duration_ms,
greatest(@invoke_duration_ms, 0) as invoke_duration_ms
| fields
restore_duration_ms + invoke_duration_ms as total_invoke_ms
| stat
max(total_invoke_ms) as max
AWS Lambda is just one option. Deploying Micronaut apps to Google Cloud Functions is also straight-forward. Here’s a quick rundown:
- Create a Cloud Function via the Google Cloud Console. Choose Java 11 (or your preferred version) as the runtime.
- Upload the Code – the FAT JAR produced by Gradle.
- Set the Entry Point – the class extending
MicronautRequestHandler
.
Google Cloud Functions use HTTP triggers to call your Micronaut app. Here’s an example of severing an HTTP request:
package example.micronaut;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import io.micronaut.json.JsonMapper;
import java.io.IOException;
import java.util.Collections;
public class FunctionHttp implements HttpFunction {
@Inject
JsonMapper objectMapper;
@Override
public void service(HttpRequest request, HttpResponse response) throws IOException {
String json = new String(objectMapper.writeValueAsBytes(Collections.singletonMap("message", "Hello World")));
response.setStatusCode(200);
response.getWriter().write(json);
}
}
This class handles HTTP requests and returns a JSON response.
Testing your app under load is paramount to ensure it performs impressively under traffic. Tools like Gatling come in handy to simulate load on your app.
Use this script to set up a load test with Gatling:
./load.sh
The script runs a simulation executing POST, GET, DELETE scenarios with 50 concurrent users for 3 minutes, then ramps up to 100 concurrent users for another 2 minutes.
Deploying Micronaut apps in serverless environments like AWS Lambda and Google Cloud Functions is a cakewalk that smartly leverages both frameworks’ and cloud providers’ robust capabilities. By sticking to these steps and optimizing for performance, you can build scalable, sleek, and efficient serverless applications.
Whether it’s AWS Lambda or Google Cloud Functions you’re working with, packaging your app correctly, handling events appropriately, and optimizing for cold starts is crucial. Micronaut offers a rich toolkit to simplify and enhance the serverless development journey.