The documentation you are viewing is not the latest documentation of Micronaut Aws

Micronaut AWS

Provides integration between Micronaut and Amazon Web Services (AWS)

Version: 1.1.2

1 Introduction

This project provides various extensions to Micronaut for Amazon Web Services (AWS). The primary focus initially is on AWS Lambda, however other integrations may be included in this project in the future.

A base AWSClientConfiguration is provided which can be used as a base configuration class for any configuration that needs to configure an AWS SDK client.

Some features described in this release depend on Micronaut 1.1 which is at the development / milestone stage.

2 AWS Credentials Provider

When working with AWS SDK, you may need to provide a com.amazonaws.auth.AWSCredentialsProvider. To ease that this module provides a utility class: EnvironmentAWSCredentialsProvider.

For example the following snippet show how you may configure a S3 Client if you set two environment variables:

export AWS_ACCESS_KEY_ID=XXXX
export AWS_SECRET_KEY=YYYY
AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3ClientBuilder.standard();
  amazonS3ClientBuilder.setCredentials(new EnvironmentAWSCredentialsProvider(applicationContext.getEnvironment()));
AmazonS3 s3 = amazonS3ClientBuilder.build();

3 AWS Lambda Support

Regular Micronaut functions created via mn create-function can be deployed to Lambda directly.

Using the CLI
$ mn create-function example

For example, when using create-function hello Micronaut 1.1 and above will create a function that looks like:

package example;
import io.micronaut.function.executor.FunctionInitializer;
import javax.inject.*;

@Singleton
public class EchoFunction extends FunctionInitializer { (1)

    @Inject EchoService echoService; (2)

    public Message echo(Message msg) { (3)
        return greetingService.hello(msg);
    }

    public static void main(String...args) throws IOException { (4)
        EchoFunction function = new EchoFunction();
        function.run(args, (context)-> function.sayHello(context.get(Message.class)));
    }
}
1 The function shouuld be a simple class annotated with @Singleton that extends from FunctionInitializer and has no constructor arguments.
2 You can use dependency injection to inject fields.
3 The function should be defined as a public method, and accept a single argument that is a simple java.lang type or a POJO bean that can be serialized/deserialized to JSON.
4 You can optionally define a main method if you plan to deploy the function to other FaaS environments that use standard in/out if not you can remove the main method

The FunctionInitiazer super class will bootstrap Micronaut and perform injection on the instance prior to the echo method being executed.

An appropriate SAM definition for this function would be:

Resources:
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: example.EchoFunction::echo

The key part is the Handler which should be set to example.EchoFunction::echo which is the class name and method name combination. The main method is optional and can be removed if it is not needed. The main method allows the function to be executed from the cli, for example:

$ echo '{"name":"Fred"}' | java -jar function.jar

The above example pipes the JSON into system in which the functions reads and writes the result to system out. Certain FaaS systems such as OpenFaaS use system in/out for function inputs and outputs, hence with the main method in place you can deploy the function to such systems.

If you need further integration with what AWS Lambda has to offer then this project includes a number of parent classes and utilities to simplify working with the native Lambda APIs.

3.1 Micronaut Request Handlers

The micronaut-function-aws module includes two parent classes you can subclass to implement native AWS Lambda functionality. To get started first add the micronaut-function-aws dependency to your build:

compile 'io.micronaut:micronaut-function-aws'
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-function-aws</artifactId>
</dependency>

You can then use either MicronautRequestHandler or MicronautRequestStreamHandler as your parent class to implement your Lambda handler. For example:

Implementing MicronautRequestHandler
package example;

import io.micronaut.context.env.Environment;
import javax.inject.Inject;

public class RoundHandler extends MicronautRequestHandler<Float, Integer> { (1)

    @Inject
    MathService mathService; (2)

    @Inject
    Environment env;

    @Override
    public Integer execute(Float input) {
        return mathService.round(input); (3)
    }
}
1 The class extends MicronautRequestHandler and must have a default constructor.
2 You can use field injection to inject dependencies
3 The function implementation

With MicronautRequestHandler it is expected that you supply generic types with the input and the output types.

If you wish to work with raw streams then subclass MicronautRequestStreamHandler instead.

You should then configure the class name as the Lambda handler when deploying to AWS. For example with SAM:

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: example.RoundHandler::handleRequest

3.2 AWS API Gateway Support

An alternative approach to using Micronaut with AWS is to use Micronaut support for the AWS Serverless Java Container project.

Using the CLI with Micronaut 1.1 or above
$ mn create-app my-app --features aws-api-gateway

In this arrangement you build your application as a regular REST application, for example:

Example Controller
package example;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/ping")
public class PingController {

    @Get("/")
    public String ping() {
        return "{\"pong\":true}";
    }
}

You then need to add the micronaut-function-aws-api-proxy dependency:

compile 'io.micronaut.aws:micronaut-function-aws-api-proxy'
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-function-aws-api-proxy</artifactId>
</dependency>

You can then implement a RequestHandler that handles the API proxy request:

Example Stream Lambda Handler
package example;

import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.*;
import com.amazonaws.services.lambda.runtime.*;
import io.micronaut.function.aws.proxy.MicronautLambdaContainerHandler;
import java.io.*;

public class StreamLambdaHandler implements RequestStreamHandler {
    private static MicronautLambdaContainerHandler handler; (1)
    static {
        try {
            handler = MicronautLambdaContainerHandler.getAwsProxyHandler();
        } catch (ContainerInitializationException e) {
            // if we fail here. We re-throw the exception to force another cold start
            e.printStackTrace();
            throw new RuntimeException("Could not initialize Micronaut", e);
        }
    }

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
        handler.proxyStream(inputStream, outputStream, context); (2)
    }
}
1 Micronaut is initialized in the static initializer
2 The API Proxy Request is handled

The MicronautLambdaContainerHandler class is used to initialization Micronaut and handle the serverless requests.

The getAwsProxyHandler() method also accepts a ApplicationContextBuilder instance which you can use to further customize application bootstrap.

See the Example Application and associated README for instructions on how to deploy locally using AWS SAM Local and AWS Cloud Formation for production.

3.3 AWS Lambda Custom Runtimes

You may wish to implement a custom runtime, for example if you wish to use a different JVM than the one AWS provides.

The MicronautLambdaRuntime class provides an implementation that you can use to execute a custom runtime. You need to add the micronaut-function-aws-custom-runtime dependency to your build:

compile 'io.micronaut.aws:micronaut-function-aws-custom-runtime'
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-function-aws-custom-runtime</artifactId>
</dependency>

The application should be built with using the API Gateway approach.

Custom Java Runtimes

Then create a bootstrap bash script. The following is an example bootstrap

Example bootstrap
#!/bin/sh
set -euo pipefail
java -XX:TieredStopAtLevel=1 -noverify -cp server.jar io.micronaut.function.aws.runtime.MicronautLambdaRuntime

You then need to create a zip bundle including the bootstrap and your application JAR filed (in the above case named server.jar) for deployment to AWS and specify a runtime of provided. For example:

aws lambda create-function --function-name my-function \
--zip-file fileb://function.zip --handler function.handler --runtime provided \
--role arn:aws:iam::123456789:role/lambda_basic_execution
See Publishing a Custom Runtime for more information.

Custom GraalVM Native Runtimes

To achieve the absolutely best cold startup time you can create an API Gateway application that is compiled into a native image and then run using a custom AWS Lambda Runtime. The quickest way to get started is with the Micronaut 1.1 or above CLI:

Using the CLI
$ mn create-app my-app --features aws-api-gateway-graal

Then create a controller, for example:

Example Controller
package example;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/ping")
public class PingController {

    @Get("/")
    public String ping() {
        return "{\"pong\":true}";
    }
}

You can then use Docker to build a function ZIP file ready for deployment to AWS Lambda:

Building the Function ZIP file
$ docker build . -t my-app
$ mkdir build
$ docker run --rm --entrypoint cat my-app  /home/application/function.zip > build/function.zip

The application can then be deployed to Lambda either via the AWS console or the CLI:

Deploying the Function with the AWS CLI
$ aws lambda create-function --function-name my-app \
--zip-file fileb://build/function.zip --handler function.handler --runtime provided \
--role arn:aws:iam::881337894647:role/lambda_basic_execution
See the Example application on Github

Then you can invoke the function:

Invoking the Function with the AWS CLI
$ aws lambda invoke --function-name my-app --payload '{"resource": "/{proxy+}", "path": "/ping", "httpMethod": "GET"}' build/response.txt
$ cat build/response.txt

4 Alexa Skill Support

The micronaut-function-aws-alexa module includes support for building Alexa Skills with Micronaut:

compile 'io.micronaut.aws:micronaut-function-aws-alexa'
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-function-aws-alexa</artifactId>
</dependency>

In Micronaut 1.1 or above you can create an Alexa Skills application with mn create-function hello-alexa --provider alexa

When using Micronaut’s Alexa support you set your Lambda handler name to AlexaFunction class or a sublcass of this class if you plan to customize it.

The AlexaFunction will wire up your Alexa application and supports dependency injection of the following types:

  • com.amazon.ask.dispatcher.request.handler.RequestHandler

  • com.amazon.ask.dispatcher.request.interceptor.RequestInterceptor

  • com.amazon.ask.dispatcher.exception.ExceptionHandler

  • com.amazon.ask.builder.SkillBuilder

Simply declaring these types as beans will automatically configure the Alexa Skill appropriately.

To set the skill ID of your skill to enable skill verification, set the following property in Micronaut (application.yml or environment variable) under the property name 'alexa.skill.id'.

You can find sample applications in Java, Kotlin and Groovy in the Examples directory of the repository.

4.1 The IntentHandler Annotation

To simplify the programming model Micronaut AWS includes a @IntentHandler annotation that can be used on any bean method to make the method an intent handler.

The method must accept a single value of type com.amazon.ask.dispatcher.request.handler.HandlerInput and return a value of type Optional<com.amazon.ask.model.Response> otherwise a compilation error will occur.

A typical Alexa application written in Micronaut looks like:

import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.model.Response;
import io.micronaut.function.aws.alexa.AlexaIntents;
import io.micronaut.function.aws.alexa.annotation.IntentHandler;

import javax.inject.Singleton;
import java.util.Optional;

@Singleton (1)
public class AlexaApplication {

    public static final String INTENT_NAME = "HelloWorldIntent";

    private final MessageService messageService;

    public AlexaApplication(MessageService messageService) { (2)
        this.messageService = messageService;
    }

    @IntentHandler(INTENT_NAME) (3)
    public Optional<Response> greet(HandlerInput input) { (4)
        String speechText = messageService.sayHello();
        return input.getResponseBuilder()
                .withSpeech(speechText)
                .withSimpleCard("HelloWorld", speechText)
                .build();
    }
import com.amazon.ask.dispatcher.request.handler.HandlerInput
import com.amazon.ask.model.Response
import groovy.transform.CompileStatic
import io.micronaut.function.aws.alexa.AlexaIntents
import io.micronaut.function.aws.alexa.annotation.IntentHandler
import javax.inject.Singleton

@Singleton (1)
@CompileStatic
class AlexaApplication {

    private final MessageService messageService

    AlexaApplication(MessageService messageService) { (2)
        this.messageService = messageService
    }

    @IntentHandler("HelloWorldIntent") (3)
    Optional<Response> greet(HandlerInput input) { (4)
        String speechText = messageService.sayHello()
        return input.getResponseBuilder()
                .withSpeech(speechText)
                .withSimpleCard("HelloWorld", speechText)
                .build()
    }
import com.amazon.ask.dispatcher.request.handler.HandlerInput
import com.amazon.ask.model.Response
import io.micronaut.function.aws.alexa.AlexaIntents
import io.micronaut.function.aws.alexa.annotation.IntentHandler

import javax.inject.Singleton
import java.util.Optional

@Singleton (1)
class AlexaApplication(val messageService: MessageService) { (2)

    companion object {
        const val INTENT_NAME = "HelloWorldIntent"
    }

    @IntentHandler(INTENT_NAME) (3)
    fun greet(input : HandlerInput) : Optional<Response> { (4)
        val speechText = messageService.sayHello()
        return input.responseBuilder
                .withSpeech(speechText)
                .withSimpleCard("HelloWorld", speechText)
                .build()
    }
1 The javax.inject.Singleton annotation is used to define AlexApplication as a bean
2 Other services can be dependency injected into the constructor
3 The @IntentHandler is used to indicate which methods are intent handlers
4 The method receives a HandlerInput and returns a Response