0

I just started a new project where I'm going to use Java, Spring cloud functions and AWS Lambda. It's my first time building a serverless application and I've been looking at different example projects and tutorials on how to get started. However, the projects I've found have been so small that it's hard to understand how to map it to a real project.

As I understand it you build a jar file and upload it to AWS Lambda where you specify which function to run. However, as the project grows, more and more functions that aren't even going to run (unreachable code) will make the jar bigger and bigger and cause each Lambda startup to be slower and slower? I could create separate modules for each Lambda function with its own Application class in order to build separate jars, but it doesn't feel like the intended architecture.

Also, I would like to be able to run all of the functions locally using tomcat in a single application. I guess I could build a separate module specifically designed to run locally, but again it doesn't feel like the intended architecture.

Any suggestions or references to best practices would be greatly appreciated.

user3677636
  • 331
  • 3
  • 14
  • Maybe I could use spring profiles to have everything in one module with a single application class and build jar's using each profile to avoid building logic that isn't going to be used? – user3677636 Mar 17 '22 at 07:58

2 Answers2

1

TL;DR:

  • One JAR per function, not all functions in one JAR.
  • Use Maven modules. One module per Lambda function.
  • Don't run the Lambda locally, use unit tests with mocks.
  • Deploy to AWS to test if the Lambda works as intended.

Reading the question I get the feeling that there are a few misconceptions on how AWS Lambda works, that need to be addressed first.

However, as the project grows, more and more functions that aren't even going to run (unreachable code) will make the jar bigger and bigger [...]

You do not deploy a single JAR that contains all your Lambda functions. Every function is deployed as a single JAR. So if you have 20 Lambda functions, you deploy 20 JAR files.

The size of the JAR file is determined by the individual dependencies of the function. A function might use a specific dependency an another might not. So JAR size will differ depending on your dependencies.

One way to improve this is to split your code from the dependencies, by putting the dependencies in Lambda layers. This way, you only deploy a small JAR with your code. The dependency JAR should only be deployed, when the dependencies have been updated. Unfortunately, this will make deployments more complex, but it is doable.

I could create separate modules for each Lambda function with its own Application class in order to build separate jars, but it doesn't feel like the intended architecture.

That's what I'd recommend. And it is more or less the only way. AWS Lambda has a 1 to 1 relationship between the JAR and the function. One Lambda function, per JAR. If you need a second Lambda function, you need to create it and deploy another JAR.

Also, I would like to be able to run all of the functions locally using tomcat in a single application. I guess I could build a separate module specifically designed to run locally, but again it doesn't feel like the intended architecture.

There are tools to run Lambdas locally, like the serverless framework. But running all the Lambdas in a Tomcat is probably going to be hard work.

In general, running Lambdas locally is something I'd not recommend. Write unit tests to run the code locally and deploy to AWS to test the Lambda. There is not really any better way I can think of to do testing efficiently.

Most Lambdas communicate with other services, like DynamoDB, S3 or RDS. So how would you run those locally? There are options, but it just makes everything more and more complicated. And what about services that you can't easily emulate locally (EventBridge, IAM, etc.)? That's why in my experience, running serverless applications locally is unrealistic and will not give you confidence that they'll work once deployed. So why not deploy during development and test the "real" thing?

Jens
  • 20,533
  • 11
  • 60
  • 86
  • > You do not deploy a single JAR that contains all your Lambda functions. Every function is deployed as a single JAR. So if you have 20 Lambda functions, you deploy 20 JAR files. Yes, I understand that, but using the spring.cloud.function framework I need to specify which function to run, which made me think that the intended architecture had multiple functions / jar and then specify which function to run for the specific lambda. I'm fine with using modules, but it would be nice to avoid duplicating all of the build stuff in the pom files as well as the Application classes. – user3677636 Mar 18 '22 at 07:46
  • `In general, running Lambdas locally is something I'd not recommend. Write unit tests to run the code locally and deploy to AWS to test the Lambda. There is not really any better way I can think of to do testing efficiently.` Ok, what I want is to be able to run it locally through IntelliJ in debug mode where it connects to the AWS services it needs in a AWS test environment. But maybe that's not necessary if I write lots of unit tests. – user3677636 Mar 18 '22 at 07:54
0

From my experience, I would recommend using multiple Maven modules, one function per Maven module. Create shared modules for common logic. This approach would require you to implement some smart deployment pipeline to tell which function must be deployed if you change a common lib shared between many functions. If you don't have shared modules using just a hash on /src might be enough, otherwise, you need to add some metadata that describes the relation between Maven modules. I haven't investigated it but it might be possible to get the relation between modules from Maven to feed in your CI/CD so you use build tool to help sort out CD

It's possible to keep all functions within the same JAR and deploy one Jar multiple times with different entry points. The downside is you have tight coupling between all functions. Changes for one function might have some side effects on the other functions. Also coupling all functions within one JAR might make your function slower as it would create one Spring context containing all different beans. Also, Spring Boot approach with autoconfiguration would not help when for one function you need DB connection configured and for another, you need messaging configured. Ofc. you might mitigate some of the downsides but I think the idea of functions is similar to microservices to have a small unite of deployment, well encapsulated.

Finally, you could create a repository per function. It's the most flexible solution but also it might bring some caveats. Ultimately I could imagine every function uses a different version of Spring Boot, some functions are written in Java and some in Kotlin etc. Every function has a slightly different way of testing and running. This all would make maintenance very hard for you in long run. I believe in keeping all functions within one repo with a common set of libraries and configurations would benefit you in terms of cost of maintenance.

Thankfully to Spring Cloud function abstraction, you can use standalone web application by importing the required starter https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_standalone_web_applications. This will allow you to trigger your function as HTTP endpoint. Additionally, Spring Cloud function provides Maven plugin which allows you to run the function locally (only GCP) function:run https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_getting_started_3

Marious
  • 143
  • 1
  • 11