Tuesday, March 8, 2016

AWS Lambda Java Deployment Maven Build

Summary


This post describes a "recipe" for building a deployment package for an Amazon AWS Lambda function authored in Java.  The current AWS Lambda documentation (links below in the "references" section) suggest using the "shade" plugin with Maven, which produces an "uber-jar", or using Gradle to produce a zip file containing classes and a "lib" subdirectory with dependency jars.  Neither of those approaches is ideal.  "Uber-jars" cause problems with any code that uses reflection, and using Gradle seems to assume you've completely set Maven aside as a build tool.  This "recipe" assumes you want to use Maven, but you'd still like the zip-containing-jars style deployment package instead of an "uber-jar."

Use Maven's Assembly Plugin


This should be at least a good 90%+ head start on configuring Maven to produce an AWS Lambda deployment package with the dependency libraries still in their original jars and your AWS Lambda function code included in package-directory / class-file format.

Create Assmembly Descriptor

Within the Maven project directory structure, create a file named:

        src/assembly/lambda_deployment_package_assembly.xml

...containing the following content...

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
    <id>lambda_deployment_package_assembly</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}/classes</directory>
            <outputDirectory>/</outputDirectory>
            <includes>
                <include>**/*.class</include>
            </includes>
        </fileSet>
        <fileSet>
            <outputDirectory>extras</outputDirectory>
            <includes>
                <include>readme.md</include>
            </includes>
        </fileSet>
    </fileSets>
    <dependencySets>
        <dependencySet>
            <outputDirectory>lib</outputDirectory>
            <useProjectArtifact>false</useProjectArtifact>
        </dependencySet>
    </dependencySets>
</assembly>

Add Plugin Configuration to pom.xml

Note: If this is a multi-module project, with each module producing its own AWS Lambda function deployment package (.zip), then this goes in the sub-module's pom.xml, not the parent.  If you have a lot of AWS Lambda function sub-modules, it might be worth trying to use pluginManagement in the parent pom, but that's outside the scope of this post.

pom.xml (excerpt)

<project>
    <dependencies>
    ...
    </dependencies>
    ...
    <plugins>
        <plugin>
            <!--  Note: Normally, this plugin would be run using
            "mvn compile assembly:single" but the execution
            clause below binds it to the normal 'package' lifecycle phase
            -->
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptors>
                    <descriptor>src/assembly/lambda_deployment_package_assembly.xml</descriptor>
                </descriptors>
            </configuration>
            <executions>
                <execution>
                    <id>lambda_deployment_package_execution</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
    ...
</project>

References


  • http://docs.aws.amazon.com/lambda/latest/dg/create-deployment-pkg-zip-java.html
  • http://docs.aws.amazon.com/lambda/latest/dg/java-create-jar-pkg-maven-no-ide.html

3 comments:

Tommy Stanton said...

Another helpful directive might be this under 'fileSets':

    <fileSet>
      <directory>src/main/resources</directory>
      <outputDirectory>/</outputDirectory>
    </fileSet>

...and furthermore, this under the 'dependencySet' (in addition to the others that you have in your example):

    <useTransitiveDependencies>false</useTransitiveDependencies>

The 'useTransitiveDependencies' was needed in my case because my AWS Lambda function was a module (which has a parent in its pom.xml).

Whirly said...

@Tommy Stanton, Thanks for the comment.

Maven is intended to "useTransitiveDependencies" and you'll run into trouble if you override that to false, unless you also explicitly declare dependencies on every direct AND TRANSITIVE library required at runtime (which would negate one of the major reasons to use Maven to begin with). If your parent pom is actually including dependencies that are _not_ common to all modules, i.e. the parent declares dependencies on some artifacts that you _don't_ need in _all_ of your modules, perhaps you should be using a dependencyMangement section for those artifacts in your parent pom, instead of actual dependencies. dependencyManagement only centralizes declaration of the artifact version, scope, etc. to use _IF_ one of your modules should include the artifact as a dependency. In other words, anything in a parent pom's dependencyManagement section does _not_ become a transitive dependency unless the module (child) pom also includes it in its dependencies section. See: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Management

Frank AFRIAT said...

Thank you for the wiki, happy to see I am not alone not appreciating uber-jar strategy recommended for maven by aws.

Here is how I prefer to do it (better resource handling):

using [] instead of \< and \>

[dependencySets]
[dependencySet]
[useProjectArtifact]
[includes]
[include]${project.groupId}:${project.artifactId}[/include]
[/includes]
[unpack]true[/unpack]
[/dependencySet]

[dependencySet]
[outputDirectory]lib[/outputDirectory]
[useProjectArtifact]false[/useProjectArtifact]
[/dependencySet]
[/dependencySets]