mn create-function-app example.micronaut.micronautguide --features=aws-lambda --build=gradle --lang=java
Micronaut AWS Lambda and S3 Event
Learn how to generate thumbnails for images uploaded to an S3 bucket with AWS Lambda and the Micronaut framework
Authors: Sergio del Amo
Micronaut Version: 3.9.2
1. Getting Started
In this guide, we will create a Micronaut application written in Java.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 1.8 or greater installed with
JAVA_HOME
configured appropriately
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the App
Create an application using the Micronaut Command Line Interface or with Micronaut Launch.
If you don’t specify the --build argument, Gradle is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.
|
If you use Micronaut Launch, select serverless function as application type and add the aws-lambda-s3-event-notification
feature.
The previous command creates a Micronaut application with the default package example.micronaut
in a directory named micronautguide
.
4.1. Thumbnail Configuration
Add the following configuration:
thumbnail:
width: 256
height: 256
Create an interface to encapsulate configuration:
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
public interface ThumbnailConfiguration {
@NonNull
Integer getWidth();
@NonNull
Integer getHeight();
}
package example.micronaut;
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.NonNull;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@ConfigurationProperties("thumbnail") (1)
public class ThumbnailConfigurationProperties implements ThumbnailConfiguration {
@NonNull
@NotNull (2)
private Integer width;
@NonNull
@NotNull (2)
private Integer height;
@Override
@NonNull
public Integer getWidth() {
return width;
}
public void setWidth(@NonNull Integer width) {
this.width = width;
}
@Override
@NonNull
public Integer getHeight() {
return height;
}
public void setHeight(@NonNull Integer height) {
this.height = height;
}
}
1 | The @ConfigurationProperties annotation takes the configuration prefix. |
2 | You can use validation constraints in the @ConfigurationProperties objects. |
4.2. Thumbnail Generation
Create a contract for thumbnail generation. We leverage the @Pattern
annotation to accept only jpg
and png
files.
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.io.InputStream;
import java.util.Optional;
public interface ThumbnailGenerator {
@NonNull
Optional<byte[]> thumbnail(@NonNull InputStream inputStream,
@NonNull @NotBlank @Pattern(regexp = "jpg|png") String format);
}
Add a dependency to Thumbnailator - a thumbnail generation library for Java.
implementation("net.coobird:thumbnailator:@thumbnailatorVersion@")
Create an implementation of ThumbnailGenerator
which uses Thumbnailator
.
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import net.coobird.thumbnailator.Thumbnails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.inject.Singleton;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
@Singleton (1)
public class ThumbnailatorThumbnailGenerator implements ThumbnailGenerator {
private static final Logger LOG = LoggerFactory.getLogger(ThumbnailatorThumbnailGenerator.class);
private final ThumbnailConfiguration thumbnailConfiguration;
public ThumbnailatorThumbnailGenerator(ThumbnailConfiguration thumbnailConfiguration) { (2)
this.thumbnailConfiguration = thumbnailConfiguration;
}
@Override
@NonNull
public Optional<byte[]> thumbnail(@NonNull InputStream inputStream, @NonNull @NotBlank @Pattern(regexp = "jpg|png") String format) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
Thumbnails.of(inputStream)
.size(thumbnailConfiguration.getWidth(), thumbnailConfiguration.getHeight())
.outputFormat(format)
.toOutputStream(byteArrayOutputStream);
return Optional.of(byteArrayOutputStream.toByteArray());
} catch (IOException e) {
LOG.warn("IOException thrown while generating the thumbnail");
}
return Optional.empty();
}
}
1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
2 | Use constructor injection to inject a bean of type ThumbnailConfiguration . |
4.3. Handler
When you select the feature aws-lambda-s3-event-notification
, the application build includes the following dependency:
implementation("com.amazonaws:aws-lambda-java-events:3.11.2")
To be able to inject an S3Client
Add the following dependencies:
implementation("io.micronaut.aws:micronaut-aws-sdk-v2")
implementation("software.amazon.awssdk:s3")
The handler receives an S3 notification event and for each S3 event of type ObjectCreated
creates a thumbnail if the S3 object is a PNG or JPG in the thumbnails
folder of the same S3 bucket which triggered the event.
package example.micronaut;
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;
import io.micronaut.serde.annotation.Serdeable;
import io.micronaut.function.aws.MicronautRequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import jakarta.inject.Inject;
import java.util.Locale;
@Serdeable
public class FunctionRequestHandler
extends MicronautRequestHandler<S3EventNotification, Void> { (1)
private static final Logger LOG = LoggerFactory.getLogger(FunctionRequestHandler.class);
private static final String SLASH = "/";
private static final String THUMBNAILS = "thumbnails";
public static final String OBJECT_CREATED = "ObjectCreated";
private static final String JPG = "jpg";
private static final String PNG = "png";
private static final char DOT = '.';
@Inject
public S3Client s3Client; (2)
@Inject
public ThumbnailGenerator thumbnailGenerator; (3)
@Override
public Void execute(S3EventNotification input) {
for (S3EventNotification.S3EventNotificationRecord record : input.getRecords()) {
LOG.info("event name: {}" , record.getEventName());
if (record.getEventName().contains(OBJECT_CREATED)) { (4)
S3EventNotification.S3Entity s3Entity = record.getS3();
String bucket = s3Entity.getBucket().getName();
String key = s3Entity.getObject().getKey();
int index = key.lastIndexOf(DOT);
if (index != -1) {
String format = key.substring(index + 1).toLowerCase(Locale.ENGLISH);
if (format.equals(PNG) || format.equals(JPG)) {
thumbnailGenerator.thumbnail(s3Client.getObject(GetObjectRequest.builder()
.bucket(bucket)
.key(key)
.build()), format)
.ifPresent(bytes -> s3Client.putObject(PutObjectRequest.builder()
.key(thumbnailKey(key))
.bucket(bucket)
.build(), RequestBody.fromBytes(bytes)));
}
}
}
}
return null;
}
private static String thumbnailKey(String key) {
int index = key.lastIndexOf(SLASH);
String fileName = index != -1 ? key.substring(index + SLASH.length()) : key;
return THUMBNAILS + SLASH + fileName;
}
}
1 | We want to handle S3EventNotification events. |
2 | Injection for S3Client . |
3 | Injection for ThumbnailGenerator . |
4 | Extra verification that the event is a ObjectCreated event. |
5. S3
Create an S3 bucket.
Inside the bucket create two folders thumbnails
and images
.
6. Lambda
Create a Lambda Function. As a runtime, select Java 11 (Correto).
6.1. IAM Role
Grant permissions to access the S3 bucket to the IAM role associated with the Lambda.
6.2. Triggers
Create two S3 trigger one for PNGs and one for JPGs:
Configure the trigger to:
-
Listen only to PUT events.
-
Only for files uploaded to the
images
folder -
Only for file suffixed
jpg
6.3. Upload Code
Create an executable jar including all dependencies:
./gradlew shadowJar
Upload it:
6.4. Handler
In the AWS Console, as Handler, set:
example.micronaut.FunctionRequestHandler
6.5. Test
You can test it easily; upload a JPG file to the images folder of the S3 bucket you created. You can do this directly in the AWS Console. In a few seconds, you will see a thumbnail saved in the thumbnails
folder of the bucket.
Read more about:
7. Help with the Micronaut Framework
The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.