In this article, we will look at one option to write a Java AWS Lambda using the still rather new microframework Micronaut and we’re going to deploy our code to production by means of Serverless.
If you’re reading this, you’re probably already familiar with AWS Lambda, an AWS product which allows developers to run small applications without having to worry about any infrastructure that backs it up. One just needs to upload a piece of code to AWS and the program is called. Then, AWS will take care of finding out where and how to execute it (on demand).
In this article we’ll see one way to write a Java AWS Lambda using the still rather new micro framework Micronaut and we’re going to deploy our code to production by means of Serverless.
In this example, we want to write an AWS Lambda which implements a REST endpoint called /capital/{country}. This endpoint will consume the REST Countries API and reply to us with the capital of a given country. Finally, we use an AWS API Gateway to call our Lambda as follows:
{
"capital”: “<capital>”
}
curl <api-gateway-url>/capital/{country}
We’re building a classic class structure which begins with a Controller, which calls a Service and this Service then calls a Client:
A controller class is always annotated with @Controller and has a path as a parameter. Similarly, our public endpoint is annotated with @Get:
@Controller("/")
public class CapitalController {
@Inject
public RestCountriesService restCountriesService;
@Get("/capital/{country}")
public String getCapital(@PathVariable @NotBlank String country) {
return "{\"capital\": \"" +
restCountriesService.getCapital(country) + "\"}";
}
}
As you can see, this controller injects a service, which is typically annotated with @Prototype:
@Prototype
public class RestCountriesService {
@Inject
public RestCountriesClient restCountriesClient;
public String getCapital(String country) {
returnOptional.ofNullable
(restCountriesClient.fetchCountry(country))
.flatMap(restCountriesResponses ->
restCountriesResponses.stream().findFirst())
.map(RestCountriesResponse::getCapital)
.orElseThrow(() -> new HttpStatusException(NOT_FOUND,
"Couldn't find country " + country));
}
}
@Prototype tells Micronaut to create a new instance of the annotated class on every injection point. This is equivalent to @Component in Spring Boot.
In order to carry out an HTTP call, Micronaut provides us with a feature called "declarative client", which consists of an interface that calls a given endpoint and maps the response to a DTO by means of Jackson:
@Client("${restCountries.apiUrl}")
interface RestCountriesClient {
@Get("/name/{country}")
List<RestCountriesResponse> fetchCountry(@PathVariable String
country);
}
This interface needs to be annotated with @Client and the base URL of the endpoint we want to call. In this case, we defined this URL in an application.yml file, just as one would do it in Spring Boot:
restCountries:
apiUrl: https://restcountries.eu/rest/v2
Then, we need to define a method for each endpoint which is annotated with the desired HTTP method and optionally a path that is appended to the base URL, previously defined in @Client. Additional parameters such as headers or query parameters must be provided to the method.
You can find the complete list of possible parameters here: https://docs.micronaut.io/latest/guide/index.html#binding
After calling fetchCountry(), the class RestCountriesResponse will be already initialised with the values from the request's response body.
As we want to deploy our service using Serverless, we first need to install it using npm:
npm install
Then, we deploy the service using this script: deploy.sh
This will run the Gradle build and deploy the service to AWS. As a result, we'll be able to call our endpoint from anywhere in the Internet.
While working with Micronaut we realized that it’s easy to learn, especially having previous experience with other frameworks such as Spring Boot and it's well documented, with lots of examples and guides.
Although we haven’t talked about it in this article, we also used GraalVM, which is a very promising solution to reduce Lambda cold starts. However, it is a rather complex solution which is certainly not easy to set up, particularly if third-party libraries are required.
The whole application together with tests is available in GitHub from the link below.
We have received your feedback.