Create AWS Lambda Functions from Spring Cloud Functions

This tutorial is about creating Lambda functions from Spring Cloud Functions.

Spring Cloud Functions provide a way to implement the business logic via functions and decouple code development from the runtime target. Hence, the developer can focus solely on the implementation of logic. The developer need not worry about the target endpoint type, connectivity, integration, infrastructure, and transportation details. Spring Cloud Functions also strongly support all the Spring Boot features like DI, IoC, and auto-configuration.

Let us start implementing a simple Spring Boot project from scratch. In this project, we will be creating two Spring Cloud functions with the following functionality:

  • Fetch all the students from the repository and,
  • Fetch specific students by name.

We will then create an AWS Lambda function from the jar of this project and test the above two Spring Cloud Functions.

The file structure of this project is as follows.

Firstly, add the necessary maven dependencies into the pom.xml file.

<?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 https://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.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.appsdeveloperblog.aws.lambda</groupId>
    <artifactId>springboot-aws-lambda</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-aws-lambda</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
        <wrapper.version>1.0.17.RELEASE</wrapper.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-adapter-aws</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <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>${wrapper.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <shadedArtifactAttached>true</shadedArtifactAttached>
                    <shadedClassifierName>aws</shadedClassifierName>
                </configuration>
            </plugin>
        </plugins>
        <finalName>springboot-aws-lambda</finalName>
    </build>

</project>

Next, create a Student class with the attributes Id, Name, Age, and CGPA.

Student.java

You can place the following Java class into the ‘com.appsdeveloperblog.aws.lambda.model’ package. Paste the following code consisting of the Student class definition, a constructor with all attributes, getters, and setters into the Student.java file.

package com.appsdeveloperblog.aws.lambda.model;

public class Student {
    private int id;
    private String name;
    private double cgpa;
    private int age;
    public Student(int id, String name, double cgpa, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.cgpa = cgpa;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getCgpa() {
        return cgpa;
    }
    public void setCgpa(double cgpa) {
        this.cgpa = cgpa;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

Later, create a StudentRepo class and add the logic to perform CRUD operations here. I have defined a simple function getAllStudents() in this class for demo purposes.

StudentRepo.java

You can put the StudentRepo.java class into the ‘com.appsdeveloperblog.aws.lambda.repository’ package. The below code implements the function getAllStudents() that returns a hard-coded list of four Student objects. 

package com.appsdeveloperblog.aws.lambda.repository;

import com.appsdeveloperblog.aws.lambda.model.Student;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Repository
public class StudentRepo {
    public List<Student> getAllStudents(){
        return Stream.of(
                new Student(101, "Chris Robert ", 3.5, 17),
                new Student(102, "Steeve Cullen", 2.99, 18),
                new Student(278, "Raj Tiwari", 3.25, 16),
                new Student(953, "Min Young", 3.8, 17)
        ).collect(Collectors.toList());
    }
}

Now, we are going to create the two Spring Cloud Functions that we have discussed.

Creating Spring Cloud Functions

We can create Spring Cloud functions in two ways.

Method 1: Exposing @Bean of type Function

In the SpringbootAwsLambdaApplication class under the ‘main()’ function, define two functions named findstudents and findStudentByName. They should be of type Function<T, R> where T is the input type, and R is the output type. Annotate both of them using the @Bean annotation and fill in the implementation logic as shown below.

package com.appsdeveloperblog.aws.lambda;

import com.appsdeveloperblog.aws.lambda.model.Student;
import com.appsdeveloperblog.aws.lambda.repository.StudentRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@SpringBootApplication
public class SpringbootAwsLambdaApplication {

    @Autowired
    private StudentRepo studentRepo;

    public static void main(String[] args) {
        SpringApplication.run(SpringbootAwsLambdaApplication.class, args);
    }

    @Bean
    public Function<String, List<Student>> findstudents() {
        return (input) -> studentRepo.getAllStudents();
    }

    @Bean
    public Function<String, List<Student>> findStudentByName() {
        return (input) -> studentRepo.getAllStudents().stream()
                .filter(st -> st.getName().equals(input))
                .collect(Collectors.toList());
    }
}

Method 2: Create a class with the function name and implement the Functional interface

Create a package ‘com.appsdeveloperblog.aws.lambda.functions’ and two class files to implement the two functions. Note that the class name is the same as the function name. Also, note that the class name should not have capital letters anywhere other than the first place to avoid the ‘No function defined’ error when invoking the Lambda function.

Next, implement the logic by overriding the apply() function of the Functional Interface.

Getstudents.java

Below is the code to fetch the details of all the students in the repository.

package com.appsdeveloperblog.aws.lambda.functions;

import com.appsdeveloperblog.aws.lambda.model.Student;
import com.appsdeveloperblog.aws.lambda.repository.StudentRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.function.Function;

@Component
public class Getstudents implements Function<String, List<Student>> {

    @Autowired
    private StudentRepo studentRepo;

    @Override
    public List<Student> apply(String s) {
        return studentRepo.getAllStudents();
    }
}

Getstudentsbyname.java

Below is the code to fetch the list of all the students present in the repository whose name is the same as the given input string.

package com.appsdeveloperblog.aws.lambda.functions;

import com.appsdeveloperblog.aws.lambda.model.Student;
import com.appsdeveloperblog.aws.lambda.repository.StudentRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
public class Getstudentsbyname implements Function<String, List<Student>> {

    @Autowired
    private StudentRepo studentRepo;

    @Override
    public List<Student> apply(String input) {
        return studentRepo.getAllStudents().stream()
                .filter(st -> st.getName().equals(input))
                .collect(Collectors.toList());
    }
}

For this method to work, we need to specify that the package ‘com.appsdeveloperblog.aws.lambda.functions’ needs to be scanned for relevant beans. We can add a single line to the application.properties file, as shown below.

spring.cloud.function.scan.packages=com.appsdeveloperblog.aws.lambda.functions

After defining the Spring Cloud functions using the above methods, create a RequestHandler class that extends the SpringBootRequestHandler<String, Object>.

RequestHandler.java

This class acts as an interface to send the requests from the AWS Lambda function to the Spring Cloud Functions. Leave this class empty, as the logic is defined in the Function definition itself. 

package com.appsdeveloperblog.aws.lambda;

import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler;

public class RequestHandler extends SpringBootRequestHandler<String,Object> {
}

That’s it. You can now build an executable using the maven install command and create a Lambda function using the generated jar file.

Let’s verify if the Spring Cloud Functions defined using the above methods are producing results as expected.

  • Go to all services in AWS Management Console and click on Lambda under Compute services.
  • Click on the newly created Lambda function.
  • Under the Configuration tab, set the environment variables. Specify the key as FUNCTION_NAME and value as findStudentByName (Function created using the first method)
  • After that, test the function by giving “Min Young” as input. You can see that the function is successfully executed and returns the JSON string of the student object whose name is the same as the given input.

  • Now, modify the environment variable to test the Function created using the second method. Make sure that the value of FUNCTION_NAME is getstudentsbyname (lowercase).
  • Finally, test the function by giving “Chris Robert ” as input. You can see that this function is also executed successfully, and it returns the JSON string of the student object whose name is the same as the given input.

Link to Code: GitHub

That is the end of the tutorial. You have successfully implemented two Spring Cloud Functions and invoked them using AWS Lambda functions.

Leave a Reply

Your email address will not be published. Required fields are marked *