Spring Cloud Function Example for AWS Lambdas
Published
Updated
The Spring Cloud Function library combined with Spring Boot is an excellent way to create an application framework which can run quickly and easily within an AWS Lambda. This article will guide you on how to create a Spring Cloud Function and deploy this application within an Amazon AWS Lambda function.
Getting Started with Spring Cloud Functions
The Spring Cloud Function library allows you to create functional applications all while retaining the benefits of provided by Spring Boot: Autowiring, Dependency Injection, auto-configuration, etc. They follow the standard Java 8 functional support with Supplier
, Function
, and Consumer
support.
Thanks to the spring-cloud-function-adapter-aws library, it is easy to integrate your app directly into Amazon’s AWS ecosystem. It provides native interactions for handling requests, streams, transparent type conversion, and even JSON serialization / deserialization which you may not otherwise have available using an out of the box AWS SDK implementation.
Creating a Spring Cloud Function for AWS Lambda
To start, you will want to create a boilerplate Spring Boot application class that defines a Main method and uses the FunctionalSpringApplication
entry point:
package com.codetinkering.example;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.function.context.FunctionalSpringApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class LambdaApp {
public static void main(String[] args) {
FunctionalSpringApplication.run(LambdaApp.class, args);
}
@Bean
public ExampleFunction exampleFunction() {
return new ExampleFunction();
}
@Bean
public ExampleConsumer exampleConsumer() {
return new ExampleConsumer();
}
@Bean
public ExampleSupplier exampleSupplier() {
return new ExampleSupplier();
}
}
Here is the pojo bean class code:
package com.codetinkering.example;
import lombok.Data;
@Data
public class MyPojo {
private String exampleField;
public MyPojo() { }
public MyPojo(String field) {
this.exampleField = field;
}
}
Next you will want to create a class function which will actually perform the function of your application. This may be any business logic or overall function intended by your application. Implement the java Function
interface and specify the parameters you are expecting to ingest and return. In this example, I am providing a simple bean called MyPojo
and returning a String. It will take a field from the bean and return the uppercase value of the String.
It should be noted that Spring will automatically handle JSON serialization and injection into the bean for you so you do not need to do any parsing or formatting. Additionally, this also applies to the return value, you can return a POJO or bean class that will automatically be converted into JSON.
package com.codetinkering.example;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.util.function.Function;
@Component
public class ExampleFunction implements Function<ExampleFunction.MyPojo, String> {
@Override
public String apply(MyPojo myPojo) {
// Do something in your lambda function here
return myPojo.getExampleField().toUpperCase();
}
}
Spring Cloud Supplier Example
The following code creates a functional Spring Cloud Supplier (basically a Spring Cloud Function that takes no input). This implementation is useful if you have a GET REST endpoint which provides data.
package com.codetinkering.example;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
@Component
public class ExampleSupplier implements Supplier<MyPojo> {
@Override
public MyPojo get() {
return new MyPojo("Example Data");
}
}
Spring Cloud Consumer Example
Though not as frequently used, Spring Cloud Consumers can be used to consume an input but not produce an output. This can oftentimes be helpful for forms which are consumed or other tasks which may not generate a result for the front-end.
package com.codetinkering.example;
import org.springframework.stereotype.Component;
import java.util.function.Consumer;
@Component
public class ExampleConsumer implements Consumer<MyPojo> {
@Override
public void accept(MyPojo myPojo) {
System.out.println("Received: " + myPojo.getExampleField());
}
}
Multiple Spring Cloud Functions in one project
In an AWS Lambda, only one function is intended to be used at a time. Rather than having multiple projects defining their own function, we can bundle them together in the same application and use a property file to specify which function is to be used by the Lambda.
spring.cloud.function.definition=exampleFunction
In effect, you can have the same project deployed multiple times (across multiple lambdas) with each corresponding to a specific endpoint. You can then use Spring profiles to define which Lambda uses what function.
Configuring your Lambda Application Maven Pom.xml
The following Maven Pom.xml contains all of the needed components and build plugins for your application. It uses a Spring Boot Starter parent, along with a build configuration targeting Java 1.8. Finally, it has all of the needed Maven build plugins for building a shaded jar and uses the Spring-Boot-Thin-Layout plugin to strip down the size of your application.
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/>
</parent>
<groupId>com.codetinkering</groupId>
<artifactId>aws-lambda-java-example</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.codetinkering.example.LambdaApp</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.26.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
Building your Lambda Application
You can build your application using the following maven commands:
mvn clean package
This will build your application into a JAR file that you can deploy into AWS for your Lambda. The following steps will show you how to deploy it to AWS.
Building your cloud function using Spring Native
See my full article here on how to configure your Spring Cloud function to be built into a native Lambda image or application using GraalVM.
Deploying your Spring Cloud Function to an AWS Lambda
Sign into your Amazon AWS Console and navigate to the AWS Lambda service page. From this dashboard, click the button to create a new Lambda Function and select the option to author from scratch.
You will need to provide a name for your lambda. Do so in the Function Name box and make it descriptive enough that you will remember what it does should you add more Lambdas later. Additionally, for the AWS Lambda Runtime, pick Java 8 or Java 11. Either are acceptable for this application. Finally, click the Create Function button to continue.
Upload your application JAR file to the AWS Lambda
You should now have an empty Lambda created within AWS and it should look something like the following:
Simply click the upload from button and navigate to the target folder of your project. Select the JAR file and upload it to AWS by pressing the Save button. If your JAR file is large, it may be more ideal to store your JAR file in S3 and reference it there instead of uploading it via the Console each time you redeploy.
Configuring Lambda Handler for Spring Cloud Function
Now that your application has been uploaded into AWS, Amazon needs to understand how to properly invoke our Spring Cloud Function. Spring has a class called FunctionInvoker
and a generic handler method called handleRequest
which is provided by the spring-cloud-function-aws-adapter
library.
On the Runtime Settings pane for your Lambda, click the Edit button and change the Handler field to the following. Then click save.
org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest
Testing your Spring Cloud Function Lambda
Click the test tab and provide the following JSON into your event:
{
"exampleField": "test-value"
}
Spring will automatically serialize the data into your object and it will be available in your Lambda function code.
Click the test button to run your lambda and see the results of your Spring Application’s return value:
Note the execution times, memory, and billed durations. Subsequent calls to this lambda will take significantly less time as the application would have already been started and the caches would be warm within the MicroVMs of AWS. You can adjust memory usage up or down to better fit your applications needs from the configuration tab as well.
Lambda Configuration, Monitoring and Logging
You can do a number of different things from here, you can create an ALB (Application Load Balancer) and link it via a trigger to your Lambda so that it can be accessed via the web. Otherwise your Lambda will not be accessible to traffic via AWS.
You can also monitor the logs of the execution and statistics via the Monitor panel in AWS to see various stats and log entries.
Failed to lookup function to route based on the value of ‘spring.cloud.function.definition’ property when defining multiple Spring Cloud functions
This can happen if you have multiple cloud functions defined within the same project. If your project is structured this way, you will need to define in your application properties which function you want to use for the lambda (there is a limit of one function per lambda). Do so by adding the following line in your properties file:
spring.cloud.function.definition=exampleFunction
Note the casing of the function name, this aligns with the bean definition for your function, not the class name. In the above example I define my bean as exampleFunction
even though it is a class of ExampleFunction
.
Checkout this project from Github
git clone https://github.com/code-tinkering/aws-lambda-java-example
Download Zip