1

I am trying to create a Spring Cloud Function application which will have multiple functions defined in it. I need to use the Functional Bean definition approach for reduced cold start time. The jar will be deployed in AWS Lambda.

The code works in local environment and I am able to curl for all the functions defined. However when deployed in Lambda , the function is not getting located by AWS.

The code runs and is working as expected on Lambda ,if there is only 1 function defined.

I will mention below the things that I have tried.

  1. Followed the Spring Doc to create the project
  2. This works fine in local and also runs in Lambda with Handler org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler::handleRequest as per the comment by Thannasi on mydeveloperplanet blog post
  3. Next I added few more functions and registered them with the Generic ApplicationContext
@SpringBootConfiguration
public class Multi_FunctionalBean1Application implements ApplicationContextInitializer<GenericApplicationContext> {
 
 public static void main(String[] args) {
 FunctionalSpringApplication.run(Multi_FunctionalBean1Application.class, args);
 }
 
 public Function<String, Boolean> containsCloud() {
 return value -> {
 System.out.println("Value is " + value);
 return value.contains("cloud");
 };
 }
 
 public Function<String, String> lowercase() {
 return String::toLowerCase;
 }
 
 public Function<String, String> uppercase() {
 return String::toUpperCase;
 }
 
 @Override
 public void initialize(GenericApplicationContext context) {
 context.registerBean("containsCloud", FunctionRegistration.class,
 () -> new FunctionRegistration<>(containsCloud())
 .type(FunctionType.from(String.class).to(Boolean.class)));
 
 context.registerBean("uppercase", FunctionRegistration.class,
 () -> new FunctionRegistration<>(uppercase()).type(FunctionType.from(String.class).to(String.class)));
 context.registerBean("lowercase", FunctionRegistration.class,
 () -> new FunctionRegistration<>(lowercase()).type(FunctionType.from(String.class).to(String.class)));
 context.registerBean("getLength", FunctionRegistration.class,
 () -> new FunctionRegistration<>(AnotherDemoFunction.getLength())
 .type(FunctionType.from(String.class).to(String.class)));
 context.registerBean("getSquare", FunctionRegistration.class,
 () -> new FunctionRegistration<>(DemoFunction.getSquare())
 .type(FunctionType.from(Integer.class).to(Double.class)));
 }
 }
  1. This runs fine in local, I am able to call all the functions separately eg. localhost:8083/lowercase -d ":SFDLKLs is A " and localhost:8083/getSquare -d 2
  2. I tried running this on Lambda , but I get the exception Failed to locate function. Tried locating default function, function by '_HANDLER' env variable as well as'spring.cloud.function.definition. I used the same handler function as before. Additionally provided the environment variable spring_cloud_function_definition with one of the function names as value
  3. I tried also by changing the handler function to org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest. However it gives the exception of missing FunctionCatalog

Below are my dependencies and build plugin. spring-boot-starter-parent version is 2.5.4

<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter</artifactId>
 </dependency>
 
 <!-- Required only during build phase for spring tests to pass and local run-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-function-webflux</artifactId>
 <version>3.0.10.RELEASE</version>
 <scope>test</scope>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 <!-- Basic Spring Cloud Function dependency -->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-function-context</artifactId>
 <version>3.1.3</version>
 </dependency>
 <!-- AWS Specific dependency for deployment in Lambda -->
 <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>
 <!-- Required for reading the AWS Lambda Request/Response event in Proxy 
 Mode -->
 <dependency>
 <groupId>com.amazonaws</groupId>
 <artifactId>aws-lambda-java-events</artifactId>
 <version>3.9.0</version>
 </dependency>
 </dependencies>
 <!-- Created Shaded and Thin jars -->
 <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.27.RELEASE</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>
 </build>

Is there any other way to have multiple Functions in Spring Cloud Functional Bean Definition for AWS Lambda

asked Aug 22, 2021 at 4:31
2
  • 2
    Why are you splitting s-c-function dependencies? Some are 3.1.3 and others 3.1.2. There were some significant fixes for AWS IN 3.1.3 hence my question. Also, look at this sample as a good starting point - github.com/spring-cloud/spring-cloud-function/tree/main/… and let us know if you still have issues Commented Aug 23, 2021 at 8:49
  • Thanks @OlegZhurakousky , that worked just fine. I updated the dependencies to latest and emptied the main method. Commented Aug 24, 2021 at 6:59

2 Answers 2

1

With help of Oleg's Comment I was able to achieve multiple functions with Functional Bean definition on AWS Lambda.

The changes done were as below

  1. Emptied the main method
public static void main(String[] args) {
 //FunctionalSpringApplication.run(Multi_FunctionalBean1Application.class, args);
 
}
  1. Removed the function-webflux dependency and added function-web. Upgraded all spring cloud function dependencies to 3.2.0-SNAPSHOT. Updated pom below
<properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 <java.version>11</java.version>
 <wrapper.version>1.0.27.RELEASE</wrapper.version>
 <aws-lambda-events.version>3.9.0</aws-lambda-events.version>
 <spring-cloud-function.version>3.2.0-SNAPSHOT</spring-cloud-function.version>
 </properties>
 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.5.4</version>
 <relativePath /> <!-- lookup parent from repository -->
 </parent>
 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-function-web</artifactId>
 </dependency>
 <!-- AWS Specific dependency for deployment in Lambda -->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-function-adapter-aws</artifactId>
 </dependency>
 <dependency>
 <groupId>com.amazonaws</groupId>
 <artifactId>aws-lambda-java-core</artifactId>
 <version>1.2.1</version>
 <scope>provided</scope>
 </dependency>
 <!-- Required for reading the AWS Lambda Request/Response event in Proxy 
 Mode -->
 <dependency>
 <groupId>com.amazonaws</groupId>
 <artifactId>aws-lambda-java-events</artifactId>
 <version>3.9.0</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-configuration-processor</artifactId>
 <optional>true</optional>
 </dependency>
 <!-- https://mvnrepository.com/artifact/org.json/json -->
 <dependency>
 <groupId>org.json</groupId>
 <artifactId>json</artifactId>
 <version>20210307</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 <exclusions>
 <exclusion>
 <groupId>com.vaadin.external.google</groupId>
 <artifactId>android-json</artifactId>
 </exclusion>
 </exclusions>
 </dependency>
 </dependencies>
 <dependencyManagement>
 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-function-dependencies</artifactId>
 <version>${spring-cloud-function.version}</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 </dependencies>
 </dependencyManagement>
 <!-- Created Shaded and Thin jars -->
 <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>
 </build>
 <repositories>
 <repository>
 <id>spring-snapshots</id>
 <name>Spring Snapshots</name>
 <url>https://repo.spring.io/snapshot</url>
 <snapshots>
 <enabled>true</enabled>
 </snapshots>
 </repository>
 <repository>
 <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/milestone</url>
 <snapshots>
 <enabled>false</enabled>
 </snapshots>
 </repository>
 </repositories>
 <pluginRepositories>
 <pluginRepository>
 <id>spring-snapshots</id>
 <name>Spring Snapshots</name>
 <url>https://repo.spring.io/snapshot</url>
 <snapshots>
 <enabled>true</enabled>
 </snapshots>
 </pluginRepository>
 <pluginRepository>
 <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/milestone</url>
 <snapshots>
 <enabled>false</enabled>
 </snapshots>
 </pluginRepository>
 </pluginRepositories>
  1. Changed the handler function in AWS Lambda to

org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest

  1. For running any specific function , used the Environment Variable spring_cloud_function_definition with function name as the value or a composition like func1|func2|func3
answered Aug 24, 2021 at 7:07
Sign up to request clarification or add additional context in comments.

1 Comment

Also, if you setup API Gateway to that function, then you can pass spring.cloud.function.definition as an HTTP header, this making it more dynamic. There is also example for that . . . look for routing in aws samples
1

With help from this thread, I was able to execute multiple Function(s) in AWS Lambda infrastructure using "spring_cloud_function_definition" environment variable. Thank you @Mayank for doing the research.

Couple of observations:

SpringAwsCloudFunctionApplication.java - observe that in the latest spring-boot (v3.1.0) the bean registration syntax has changed as follows.

@SpringBootConfiguration
public class SpringAwsCloudFunctionApplication implements ApplicationContextInitializer<GenericApplicationContext> {
 public static void main(String[] args) {
 FunctionalSpringApplication.run(SpringAwsCloudFunctionApplication.class, args);
 }
 public Function<String, String> reverseStringWithUpper() {
 return value -> new StringBuilder(value).reverse().toString().toUpperCase() + System.lineSeparator();
 }
 @Override
 public void initialize(GenericApplicationContext context) {
 context.registerBean("reverseString",
 FunctionRegistration.class,
 () -> new FunctionRegistration<>(new ReverseStringFunction()).type(ReverseStringFunction.class));
 context.registerBean("reverseStringWithUpper",
 FunctionRegistration.class,
 () -> new FunctionRegistration<>(reverseStringWithUpper())
 .type(FunctionTypeUtils.functionType(String.class, String.class)));
 }

ReverseStringFunction.java

@Component
public class ReverseStringFunction implements Function<String, String> {
 @Override
 public String apply(String input) {
 return new StringBuilder(input).reverse().toString() + System.lineSeparator();
 }
}

Configuration screenshots in latest AWS-Lambda console

Handler configuration Handler configuration

Function entry point (method to invoke) configuration Function entry point (method to invoke) configuration

With this I am able to test the cloud functions locally (mvn spring-boot:run) and also on AWS.

answered Jun 23, 2023 at 12:25

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.