Just Spring-enabled AWS Lambdas

engels

 

Ok, so you want to program your AWS Lambdas in Java and use Spring?

You have a few options, such as using Spring Cloud Functions or the AWS Serverless Java Container project by AWSLabs. Both of these projects are interesting in their own way; The first tries to save us from vendor lock-in by abstracting function implementations behind the java.util.function.Function interface (great idea!), while the latter proxies AWS-specific request objects to HttpServletRequest et al. such that you can write your code sort of as though you are not running inside AWS Lambda at all.

There are arguments in favour of both approaches. Especially when you are concerned about the lock-in, but what might bother you about these approaches is that there is also quite some ‘magic’ involved. So if you are looking for a simple way to enable Spring and use its dependency injection capabilities for your AWS Lambdas, read on..

Vanilla Lambda

When programming AWS Lambdas in Java you should create a class that implements the RequestHandler interface provided by the aws-lambda-java-core library to create your Lambda function handler. For example:


import java.util.Map;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class TestLambda implements RequestHandler<Map<String, Object>, String> {

    public String handleRequest(Map<String, Object> input, Context context) {
        return “hello world“;
    }
}

 

The platform instantiates an object that belongs to this class as the main entry point for Lambda. The time spent during this process is part of the infamous cold start. Every lambda invocation (cold or warm) will eventually end up in the handleRequest method.

Spring-enabled Lambda

In Spring we create @Component classes that are used to auto-detect and auto-configure beans using classpath scanning. The container then takes care of creating beans and injecting dependent beans (its dependencies).  The ApplicationContext ensures that you have a fully configured and executable system if all bean dependencies are resolved.

The catch in case of a AWS Lambda is that the Lambda function handler object is already created by the platform. So we might think that ship has sailed and need to create an indirection or other proxy, but we do not need to: 

import java.util.Map;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestLambda implements RequestHandler<Map, String> {

    static AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
        TestLambda.class.getPackage().getName());

    public TestLambda() {
        ctx.getAutowireCapableBeanFactory().autowireBean(this);
    }

    @Autowired
    private MessageService messsageService;

    public String handleRequest(Map input, Context context) {
        return messsageService.getMessage();
    }
}

TestLambda.java

import org.springframework.stereotype.Component;

@Component
public class MessageService {

	public String getMessage() {
		return "hello world";
	}
}

MessageService.java

As you can see in the constructor of the TestLambda class, the handler object created by AWS is wired as a bean by the ApplicationContext while being initialized to allow for dependency injections on that object. The dependency, a MessageService bean, is located in the same package as the TestLambda class and therefore found and injected into the TestLambda bean.