BAEL-6255 - Run a Spring Boot application in AWS Lambda (#13629)

* BAEL-6255 - Run a Spring Boot application in AWS Lambda

* BAEL-6255 - Run a Spring Boot application in AWS Lambda

* fix on template.yaml

* fix on template.yaml

* removed log from test

* resolved issues reported on PR

---------

Co-authored-by: Cesare <cesare.valenti@hotmail.com>
This commit is contained in:
cesarevalenti90 2023-03-23 21:43:34 +01:00 committed by GitHub
parent b877f01511
commit 6a2d723155
11 changed files with 297 additions and 0 deletions

View File

@ -0,0 +1 @@
### Relevant Articles:

View File

@ -0,0 +1,77 @@
<?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>
<groupId>com.baeldung</groupId>
<artifactId>spring-boot-aws</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-data-3</name>
<description>spring-boot-boot</description>
<parent>
<groupId>com.baeldung.spring-boot-modules</groupId>
<artifactId>spring-boot-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<properties>
<springboot2.aws.version>1.9.1</springboot2.aws.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws.serverless</groupId>
<artifactId>aws-serverless-java-container-springboot2</artifactId>
<version>${springboot2.aws.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>org.apache.tomcat.embed:*</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package com.baeldung.aws;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,21 @@
package com.baeldung.aws.controller;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baeldung.aws.data.User;
@RestController
@RequestMapping("/api/v1/")
public class ProfileController {
@GetMapping(value = "users", produces = MediaType.APPLICATION_JSON_VALUE)
public List<User> getUser() {
return List.of(new User("John", "Doe", "john.doe@baeldung.com"), new User("John", "Doe", "john.doe-2@baeldung.com"));
}
}

View File

@ -0,0 +1,38 @@
package com.baeldung.aws.data;
public class User {
private String name;
private String surname;
private String emailAddress;
public User(String name, String surname, String emailAddress) {
this.name = name;
this.surname = surname;
this.emailAddress = emailAddress;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}

View File

@ -0,0 +1,25 @@
package com.baeldung.aws.handler;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.serverless.proxy.spring.SpringBootProxyHandlerBuilder;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.baeldung.aws.Application;
public class AsynchronousLambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
private SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
public AsynchronousLambdaHandler() throws ContainerInitializationException {
handler = (SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse>) new SpringBootProxyHandlerBuilder().springBootApplication(Application.class)
.asyncInit()
.buildAndInitialize();
}
@Override
public AwsProxyResponse handleRequest(AwsProxyRequest input, Context context) {
return handler.proxy(input, context);
}
}

View File

@ -0,0 +1,26 @@
package com.baeldung.aws.handler;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.baeldung.aws.Application;
public class HttpV2LambdaHandler implements RequestHandler<HttpApiV2ProxyRequest, AwsProxyResponse> {
private static SpringBootLambdaContainerHandler<HttpApiV2ProxyRequest, AwsProxyResponse> handler;
static {
try {
handler = SpringBootLambdaContainerHandler.getHttpApiV2ProxyHandler(Application.class);
} catch (ContainerInitializationException ex) {
throw new RuntimeException("Unable to load spring boot application", ex);
}
}
@Override
public AwsProxyResponse handleRequest(HttpApiV2ProxyRequest input, Context context) {
return handler.proxy(input, context);
}
}

View File

@ -0,0 +1,26 @@
package com.baeldung.aws.handler;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.baeldung.aws.Application;
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
} catch (ContainerInitializationException ex) {
throw new RuntimeException("Unable to load spring boot application", ex);
}
}
@Override
public AwsProxyResponse handleRequest(AwsProxyRequest input, Context context) {
return handler.proxy(input, context);
}
}

View File

@ -0,0 +1,3 @@
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=myusername
spring.datasource.password=mypassword

View File

@ -0,0 +1,36 @@
package com.baeldung;
import java.io.IOException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.baeldung.aws.handler.LambdaHandler;
@SpringBootApplication
public class ProfileIntegrationTest {
MockLambdaContext lambdaContext = new MockLambdaContext();
@Test
void whenTheUsersPathIsInvokedViaLambda_thenShouldReturnAList() throws IOException {
LambdaHandler lambdaHandler = new LambdaHandler();
AwsProxyRequest req = new AwsProxyRequestBuilder("/api/v1/users", "GET").build();
AwsProxyResponse resp = lambdaHandler.handleRequest(req, lambdaContext);
Assertions.assertNotNull(resp.getBody());
Assertions.assertEquals(200, resp.getStatusCode());
}
@Test
void whenWrongPathPathIsInvokedViaLambda_thenShouldNotFound() throws IOException {
LambdaHandler lambdaHandler = new LambdaHandler();
AwsProxyRequest req = new AwsProxyRequestBuilder("/api/v1/users/plus-one-level", "GET").build();
AwsProxyResponse resp = lambdaHandler.handleRequest(req, lambdaContext);
Assertions.assertEquals(404, resp.getStatusCode());
}
}

View File

@ -0,0 +1,32 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 30
Resources:
ProfileApiFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: .
Handler: com.baeldung.aws.handler.LambdaHandler::handleRequest
Runtime: java11
AutoPublishAlias: production
SnapStart:
ApplyOn: PublishedVersions
Architectures:
- x86_64
MemorySize: 2048
Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
Variables:
JAVA_TOOL_OPTIONS: -XX:+TieredCompilation -XX:TieredStopAtLevel=1 # More info about tiered compilation https://aws.amazon.com/blogs/compute/optimizing-aws-lambda-function-performance-for-java/
Policies:
- AWSSecretsManagerGetSecretValuePolicy:
SecretArn: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${DatabaseSecretName}
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /{proxy+}
Method: ANY