Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Spring Cloud Functions in one project for deployment on AWS Lambda

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!

like image 476
markusgulden Avatar asked Nov 23 '18 20:11

markusgulden


1 Answers

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.

like image 183
markusgulden Avatar answered Nov 15 '22 07:11

markusgulden