6

I could need some help here...

I'm using Spring Cloud Function, and I want to deploy my functions on AWS Lambda, using the adapter for AWS.

My application class looks like this:

package example;

@SpringBootApplication
public class SpringCloudFunctionApiGatewayApplication {

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

}

Function 1 looks like this:

package example;

@Component
public class StoreFunction implements Consumer<Message<DemoEntity>>{

    @Override
    public void accept(Message<DemoEntity> t) {

        System.out.println("Stored entity " + ((DemoEntity)t.getPayload()).getName());
        return;
    }
}

Finally, my function handler looks like this:

package example;

public class TestFunctionHandler extends SpringBootApiGatewayRequestHandler {

}

This setup works perfectly. When deploying to Lambda, I provide example.TestFunctionHandler as handler in the AWS console, and Spring Cloud recognizes automatically that example.QueryFunction is the only function in the context.

The log output looks like this:

START RequestId: 3bd996e7-ef5e-11e8-9829-1f50e2b93b6c Version: $LATEST
20:27:45.821 [main] INFO org.springframework.cloud.function.adapter.aws.SpringFunctionInitializer - Initializing: class de.margul.awstutorials.springcloudfunction.apigateway.SpringCloudFunctionApiGatewayApplication

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                        

2018-11-23 20:27:48.221  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Starting LambdaRTEntry on ip-10-153-127-174.ec2.internal with PID 1 (/var/runtime/lib/LambdaJavaRTEntry-1.0.jar started by sbx_user1060 in /)
2018-11-23 20:27:48.242  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : No active profile set, falling back to default profiles: default
2018-11-23 20:27:52.081  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Started LambdaRTEntry in 5.941 seconds (JVM running for 7.429)
Stored entity John Doe
END RequestId: 3bd996e7-ef5e-11e8-9829-1f50e2b93b6c
REPORT RequestId: 3bd996e7-ef5e-11e8-9829-1f50e2b93b6c  Duration: 7113.98 ms    Billed Duration: 7200 ms    Memory Size: 1088 MB    Max Memory Used: 113 MB 

Now, my problem comes here. I want to have multiple functions in one project. I know, on Lambda there can be only one function per deployment. However, for code maintenance reasons (there is some shared code as well as configurations in the real project), we want to have all functions in one project, deploy the project multiple times, and define in the deployment, which is the relevant function.

Using the native AWS SDK for Lambda, that was easy (like in this example in section 4.2): One implementation of RequestStreamHandler, with multiple methods (even if RequestStreamHandler has only one handleRequest() method). The point was that one could define the relevant function as handler: package.ClassName::methodName

However, that does not work with Spring Cloud Function (as we can have only one handler, which is TestFunctionHandler in this case). The documentations mentions that multiple functions are possible, by specifying function.name in application.properties, or as Lambda environment variable FUNCTION_NAME. Anyhow, I don't get that working.

My function 2 looks like this:

package example;

@Component
public class QueryFunction implements Function<Message<String>, Message<DemoEntity>>{

    @Override
    public Message<DemoEntity> apply(Message<String> m) {

        String name = m.getPayload();

        DemoEntity response = new DemoEntity();
        response.setName(name);
        Message<DemoEntity> message = MessageBuilder
                .withPayload(response)
                .setHeader("contentType", "application/json")
                .build();
        return message;
    }
}

In my application.properties, I have this line:

function.name = example.StoreFunction

The same applies if I create an environment variable FUNCTION_NAME: example.StoreFunction

If I now deploy the library and trigger it, I get the following logs:

START RequestId: 67e64098-ef5d-11e8-bdbf-9ddadadef0ce Version: $LATEST
20:21:50.802 [main] INFO org.springframework.cloud.function.adapter.aws.SpringFunctionInitializer - Initializing: class example.SpringCloudFunctionApiGatewayApplication

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                        

2018-11-23 20:21:53.684  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Starting LambdaRTEntry on ip-10-153-127-174.ec2.internal with PID 1 (/var/runtime/lib/LambdaJavaRTEntry-1.0.jar started by sbx_user1059 in /)
2018-11-23 20:21:53.687  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : No active profile set, falling back to default profiles: default
2018-11-23 20:21:57.488  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Started LambdaRTEntry in 6.353 seconds (JVM running for 8.326)
No function defined: java.lang.IllegalStateException
java.lang.IllegalStateException: No function defined
    at org.springframework.cloud.function.adapter.aws.SpringFunctionInitializer.apply(SpringFunctionInitializer.java:134)
    at org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler.handleRequest(SpringBootRequestHandler.java:48)

END RequestId: 67e64098-ef5d-11e8-bdbf-9ddadadef0ce
REPORT RequestId: 67e64098-ef5d-11e8-bdbf-9ddadadef0ce  Duration: 7454.73 ms    Billed Duration: 7500 ms    Memory Size: 1088 MB    Max Memory Used: 130 MB 

Every help is highly appreciated!

markusgulden
  • 503
  • 1
  • 6
  • 18
  • Any way to have this multiple EndPoints functionality in the same AWS Lambda in runtime at the same time with Spring Cloud Function? – pellyadolfo Dec 18 '18 at 12:51
  • 1
    @pellyadolfo I think you are talking about what people say as "Monolithic Lambda" (single lambda with multiple functions inside). In this case, there should be a single function in your lambda (only that will be triggered by lambda container on event like proxy type API gateway request) and in turn this will route to different internal functions. Though it is debatable now a days in architecture perspective that which one to use "Monolithic Lambda" vs "Micro Lambda" (single lambda with single function), where each of them has its own pros and cons and entirely based on application needs. – Abhishek Chatterjee Aug 08 '20 at 11:08
  • @AbhishekChatterjee Finally I did in that way as monolitic lambda with a RouterFunction. I agree with you..... if you have plenty of resources. In my case I am the only developer of my project and do not have want to maintain a bunch of lambda functions. It works pretty well in fact. – pellyadolfo Aug 13 '20 at 07:04
  • @pellyadolfo, thats correct, it entirely depends on scenario. However, it seems that your are using env variable to route between two functions but that is static binding decided at deployment time. There is one recommended option that you can think about, using spring built-in router function. Actually we both are on the same path, trying to achieve same goal in different way. In my spring-routing way, I was facing some routing related issues regarding implementation. Please have a look if you can help, https://stackoverflow.com/questions/63389412/vanilla-spring-cloud-function-with-routing – Abhishek Chatterjee Aug 13 '20 at 08:55

4 Answers4

6

Ok, the problem was obviously my limited knowledge about Spring Beans.

After I viewed a list of all available beans in the context, it was clear that I had to use the class name, but starting with a lower case, i. e. function.name = storeFunction or function.name = queryFunction.

Edit to explain my solution in detail:

In my project, I have several functions like this:

@Component
public class StoreFunction implements Consumer<String>{

    @Override
    public void accept(String s) {

        // Logic comes here
    }
}

@Component
public class QueryFunction implements Function<String, String>{

    @Override
    public void apply(String s) {

        return s;
    }
}

Then, I register them as beans, e. g. like this:

@SpringBootApplication
public class SpringCloudFunctionApiGatewayApplication {

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

    @Bean
    StoreFunction storeFunction(){
        return new StoreFunction();
    }

    @Bean
    QueryFunction queryFunction(){
        return new QueryFunction();
    }
}

Now, I have the two beans storeFunction and queryFunction (the names of the @Bean methods above) available in my Spring context.

Finally, I have to tell Spring which of the functions to call. That can be done by creating an environment variable FUNCTION_NAME and setting it to one of the bean names.

When I now deploy the project to AWS Lambda, I have to tell Lambda which function the invoke (as Lambda only can invoke one function per deployment).

Btw, I created a tutorial for that.

markusgulden
  • 503
  • 1
  • 6
  • 18
  • Hello @margul .. I have the same problem, but however, your solution is not clear. How can you have both function names in one property? Could you kindly elaborate? Thanks! – Gunith D Feb 14 '19 at 12:43
  • 2
    I edited my answer with some more details. Let me know if you need more information. – markusgulden Feb 16 '19 at 09:11
  • Is there any way, I can define multiple Functions in the project and deploy each function individually in AWS? Like some kind of way to generate JARs per Function and deploy each – Mahesh Bhuva May 18 '20 at 05:23
  • @markusgulden: Hi mark, Do you have any official source link of aws lambda documentation which specifies that we can only use ONE FUNCTION for a lambda. It would be really helpful for me.. – Prasanna Jul 08 '20 at 06:42
  • @markusgulden thanks for the tutorial. But I have one comment on that in terms of cloud agnostic part. I think, the data modelling and repository strategy that you tried to make vendor independant, that is not that simple in real world application (though very helpful for pointing out cloud agnostic notion) since data model, itself can vary based on databases (like mysql data model of a sample application is different from Cassandra data model of same application) and hence the repository wiring. In my opinion, it is very difficult to generalize the entity/repository in a cloud agnostic way. – Abhishek Chatterjee Aug 08 '20 at 11:18
  • @Abhishek Chatterjee, agree that this is not trivial, and my project was just a small PoC under lab conditions. Since you mention MySQL and Cassandra, be aware that these are not related to specific cloud vendors. Instead you are talking about relational vs. NoSQL, and that's a whole different ballgame. However, when you want to use AWS DynamoDB vs. Azure CosmosDB (which are both document store and key-value store), I'm sure that this could work. Anyway, you probably still would need vendor-specific DAOs (what my PoC did). – markusgulden Aug 12 '20 at 15:41
  • How to manage dependencies of bean function as you are creating new. – Milind Singh Nov 29 '21 at 21:56
1

I was facing same problem. In my project I had multiple springs function wanted to call them dynamically based on request.

My Functions :

Function 1:

    public class Login implements Function<AuthenticationRequestDTO, JwtTokenDTO>{
        public JwtTokenDTO apply(AuthenticationRequestDTO user) {
           // Logic
        }
   }

Function 2:

public class UserInfo implements Function<JwtTokenDTO, WhoAmIDTO>{
    public WhoAmIDTO apply(JwtTokenDTO jwt) {
       // Logic 
    }
}

In my Spring Boot Application class I added customRouter which implements interface MessageRoutingCallback and returns http request specific "func_name" from http headers. This way We dont need Static FUNCTION_NAME or spring.cloud.function.definition which doesnt support dynamic function routing.

From UI while sending request set header "func_name" to the spring function you want to call.

My Application class:

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

    @Bean
    public MessageRoutingCallback customRouter() {
        return new MessageRoutingCallback() {
            @Override
            public String functionDefinition(Message<?> message) {
                String fnName = (String) message.getHeaders().get("func_name");
                return fnName;
            }
        };
    }
}

In AWS Handler is pointing to : org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest

Manik
  • 11
  • 2
0

Here you can find interesting idea to solve this problem: https://github.com/MelonProjectCom/lambda-http-router

Easy to use:

    @PathPostMapping("/example/test")
    public String example(@Body String body) {
        return "Hello World! - body: " + body;
    }
Prime171
  • 11
  • 1
0

I had the same use use case. Below is what I did where you can have any number of functions in your spring project & use your desired function(which you want invoke) in your aws lambda environment.

How to achieve?

Set this property spring_cloud_function_definition=<name of the function to be invoke> in your aws lambda environment variable.

A pointed to be noted here is that when you define spring.cloud.function.definition in spring project you cannot define multiple individual standalone function name but composite function definition is possible.

Property definition: https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_function_definition

Composite function: https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_declarative_function_composition

Reference: https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_aws_function_routing

Aravinth
  • 686
  • 2
  • 8
  • 14