AWS Lambda and other Maven projects


Background

AWS Lambda is a FaaS (Function as a service) that is event-driven and serverless.

It is termed event-driven due to how AWS Lambda functions are invoked - the event that triggers a AWS Lambda function can be of many different types in the AWS realm.

For example, AWS Kinesis can be configured as an event to invoke AWS Lambda. When a Kinesis record is picked up in a Kinesis stream, AWS Lambda can be invoked as a handler and do some logic with the incoming Kinesis records.

Another example is API Gateway, which acts as a reverse proxy that allows you to create and manage scalable APIs. When a GET request hits an API that is set up by API Gateway, this could also invoke an AWS Lambda function as a handler to do something when that GET request comes in.

AWS Lambda can also be set up to run on a timely basis with CloudWatch Events.

Lastly, it is termed serverless to conceptually take out the consideration of servers and focus on the application logic instead of worrying about fine details of servers such as the operating system, the hardware specs, and lifecycle of servers (i.e. what to do when one crashes or shuts down?)

Implementation

Implementation of AWS Lambda can be done in many ways; as of this writing, one can write a AWS Lambda application with Python, Node.js, or Java 8. To build AWS Lambda applications, an open-source framework called AWS SAM (Serverless Application Model) can be used. The SAM framework contains a template file that can be deployed to AWS to deploy the Lambda function into the cloud (layman's terms).

Maven

For Java 8 SAM projects, Maven is used to bundle up the JAR.

The pom.xml used for the Lambda function is a little different from the standard default pom.xml for a blank project.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>doc-examples</groupId>
  <artifactId>lambda-java-example</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>lambda-java-example</name>

  <dependencies>
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-lambda-java-core</artifactId>
      <version>1.1.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <createDependencyReducedPom>false</createDependencyReducedPom>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>  

pom.xml for an AWS Lambda application written with Java

What is important of note here is maven-shade-plugin for the building of the uber-jar and the aws-lambda-java-core artifact.

The maven-shade-plugin packages all of our dependent JAR files inside of our JAR file so that it can run standalone. In essence, this is a hard requirement for running our Lambda functions on the cloud. It is advocated to use maven-shade-plugin in all of the AWS documentation for AWS Lambda + Maven projects.

This is important because there are many other ways to package a JAR/WAR.

For example, with Spring Boot, you would opt for spring-boot-maven-plugin to build your JAR. This technically means that it would be a headache to spin up a Spring Boot application as a AWS Lambda function, although it is theoretically possible to do so with some hacky solutions. This combination is typically not preferred since it will increase cold boot-up time for Lambda functions and introduce some more overhead.

Dependency Management (Hell)

When the AWS Lambda project is packaged as a JAR via Maven and it is referenced as a dependency for other Maven projects, this will (likely) introduce problems.

For example, if a Spring Boot project has a Lambda project as a dependency, the uber jar created from the maven-shade-plugin will override the manifest attributes (META-INF/*), causing your Spring Boot project to fail when it does its Spring magic of autowiring beans and component scanning.

Solution

The nice thing about Java is the simplicity of structuring object-oriented classes in packages with type-checking at compile time. For the most part, a vanilla Node.js or Python cannot offer this; what it does offer though is a slight faster boot-up time.

Adding domain classes (such as DTOs), or simple POJOs in the Lambda project at first instinct doesn't sound like a bad idea. But if these classes were to be re-used across other Maven projects (or maybe even other Lambda functions), it will be bound to cause conflicts (either now or in the future). I think it suffices to say that these classes should belong in a separate Maven project that does not have to re-package itself via maven-shade-plugin or something similar.

This solution avoids the need to add a whole bunch of exclusions and filters to the maven-shade-plugin in the Lambda project. The downside is that it is an introduction of another Maven project.

Any thoughts? If you have any differing opinions or suggestions, please let me know in the comments section!