Lammy is a microframework for building AWS Lambda functions in Java 8+.
- Make it easier to build and maintain Lambda functions using Java 8+
- Keep JAR size small
- Provide implementations of common use cases (e.g., "copy file to S3")
- Build a framework for microservices in general (i.e., don't rebuild Quarkus or Micronaut)
To create a simple Lambda function with Lammy, add the following dependency to your project:
<dependency>
<groupId>io.aleph0</groupId>
<artifactId>lammy-core</artifactId>
<version>1.0.0-B0</version>
</dependency>
Next, add the following code to your project:
/**
* The JSON-serializable request object
*/
public class Request {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* The JSON-serializable response object
*/
public class Response {
private String greeting;
public String getGreeting() {
return greeting;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
}
/**
* The lambda function implementation
*/
public class ExampleLambdaFunction
extends BeanLambdaProcessorBase<Request, Response> {
@Override
public Response handleBeanRequest(Request request, Context context) {
if(request.getName() == null)
throw new IllegalArgumentException("name is required");
final String name = request.getName();
final String greeting = "Hello, " + getName() + "!";
final Response response=new Response();
response.setGreeting(greeting);
return response;
}
}
Congratulations! You have created a working AWS Lambda function.
Lammy's design is heavily influenced by JAX-RS. Specifically, the framework implement a straightforward, mechanical control flow that centralizes business logic while exposing hooks that libraries and applications can use for things like customization, serialization, exception handling, and so on. Hopefully, Lammy's design feels clear and familiar.
Lammy uses the following terms to talk about Lambda functions.
There are two logical function styles:
- Processor -- a Lambda function that takes an input and produces an output. Example: Given an entity ID, fetch the corresponding entity from a data store and return it.
- Consumer -- a Lambda function that takes an input, but produces no logical output. (Of course, all Lambda functions must return an output per the Lambda API, but consumer outputs are semantically void.) Example: Given an entity update, apply the given update to a data store, then exit.
A "stream function" is any AWS Lambda function that operates on byte streams (e.g., InputStream
and OutputStream
). These correspond to RequestStreamHandler
functions.
Stream-style functions support interceptors, which are plugin-style objects that can pre-process function input streams and post-process function output streams, for example to perform base64 encoding and decoding.
Additionally, stream-style functions support exception writers, which are plugin-style objects that act as exception handlers for specific exception types. An exception writer can propagate the exception, which is treated as an error by the Lambda runtime; throw a different exception, which is treated as an error by the Lambda runtime; or write a message to the output stream, which is treated as a success by the Lambda runtime.
Lammy provides StreamLambdaProcessorBase
and StreamLambdaConsumerBase
for processor and consumer stream-style functions, respectively.
A "bean function" is any AWS Lambda function that operates on de/serialized Java bean objects (POJOs) as input/output, as opposed to streams where the serialization is handled inside the Lambda runtime. These correspond to RequestHandler
functions.
Bean-style functions support filters, which are plugin-style objects that can pre-process function inputs and post-process function outputs, for example to perform object validation.
Additionally, bean-style functions support exception mappers, which are plugin-style objects that act as exception handlers for specific exception types. An exception writer can propagate the exception, which is treated as an error by the Lambda runtime; throw a different exception, which is treated as an error by the Lambda runtime; or return a message of the same type as the function response type, which is then handled as the function output and treated as a success by the Lambda runtime.
Lammy provides BeanLambdaProcessorBase
and BeanLambdaConsumerBase
for processor and consumer bean-style functions, respectively.
A "streamed bean function" is any AWS Lambda function that operates on de/serialized Java bean objects (POJOs) as input/output as opposed to streams where the serialization is handled in the function implementation itself. The implementation uses a stream-style RequestStreamHandler
to interface with the Lambda runtime, then performs serialization to expose a bean-style programming interface for the user.
Because they perform their serialization manually, streamed bean-style functions support both interceptors and filters. This gives users maximum control over how I/O is encoded over the wire and how objects are de/serialized for processing.
Streamed bean functions also support exception mappers.
Lammy provides StreamedBeanLambdaProcessorBase
and StreamedBeanLambdaConsumerBase
for processor and consumer bean-style functions, respectively.
Lammy makes heavy use of Java's ServiceLoader
to simplify the developer's life. The following types can all be loaded automatically using the ServiceLoader
:
CustomPojoSerializer
-- For bean functions and streamed bean functionsContextAwareCustomPojoSerializer
-- For streamed bean functionsRequestFilter
,ResponseFilter
-- For bean functions and streamed bean functionsInputInterceptor
,OutputInterceptor
-- For stream functions and streamed bean functionsExceptionWriter
-- For stream functionsExceptionMapper
-- For bean functions and streamed bean functions
The CustomPojoSerializer
and ContextAwareCustomPojoSerializer
objects are always loaded automatically if present.
To load all other objects automatically via ServiceLoader
, the user must do one of two things:
- Set the environment variable
LAMMY_AUTOLOAD_ALL=TRUE
- Set individual types to autoload using function base's corresponding configuration object (e.g.,
BeanLambdaProcessorConfiguration
) in the function's constructor
When building lammy from source, you may notice tests failing, especially with timeout messages indicating that LocalStack has stopped responding to requests. Try running the offending tests individually like this:
JAVA_HOME="/Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home" mvn -pl lammy-test test -Dtest='BeanLambdaFunctionIntegrationTest#givenExceptionMapperServicesAB_whenAutoloadExplicitlyEnabledAndThrowNonMatching_thenPropagate
If the tests pass when run individually but fail when run in the context of the larger build, then then it's likely that the underlying LocalStack instance is running out of memory and crashing. In this situation, try increasing the memory limit for your container engine. For example, in Docker Desktop, increasing the memory limit by going to Settings → Resources → Memory Limit to at least 4GB.