Using Guice with AWS Lambda
2022-10-16T10:00:00.00Z

The Problem

AWS Lambda's java runtime environment takes a handler string like "my.package.MyClass::handleRequest" which tells it to

  1. Instantiate "MyClass" using the default constructor and
  2. call "handleRequest" on it passing input, output, and a context object.

Annoyingly, there is no hook in the runtime for inserting a dependency injection mechanism (like guice) meaning lambda will only create your object if it has a default construct.

The direct result is a lot of lambda implementations just directly create all their dependencies right inline including many examples in the AWS documentation

public class Example1 {

  public void handleRequest(S3ObjectLambdaEvent event, Context context) throws Exception {
    //hardcoded client #1
    AmazonS3 s3Client = AmazonS3Client.builder().build();

    ...

    //Another hardcoded client :/
    HttpClient httpClient = HttpClient.newBuilder().build();
    var presignedResponse = httpClient.send(
      HttpRequest.newBuilder(new URI(event.inputS3Url())).GET().build(),
      HttpResponse.BodyHandlers.ofInputStream());

    s3Client.writeGetObjectResponse(new WriteGetObjectResponseRequest()
      .withRequestRoute(event.outputRoute())
      .withRequestToken(event.outputToken())
      .withInputStream(presignedResponse.body()));
  }
}

A Solution

aws-lambda-guice allows you to define a Guice Module and a lambda, and a RequestHandler with a non-default constructor.

aws-lambda-guice will now use the provided module to create your handler.

public class ExampleModule extends AbstractModule {
  @Provides
  public S3Client s3() { 
    return AmazonS3Client.builder().build();
  }
  @Provides
  public HttpClient http() {
    return HttpClient.newBuilder().build();
  }
  public Example example(S3Client s3,
                         HttpClient http) {
    return new Example(s3, http);
  }
}
public class Example implements RequestHandler<S3ObjectLambdaEvent, Void>{

  private final S3Client s3;
  private final HttpClient http;

  public Example(S3Client s3, HttpClient http) {
    this.s3 = s3;
    this.http = http;
  }

  @Override
  public Void handleRequest(S3ObjectLambdaEvent event,
    Context context) {
    
    ...

    var presignedResponse = http.send(
      HttpRequest.newBuilder(new URI(event.inputS3Url())).GET().build(),
      HttpResponse.BodyHandlers.ofInputStream());

    s3.writeGetObjectResponse(new WriteGetObjectResponseRequest()
      .withRequestRoute(event.outputRoute())
      .withRequestToken(event.outputToken())
      .withInputStream(presignedResponse.body()));
    return null;
  }
}

With the following configuration:

  1. lambda handler : com.coderberry.aws.guice.GuiceRequestHandler::handleRequest
  2. CB_GUICE_MODULE = test.package.ExampleModule
  3. CB_HANDLER_CLASS = test.package.Example
Copyright 2022, Michael Smit