Why spring cloud functions? ๐ค
- Serverless architecture
- Ignore transport details and infrastructure, and focus on business logic
- Keep using Spring Boot features
- Run same code as REST API, a stream processor, or a task
AWS Lambda is one of the most popular serverless solutions. In this blog, we will create a simple spring function and deploy it as an AWS Lambda function.
First, we will create spring function
Let’s create a new project from https://start.spring.io/
In this example, we will create a simple function that will receive some name and will return the sum of all ASCII values of characters. For that purpose, we will create two DTO classes.
- InputDTO
public class InputDTO {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- OutputDTO
public class OutputDTO {
private int sum;
public OutputDTO(int sum) {
this.sum = sum;
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
}
Let’s update our pom file. We will add all the dependencies we need for this demo. This is how the pom file should look like:
<?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.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.north</groupId>
<artifactId>north-demo-spring-aws</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>north-demo-spring-aws</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
<version>${spring-cloud-function.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-function-web</artifactId>
<version>${spring-cloud-function.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>${aws-lambda-events.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>${aws-lambda-java-core.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>aws</shadedClassifierName>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud-function.version>2.1.1.RELEASE</spring-cloud-function.version>
<aws-lambda-events.version>2.2.6</aws-lambda-events.version>
<aws-lambda-java-core.version>1.2.0</aws-lambda-java-core.version>
</properties>
</project>
Now let’s write the function. We are implementing the function interface and override the method “apply”. All of the business logic we need, we are writing in that method.
public class UseCaseHandler implements Function<InputDTO, OutputDTO> {
@Override
public OutputDTO apply(InputDTO inputDTO) {
int sum = 0;
for (int i = 0; i < inputDTO.getName().length(); i++) {
sum += ((int) inputDTO.getName().charAt(i));
}
return new OutputDTO(sum);
}
}
UseCaseHandler class is created in com.north.northdemospringaws.function. Because of that, application.yml file needs an update.
spring:
cloud:
function:
scan:
packages: com.north.northdemospringaws.function
Now we will test our function. I will try it with my name Antonie Zafirov.
First, let’s create a simple unit test to check if the function works correctly.
@RunWith(MockitoJUnitRunner.class)
public class UseCaseHandlerTest {
@InjectMocks
private UseCaseHandler useCaseHandler;
@Test
public void testUseCaseHandler() {
InputDTO inputDTO = new InputDTO();
inputDTO.setName("Antonie Zafirov");
OutputDTO outputDTO = useCaseHandler.apply(inputDTO);
assertEquals(1487, outputDTO.getSum());
}
}
We can check with postman if the function is acting like RESTful API
And it works!
Next step is exposing our function and uploading as a Lambda function.
The magic is done with extending SpringBootRequestHandler from AWS adapter. This class is acting as the entry point of the Lambda function and also defining its input and output.
package com.north.northdemospringaws.function;
import com.north.northdemospringaws.dto.InputDTO;
import com.north.northdemospringaws.dto.OutputDTO;
import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler;
public class DemoLambdaFunctionHandler extends SpringBootRequestHandler<InputDTO, OutputDTO> {
}
You should have AWS account for this step, so if you do not have you should create one. After that, go to AWS console and select Lambda from the services list.
From submenu select functions and click on the create function.
Add name on function and select runtime Java 8.
In my case, the function name is “demo”.
Build jar from the application with simple maven command, mvn clean package.
Upload the aws jar, in my case north-demo-spring-aws-0.0.1-SNAPSHOT-aws.jar
In the handler part, we should write the path to the DemoLambdaFunctionHandler. In this example, the path is “com.north.northdemospringaws.function.DemoLambdaFunctionHandler”.
We create environment variable FUNCTION_NAME with the name of our function as value starting with a lowercase letter useCaseHandler. Now let’s save it all and we are done!!! And the last step is to test it.
Create a test event with the name testEvent and value:
{
"name": "Antonie Zafirov"
}
Choose testEvent as event and execute the function with clicking Test button. The result is:
And we are done, and it works!!!
Download the source code
Project is freely available on our GitLab repository. Feel free to fix any mistakes and to comment here if you have any questions or feedback.