Micronaut AWS

Provides integration between Micronaut and Amazon Web Services (AWS)

Version:

1 Introduction

This project provides various extensions to Micronaut for Amazon Web Services (AWS).

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.

2 What's new?

Micronaut AWS 2.0.0 contains improvements to ease the creation of Alexa Skills with Micronaut:

  • Support for AWS SDK v2.

  • Improvements to GraalVM support on Lambda.

  • Creation of Alexa Skills as http services.

  • Creation of Flash Briefing skills.

  • Easier creation of SSML.

Check the Alexa section to learn more.

See the section Breaking Changes to ease into the migration to Micronaut AWS 2.x

3 Amazon Correto

Amazon Corretto is an OpenJDK distribution that provides free, long-term support with no pay-gated features or restrictions on how it’s used in production. Corretto is used by thousands of Amazon workloads; for example, it’s the JDK used by the AWS Lambda java11 runtime, which provides insights Amazon uses to push improvements upstream.

4 AWS SDK v1

To use the AWS SDK v1, add the following dependency:

implementation("io.micronaut.aws:micronaut-aws-sdk-v1")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-aws-sdk-v1</artifactId>
</dependency>

4.1 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();

5 AWS SDK v2

To use the AWS SDK v2, add the following dependency:

implementation("io.micronaut.aws:micronaut-aws-sdk-v2")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-aws-sdk-v2</artifactId>
</dependency>

By default, the AWS SDK v2 will pull transitively both the Netty (async) and Apache HTTP (sync) clients. If you wish to use a client based on the JVM’s lightweight URLConnection, you should configure it as explained below.

URLConnection client

To use the URLCnnection-based client, you should exclude the other clients from the classpath:

Maven
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <exclusions>
        <exclusion>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>apache-client</artifactId>
        </exclusion>
        <exclusion>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>netty-nio-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>url-connection-client</artifactId>
</dependency>
Gradle
implementation "software.amazon.awssdk:s3", {
    exclude group: "software.amazon.awssdk", module: "apache-client"
    exclude group: "software.amazon.awssdk", module: "netty-nio-client"
}
implementation "software.amazon.awssdk:url-connection-client"

Then, you can configure it with the following configuration properties:

🔗
Table 1. Configuration Properties for UrlConnectionClientConfiguration
Property Type Description

aws.url-connection-client.socket-timeout

java.time.Duration

aws.url-connection-client.connection-timeout

java.time.Duration

If you don’t exclude the other clients from the classpath, you still can configure which one is used by setting the following JVM system properties:

  • software.amazon.awssdk.http.service.impl. Possible values:

    • software.amazon.awssdk.http.urlconnection.UrlConnectionSdkHttpService for the URLConnection based client.

    • software.amazon.awssdk.http.apache.ApacheSdkHttpService for the Apache HTTP client (if in the classpath).

  • software.amazon.awssdk.http.async.service.impl. Possible values:

    • software.amazon.awssdk.http.nio.netty.NettySdkAsyncHttpService for the Netty client (if in the classpath).

Apache HTTP client

The Apache HTTP client can be configured with the following options:

🔗
Table 2. Configuration Properties for ApacheClientConfiguration
Property Type Description

aws.apache-client.socket-timeout

java.time.Duration

aws.apache-client.connection-timeout

java.time.Duration

aws.apache-client.connection-acquisition-timeout

java.time.Duration

aws.apache-client.max-connections

java.lang.Integer

aws.apache-client.local-address

java.net.InetAddress

aws.apache-client.expect-continue-enabled

java.lang.Boolean

aws.apache-client.connection-time-to-live

java.time.Duration

aws.apache-client.connection-max-idle-time

java.time.Duration

aws.apache-client.use-idle-connection-reaper

java.lang.Boolean

aws.apache-client.proxy.endpoint

java.net.URI

aws.apache-client.proxy.username

java.lang.String

aws.apache-client.proxy.password

java.lang.String

aws.apache-client.proxy.ntlm-domain

java.lang.String

aws.apache-client.proxy.ntlm-workstation

java.lang.String

aws.apache-client.proxy.non-proxy-hosts

java.util.Set

aws.apache-client.proxy.add-non-proxy-host

java.lang.String

aws.apache-client.proxy.preemptive-basic-authentication-enabled

java.lang.Boolean

aws.apache-client.proxy.use-system-property-values

java.lang.Boolean

Netty client

The Netty client can be configured with the following options:

🔗
Table 3. Configuration Properties for NettyClientConfiguration
Property Type Description

aws.netty-client.max-concurrency

java.lang.Integer

aws.netty-client.max-pending-connection-acquires

java.lang.Integer

aws.netty-client.read-timeout

java.time.Duration

aws.netty-client.write-timeout

java.time.Duration

aws.netty-client.connection-timeout

java.time.Duration

aws.netty-client.connection-acquisition-timeout

java.time.Duration

aws.netty-client.connection-time-to-live

java.time.Duration

aws.netty-client.connection-max-idle-time

java.time.Duration

aws.netty-client.use-idle-connection-reaper

java.lang.Boolean

aws.netty-client.protocol

software.amazon.awssdk.http.Protocol

aws.netty-client.max-http2streams

java.lang.Integer

aws.netty-client.proxy.host

java.lang.String

aws.netty-client.proxy.port

int

aws.netty-client.proxy.scheme

java.lang.String

aws.netty-client.proxy.non-proxy-hosts

java.util.Set

Supplying AWS credentials

By default, AWS SDK v2 will attempt to find AWS credentials from the following places:

  1. Java system properties: aws.accessKeyId and aws.secretAccessKey.

  2. Environment variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

  3. The default credential profiles file: ~/.aws/credentials.

  4. Amazon ECS container credentials, loaded from Amazon ECS if the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set.

  5. Instance profile credentials, used on Amazon EC2 instances, and delivered through the Amazon EC2 metadata service.

For more information, check the AWS documentation.

If you still want to specify the credentials via configuration, you can do so in application.yml:

aws:
  accessKeyId: your_access_key_id_here
  secretKey: your_secret_key_id_here
  sessionToken: your_session_token_here

AWS region selection

By default, AWS SDK v2 will attempt to determine the AWS region in the following ways:

  1. Environment variable: AWS_REGION.

  2. The default shared configuration file: ~/.aws/config.

  3. Amazon EC2 instance metadata service.

For more information, check the AWS documentation.

If you still want to specify the region via configuration, you can do so in application.yml:

aws:
  region: eu-west-1

Third-party libraries

If you are looking for a higher-level API, check out Agorapulse’s AWS SDK for Micronaut.

5.1 S3

To use an S3 client, add the following dependency:

implementation("software.amazon.awssdk:s3")
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
</dependency>

Then, the following beans will be created:

  • software.amazon.awssdk.services.s3.S3ClientBuilder

  • software.amazon.awssdk.services.s3.S3Client.

And:

  • software.amazon.awssdk.services.s3.S3AsyncClientBuilder

  • software.amazon.awssdk.services.s3.S3AsyncClient.

The HTTP client, credentials and region will be configured as per described in the SDK v2 documentation.

Additionally, this service accepts the following configuration properties:

🔗
Table 1. Configuration Properties for S3ConfigurationProperties
Property Type Description

aws.s3.dualstack-enabled

java.lang.Boolean

aws.s3.accelerate-mode-enabled

java.lang.Boolean

aws.s3.path-style-access-enabled

java.lang.Boolean

aws.s3.checksum-validation-enabled

java.lang.Boolean

aws.s3.chunked-encoding-enabled

java.lang.Boolean

aws.s3.use-arn-region-enabled

java.lang.Boolean

aws.s3.profile-name

java.lang.String

5.2 Dynamo DB

To use a DynamoDb client, add the following dependency:

implementation("software.amazon.awssdk:dynamodb")
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>dynamodb</artifactId>
</dependency>

Then, the following beans will be created:

  • software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder

  • software.amazon.awssdk.services.dynamodb.DynamoDbClient.

And:

  • software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClientBuilder

  • software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient.

The HTTP client, credentials and region will be configured as per described in the SDK v2 documentation.

5.3 SES

To use a SES client, add the following dependency:

implementation("software.amazon.awssdk:ses")
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>ses</artifactId>
</dependency>

Then, the following beans will be created:

  • software.amazon.awssdk.services.ses.SesClientBuilder

  • software.amazon.awssdk.services.ses.SesClient.

And:

  • software.amazon.awssdk.services.ses.SesAsyncClientBuilder

  • software.amazon.awssdk.services.ses.SesAsyncClient.

The HTTP client, credentials and region will be configured as per described in the SDK v2 documentation.

5.4 SNS

To use a SNS client, add the following dependency:

implementation("software.amazon.awssdk:sns")
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>sns</artifactId>
</dependency>

Then, the following beans will be created:

  • software.amazon.awssdk.services.sns.SnsClientBuilder

  • software.amazon.awssdk.services.sns.SnsClient.

And:

  • software.amazon.awssdk.services.sns.SnsAsyncClientBuilder

  • software.amazon.awssdk.services.sns.SnsAsyncClient.

The HTTP client, credentials and region will be configured as per described in the SDK v2 documentation.

5.5 SQS

To use a SQS client, add the following dependency:

implementation("software.amazon.awssdk:sqs")
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>sqs</artifactId>
</dependency>

Then, the following beans will be created:

  • software.amazon.awssdk.services.sqs.SqsClientBuilder

  • software.amazon.awssdk.services.sqs.SqsClient.

And:

  • software.amazon.awssdk.services.sqs.SqsAsyncClientBuilder

  • software.amazon.awssdk.services.sqs.SqsAsyncClient.

The HTTP client, credentials and region will be configured as per described in the SDK v2 documentation.

5.6 Advanced configuration

For advanced configuration options that are not suitable to provide via application.yml, you can declare a BeanCreatedEventListener bean that listens for builder bean creations, and apply any further customisation there:

@Singleton
public class S3ClientBuilderListener implements BeanCreatedEventListener<S3ClientBuilder> {

    @Override
    public S3ClientBuilder onCreated(BeanCreatedEvent<S3ClientBuilder> event) {
        S3ClientBuilder builder = event.getBean();
        builder.overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RetryMode.LEGACY).build());

        return builder;
    }
}

5.7 Other services

Since the list of services offered by AWS is huge, you can write your own client support and leverage the foundation classes that support the services supported by Micronaut.

To do so, you would create a @Factory class that would get injected some other beans to do its job.

For example, to create a client for AWS Rekognition:

@Factory
public class RekognitionClientFactory extends AwsClientFactory<RekognitionClientBuilder, RekognitionAsyncClientBuilder, RekognitionClient, RekognitionAsyncClient> {

    protected RekognitionClientFactory(AwsCredentialsProviderChain credentialsProvider, AwsRegionProviderChain regionProvider) {
        super(credentialsProvider, regionProvider);
    }

    // Sync client
    @Override
    protected RekognitionClientBuilder createSyncBuilder() { (1)
        return RekognitionClient.builder();
    }

    @Override
    @Singleton
    public RekognitionClientBuilder syncBuilder(SdkHttpClient httpClient) { (2)
        return super.syncBuilder(httpClient);
    }

    @Override
    @Bean(preDestroy = "close")
    public RekognitionClient syncClient(RekognitionClientBuilder builder) { (3)
        return super.syncClient(builder);
    }

    // Async client
    @Override
    protected RekognitionAsyncClientBuilder createAsyncBuilder() { (1)
        return RekognitionAsyncClient.builder();
    }

    @Override
    @Singleton
    @Requires(beans = SdkAsyncHttpClient.class)
    public RekognitionAsyncClientBuilder asyncBuilder(SdkAsyncHttpClient httpClient) { (2)
        return super.asyncBuilder(httpClient);
    }

    @Override
    @Bean(preDestroy = "close")
    @Requires(beans = SdkAsyncHttpClient.class)
    public RekognitionAsyncClient asyncClient(RekognitionAsyncClientBuilder builder) { (3)
        return super.asyncClient(builder);
    }
}
1 This method needs to be implemented so that the parent factory class knows how to create the builder. You may apply additional customisations to the builder here.
2 This method gives a chance to register a BeanCreaterEventListener over the builder, so that any builder can be customised. Needs to be overriden to apply the @Singleton annotation.
3 This method builds the client. Needs to be overriden to apply the @Bean annotation.

6 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 this:

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

@Singleton
public class EchoFunction extends FunctionInitializer { (1)

    @Inject GreetingService greetingService; (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.echo(context.get(Message.class)));
    }
}
1 The function should 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.

6.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:

implementation("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

6.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.*;
import org.slf4j.*;

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

    private Logger log = LoggerFactory.getLogger(PingController.class);

    @Get("/")
    public String ping() {
        log.trace("Received a ping.");
        return "{\"pong\":true}";
    }
}

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

implementation("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.services.lambda.runtime.*;
import io.micronaut.function.aws.proxy.MicronautLambdaContainerHandler;
import java.io.*;

public class StreamLambdaHandler implements RequestStreamHandler {

    private MicronautLambdaContainerHandler handler;

    public StreamLambdaHandler() {
        try {
            handler = new MicronautLambdaContainerHandler(); (1)
        } 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 constructor
2 The API Proxy Request is handled

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

The MicronautLambdaContainerHandler constructor also accepts a ApplicationContextBuilder instance which you can use to further customize application bootstrap.

Using the CLI

To create an AWS API Gateway Proxy application:

$ mn create-app my-app --features aws-lambda

Or with Micronaut Launch

$ curl https://launch.micronaut.io/example.zip\?features\=aws-lambda -o example.zip
$ unzip example.zip -d example

Micronaut API Gateway Proxy builds on top of AWS Servless Java Container. You can check a Pet Store sample application.

ObjectMapper configuration

By default, for efficiency reasons, Micronaut uses the default Jackson ObjectMapper to communicate with the AWS API proxy. However, if you have configured it for your application (for example, setting jackson.propertyNamingStrategy: SNAKE_CASE) in a way that it would be incompatible with the API proxy, you can set aws.proxy.shared-object-mapper: false, and Micronaut will create a brand new ObjectMapper for the API proxy.

If you wish to further configure this ObjectMapper, you can register a BeanCreatedEventListener<ObjectMapper> and filter based on event.getBeanDefinition() having an annotation like @Named("aws").

7 AWS Lambda Custom Runtime

Micronaut’s eases the deployment of your functions as a Custom AWS Lambda runtime:

A runtime is a program that runs a Lambda function’s handler method when the function is invoked. You can include a runtime in your function’s deployment package in the form of an executable file named bootstrap.

Why would you want to deploy your function as a Custom Runtime? For example, to use a different JVM than the one AWS provides or execute your function as GraalVM Native Image.

First, you need to add the micronaut-function-aws-custom-runtime dependency to your build:

implementation("io.micronaut.aws:micronaut-function-aws-custom-runtime")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-function-aws-custom-runtime</artifactId>
</dependency>

The main API you will interact with is AbstractMicronautLambdaRuntime. An abstract class which you can extend to create your custom runtime mainClass. That class includes the necessary code to perform the Processing Tasks described in the Custom Runtime documentation.

7.1 Custom Runtime for Handler functions

Lets describe a scenario with a handler which works with an input and output POJO.

package io.micronaut.docs;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.function.aws.MicronautRequestHandler;

@Introspected (1)
public class BookRequestHandler extends MicronautRequestHandler<Book, BookSaved> {

    @Override
    public BookSaved execute(Book input) {
        BookSaved bookSaved = new BookSaved();
        bookSaved.setName(input.getName());
        bookSaved.setIsbn("XXX");
        return bookSaved;
    }
}
1 Annotate your handler with io.micronaut.core.annotation.Introspected so that Micronaut can instantiate the handler.
import edu.umd.cs.findbugs.annotations.NonNull;
import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.NotBlank;

@Introspected (1)
public class Book {

    @NonNull
    @NotBlank
    private String name;

    public Book() {
    }

    // Getters & Setters
1 Annotate your POJOs with io.micronaut.core.annotation.Introspected for reflection-free JSON serialization and deserialization.
import edu.umd.cs.findbugs.annotations.NonNull;
import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.NotBlank;

@Introspected (1)
public class BookSaved {

    @NonNull
    @NotBlank
    private String name;

    @NonNull
    @NotBlank
    private String isbn;

    public BookSaved() {

    }

    // Getters & Setters
1 Annotate your POJOs with io.micronaut.core.annotation.Introspected for reflection-free JSON serialization and deserialization.
In the AWS Console, as Handler value you will set io.micronaut.docs.BookRequestHandler.

bootstrap

You will deploy your custom runtime code as a ZIP file. At the root of the ZIP file you need a bootstrap file.

If there’s a file named bootstrap in your deployment package, Lambda executes that file.

Create a bootstrap bash script:

Example bootstrap
#!/bin/sh
set -euo pipefail
java -XX:TieredStopAtLevel=1 -noverify -cp demo.jar io.micronaut.docs.BookLambdaRuntime

Create the class referenced in the previous bootstrap file:

package io.micronaut.docs;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import io.micronaut.function.aws.runtime.AbstractMicronautLambdaRuntime;

import java.net.MalformedURLException;

public class BookLambdaRuntime
        extends AbstractMicronautLambdaRuntime<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent, Book, BookSaved> {
    public static void main(String[] args) {
        try {
            new BookLambdaRuntime().run(args);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected Object createHandler(String... args) {
        return new BookRequestHandler();
    }
}

The above class is an example of a Lambda whose trigger is an API Gateway event.

7.2 Deploy a Custom Runtime

You can deploy it via the AWS console or the AWS CLI. You will need to create a zip bundle including the bootstrap and your application JAR file (in the above case named demo.jar).

If you use the AWS CLI, specify a runtime of provided:

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.

7.3 Custom runtime for AWS API Gateway Proxy functions

If you build your application with the API Gateway approach. You don’t need to create a class which extends AbstractMicronautLambdaRuntime you can use MicronautLambdaRuntime.

The bootstrap bash script will reference that class:

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

In addition to the dependencies to micronaut-function-aws-aws-custom-runtime, you will need to include dependencies to the micronaut-function-aws-api-proxy module.

7.4 Custom runtime for Function as a GraalVM Native Image

To achieve the absolutely best cold startup time you can create a function that is compiled into a GraalVM native image and then run it from the bootstrap script.

You need several pieces in place:

GraalVM native-image.properties

First you will need to create a native-image.properties file:

src/main/resources/META-INF/io/micronaut/docs/native-image.properties
Args = -H:IncludeResources=logback.xml|application.yml \
       -H:Name=demo (1)
       -H:Class=io.micronaut.docs.BookLambdaRuntime (2)
1 This is the native image name. You reference this name in the Dockerfile which builds the native image
2 This is the mainClass of your app.

A Dockerfile to build a GraalVM native image

Create a Dockerfile which:

  • Uses the amazonlinux image

  • Builds the JAR of the function.

  • Install the necessary dependencies.

  • Downloads GraalVM community edition

  • Installs native-image utility.

  • With the native-image command and the JAR, generates a GraalVM native image

  • Bundles the native image of our function and the bootstrap file into a ZIP file.

Dockerfile to generate a native image
FROM gradle:6.3.0-jdk8 as builder
COPY --chown=gradle:gradle . /home/application
WORKDIR /home/application
RUN ./gradlew build --no-daemon

FROM amazonlinux:2018.03.0.20191014.0 as graalvm

ENV LANG=en_US.UTF-8

RUN yum install -y gcc gcc-c++ libc6-dev  zlib1g-dev curl bash zlib zlib-devel zip

ENV GRAAL_VERSION 20.0.0
ENV JDK_VERSION java8
ENV GRAAL_FILENAME graalvm-ce-${JDK_VERSION}-linux-amd64-${GRAAL_VERSION}.tar.gz

RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${GRAAL_VERSION}/${GRAAL_FILENAME} -o /tmp/${GRAAL_FILENAME}

RUN tar -zxvf /tmp/${GRAAL_FILENAME} -C /tmp \
    && mv /tmp/graalvm-ce-${JDK_VERSION}-${GRAAL_VERSION} /usr/lib/graalvm

RUN rm -rf /tmp/*
CMD ["/usr/lib/graalvm/bin/native-image"]

FROM graalvm
COPY --from=builder /home/application/ /home/application/
WORKDIR /home/application
RUN /usr/lib/graalvm/bin/gu install native-image
RUN /usr/lib/graalvm/bin/native-image --no-server -cp build/libs/demo-*-all.jar
RUN chmod 777 bootstrap
RUN chmod 777 demo (1)
RUN zip -j function.zip bootstrap demo (1)
EXPOSE 8080
ENTRYPOINT ["/home/application/demo"]
1 It matches the name used in the native-image.properties file.

Execute the native image from bootstrap

Invoke the native image from the bootstrap script:

Example bootstrap with native image
#!/bin/sh
set -euo pipefail
./demo -Xmx128m -Djava.library.path=$(pwd)

A script to build the function ZIP

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

deploy.sh
#!/bin/bash
docker build . -t demo
mkdir -p build
docker run --rm --entrypoint cat demo  /home/application/function.zip > build/function.zip

8 Alexa Support

The Micronaut’s aws-alexa module simplifies development of Alexa Skills with Java, Kotlin or Groovy.

implementation("io.micronaut.aws:micronaut-aws-alexa")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-aws-alexa</artifactId>
</dependency>

To create the sample skill described in Amazon Documentation - Develop your first skill with Micronaut’s Alexa you will write the same LaunchRequestHandler, HelloWorldIntent, HelpIntent, CancelandStopHandler, FallbackIntentHandler, SessionEndedRequestHandler handlers.

You will do just one change, you will annotate those handlers with javax.inject.Singleton.

import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.RequestHandler;
import com.amazon.ask.model.Response;
import com.amazon.ask.request.Predicates;

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

@Singleton (1)
public class HelloWorldIntentHandler implements RequestHandler {

    @Override
    public boolean canHandle(HandlerInput input) {
        return input.matches(Predicates.intentName("HelloWorldIntent"));
    }

    @Override
    public Optional<Response> handle(HandlerInput input) {
        String speechText = "Hello world";
        return input.getResponseBuilder()
                .withSpeech(speechText)
                .withSimpleCard("HelloWorld", speechText)
                .build();
    }

}
1 The Singleton scope indicates only one instance of the bean should exist in the Micronaut’s Bean Context

Typically, the next step will be to provide an instance of AlexaSkillConfiguration. The easiest way to do that is to configure the skill id via configuration:

alexa:
  skills:
    myskill:
      skill-id 'xxxx-yaaa-zz123'

Micronaut’s alexa module provides by default StandardSkillBuilderProvider which creates an SDK instance using the Skills.standard builder. You can provide your own implementation of Micronaut’s SkillBuilderProvider.

For each AlexaSkillConfiguration bean, Micronaut uses the builder provided by [SkillBuilderProvider to create for you a bean of type AlexaSkill for you and wires up the beans of the following types:

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

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

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

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

  • com.amazon.ask.builder.SkillBuilder

8.1 SSML Builder

Micronaut Alexa ships with a Speech Systhesys Markup Language builder.

new Ssml().speak(new Ssml("Welcome to Ride Hailer. ").audio('soundbank://soundlibrary/transportation/amzn_sfx_car_accelerate_01').build()).build() == '<speak>Welcome to Ride Hailer. <audio src="soundbank://soundlibrary/transportation/amzn_sfx_car_accelerate_01"/></speak>'

8.2 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:

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

8.3 Alexa Skill as a Web Service

The micronaut-aws-webservice is a fork of Amazon Servlet module. It allow you to run your Alexa Skill backend logic in Micronaut applications deployed with a netty or servlet runtimes.

Just by including the dependency:

implementation("io.micronaut.aws:micronaut-aws-alexa-httpserver")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-aws-alexa-httpserver</artifactId>
</dependency>

you get an POST endpoint at /alexa (the route is configurable via alexa.endpoint.path.

You can configure your Skill’s endpoint under Build/Endpoint in the Alexa developer console.

8.4 Alexa Skill as an AWS Lambda Function

The micronaut-function-aws-alexa module includes support for building deploying an Alexa Skill as a Lambda Function.

implementation("io.micronaut.aws:micronaut-function-aws-alexa")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-function-aws-alexa</artifactId>
</dependency>

As handler specify io.micronaut.function.aws.alexa.AlexaFunction. You don’t need to create a class which extends SkillStreamHandler, AlexaFunction takes care of adding request handlers interceptors etc.

8.5 Flash Briefings

Micronaut’s eases the creation of Flash Briefing Skills.

You can create a flash briefing skill to provide Alexa customers with news headlines and other short content. Typically a flash briefing becomes a part of a customer’s daily routine.

Your application must expose an endpoint which returns a JSON Array of FlashBriefingItem

package io.micronaut.aws.alexa.flashbriefing;

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

import javax.validation.Validator;
import java.util.List;
import java.util.stream.Collectors;

@Controller("/news")
public class FlashBriefingsController {

    private final Validator validator;
    private final FlashBriefingRepository flashBriefingRepository;

    public FlashBriefingsController(Validator validator,
                                    FlashBriefingRepository flashBriefingRepository) {
        this.validator = validator;
        this.flashBriefingRepository = flashBriefingRepository;
    }

    @Get (1)
    public List<FlashBriefingItem> index() {
        return flashBriefingRepository.find()
                .stream()
                .filter(item -> validator.validate(item).isEmpty()) (2)
                .sorted() (3)
                .limit(5) (4)
                .collect(Collectors.toList());
    }

}
1 By default, Micronaut sets the response HTTP Header Content-Type with the value application-json.
2 Flash Briefing Feed items must be valid according to Flash Briefing Skill API Feed Reference constraints.
3 Items should be provided in order from newest to oldest, based on the date value for the item. Alexa may ignore older items.
4 Flash Briefing Skill API Feed Reference instructs to provide between 1 and 5 unique items at a time.

9 Repository

You can find the source code of this project in this repository:

10 Breaking Changes

Class changes

AlexaFunction has been refactored to ease extension.

Table 1. Classes renamed
Old Class Name New Class

io.micronaut.function.aws.alexa.AlexaConfiguration

io.micronaut.aws.alexa.conf.AlexaSkillConfiguration

Table 2. Classes relocated
Class Name Old Package New Package

IntentHandler

io.micronaut.function.aws.alexa.annotation

io.micronaut.aws.alexa.annotation

AnnotatedRequestHandler

io.micronaut.function.aws.alexa.handlers

io.micronaut.aws.alexa.handlers

AlexaIntents

io.micronaut.function.aws.alexa

io.micronaut.aws.alexa.conf

Artifact changes

Now all artifacts are published under the io.micronaut.aws group id.

Table 3. Artifacts relocated
Old artifact New artifact

io.micronaut:micronaut-function-aws

io.micronaut.aws:micronaut-function-aws

Content negotiation

When using AWS API Proxy, the new support for server side content negotiation may require changes to tests. For example a test that makes a call such as:

String result = client.toBlocking().retrieve(
    HttpRequest.GET("/test")
        .accept("text/plain"), String)

If the server implementation does not declare the route as @Produces("text/plain") the request won’t match.

11 Release History

For more information, check the GitHub release list.

2.0.1
  • Fixed support for API Versioning with @Version.

2.0
  • Upgrade to Micronaut 2.

  • Support for the AWS SDK v2

  • Improved support for Alexa Skills.

1.4

Setting Micronaut 1.2 as the minimum supported version

1.3
  • Improved support for GraalVM native images

  • Upgrade to aws-serverless-java-container-core 1.4

1.2

Compatibility with Micronaut 1.2

1.1

Compatibility with Micronaut 1.1

1.0

Initial Release