Spring Native: Plantilla de AWS Lambda

spring native

Plantilla de AWS Lambda a través de Spring Native

El arranque en frío de Java no es excelente en AWS Lambda en comparación con lenguajes como Python y NodeJS. Además, si decidimos usar frameworks como Spring Native, el tiempo de calentamiento será aún mayor.

Estoy bastante acostumbrado a usar Spring Native y quería aprovechar esta experiencia mientras desarrollaba funciones en AWS usando Spring Cloud Function. Entonces, ¿cómo podemos justificar el costo de usar Java (y Spring)?

Bueno, me topé con un video que describe Spring Native y pensé que esta podría ser una solución.


spring-native-aws-lambda
.mvn/wrapper # for building without installing maven 
mvnw # build on unix like OS
mvnw.cmd # build on windows 
Dockerfile # Building for aws environment using amazonlinux image
pom.xml # project dependencies
src
assembly
native.xml # Used by maven-assembly-plugin to zip the native image and shell/native/bootstrap
shell
native
bootstrap # bootstraps the function in AWS Lambda env
main
java
com.coffeebeans.springnativeawslambda

Naveguemos por el código en estos archivos.

Código

Dependencias de funciones de Spring Cloud

1. Gestión de Dependencias

La función Spring Cloud (SCF) proporciona una dependencia pom que podemos importar al elemento dependencyManagement

<gestión de dependencias>
<dependencias>
<dependencia>
<groupId>org.springframework.cloud</groupId>
<artifactId>primavera-nube-dependencias</artifactId>
<versión>2020.0.2</versión>
<tipo>pom</tipo>
<ámbito>importar</ámbito>
</dependencia>
</dependencias>
</administración de dependencias>

Esto nos ayuda a importar una versión compatible para SCF

2. Dependencias

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>

NOTA: que usaremos el modo reactivo, por lo tanto, org.springframework.boot:spring-boot-starter-webfluxorg.reactivestreams:reactive-streams —

Dependencias nativas de Spring

1. Gestión de Dependencias

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.10.0</version>
</dependency>
</dependencies>
</dependencyManagement>

2. Dependencia

<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
</dependency>
</dependencies>

3. Complementos

<build>
<plugins>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

4. Perfil

<profiles>
  <!-- Enable building a native image using a local installation of native-image with GraalVM native-image-maven-plugin -->
  <profile>
    <id>native-image</id>
    <properties>
      <!-- Avoid a clash between Spring Boot repackaging and native-image-maven-plugin -->
      <classifier>exec</classifier>
    </properties>
    <build>
      <plugins>
        <plugin>
          <groupId>org.graalvm.nativeimage</groupId>
          <artifactId>native-image-maven-plugin</artifactId>
          <version>21.1.0</version>
          <configuration>
            <imageName>${project.artifactId}</imageName>
            <buildArgs>${native.build.args}</buildArgs>
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>native-image</goal>
              </goals>
              <phase>package</phase>
            </execution>
          </executions>
        </plugin>        <plugin>
          <!-- packages native image and bootstrap file for uploading to AWS -->
          <artifactId>maven-assembly-plugin</artifactId>
          <executions>
            <execution>
              <id>native-zip</id>
              <phase>package</phase>
              <goals>
                <goal>single</goal>
              </goals>
              <inherited>false</inherited>
            </execution>
          </executions>
          <configuration>
            <descriptors>
              <descriptor>src/assembly/native.xml</descriptor>
            </descriptors>
          </configuration>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

Configuración de la aplicación

La aplicación está configurada como cualquier otra aplicación Spring Boot con muy pocas diferencias.

  • Utiliza @SpringBootConfiguration en lugar de @SpringBootApplication
  • FunctionalSpringApplication#run(Class, String[]) en lugar de SpringApplication#run(Class, String[]) SpringApplication#run(Class, String[])
  • Hicimos una sugerencia nativa a nuestra aplicación para serializar nuestros modelos correctamente
@SerializationHint(types = {Request.class, Response.class})
@SpringBootConfiguration
public class SpringNativeAwsLambdaApplication implements ApplicationContextInitializer<GenericApplicationContext> {

public static void main(final String[] args) {
FunctionalSpringApplication.run(SpringNativeAwsLambdaApplication.class, args);
}

@Override
public void initialize(final GenericApplicationContext context) {
context.registerBean("exampleFunction",
FunctionRegistration.class,
() -> new FunctionRegistration<>(new ExampleFunction()).type(ExampleFunction.class));
}
}

Configuraciones de tiempo de ejecución personalizadas de AWS Lambda

#this is required to be true for custom runtime where we will run our native-image, override it to false if you deploying spring cloud function to aws without spring-native
spring.cloud.function.web.export.enabled=true

NOTA: Spring Cloud Function admite un estilo “funcional” de declaraciones de beans para aplicaciones pequeñas en las que necesita un inicio rápido. Por lo tanto, lo usamos en nuestra aplicación (este es el método de initialize alización que también implementa ApplicationContextInitializer)

La función

Spring Cloud Functions nos permite crear funciones extendiendo las interfaces funcionales principales de java8 3

  • java.util.function.Function
  • java.util.function.Consumer
  • java.util.function.Supplier

Puede encontrar más información sobre cada caso de uso aquí

Para nuestro ejemplo, extendimos java.util.function.Function

NOTA: colocamos nuestra clase en un paquete llamado functions. De esta forma, se escaneará automáticamente incluso sin la anotación @Component.

@Slf4j
public class ExampleFunction implements Function<Request, Response> {  @Override
  public Response apply(final Request request) {
    log.info("Converting request into a response...");    final Response response = Response.builder()
        .name(request.getName())
        .saved(true)
        .build();    log.info("Converted request into a response.");
    return response;
  }
}

Modelos

Nuestros modelos están implementando Serializable

Configuración.xml

Un último paso es asegurarnos de que podamos obtener dependencias y complementos de org.springframework.experimental. Para esto, necesitamos agregar https://repo.spring.io/release como repositorio y pluginrepository en settings.xml

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<profiles>
<profile>
<id>spring-native-demo</id>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>spring-native-demo</activeProfile>
</activeProfiles>
</settings>

Construyendo

Construyendo para el Medio Ambiente Local

./mvnw -ntp clean package -Pnative-image
target/spring-native-aws-lambda

Creación para el entorno de AWS

  • Ejecute los siguientes comandos (en la terminal local o en la canalización de CI/CD)
mkdir targetdocker build --no-cache \
--tag spring-native-aws-lambda:0.0.1-SNAPSHOT \
--file Dockerfile .docker ps -a  
docker rm spring-native-aws-lambda                                                                                                                                
docker run --name spring-native-aws-lambda spring-native-aws-lambda:0.0.1-SNAPSHOT                                                            
docker cp spring-native-aws-lambda:app/target/ .
  • Cargue el archivo spring-native-aws-lambda-0.0.1-SNAPSHOT-native-zip.zip en aws lambda,
  • Establezca el controlador en exampleFunction
  • Prueba, y
  • Et voila! Funciona con 500 ms para arranque en frío (supongo que no está mal para la aplicación Spring Boot)

NOTA: Necesitamos construir dentro de una arquitectura de sistema operativo lo más cerca posible del sistema operativo de Lambda.

Esto se debe a que la imagen nativa se construirá para esa arquitectura. Es decir, si lo construimos en MacOS, no se ejecutará en Amazon Linux.

Esta es la razón por la que estamos construyendo en su lugar un contenedor amazonlinux2 usando el Dockerfile


Probando localmente

Puede acceder a la aplicación en cualquier puerto en el que la inició utilizando la solicitud http.

Recuerde, para probar localmente, asegúrese de que spring.main.web-application-type esté configurado como reactive. Para el entorno de AWS, debe establecerse en none.

curl --location --request POST 'http://localhost:8080/exampleFunction' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "CoffeeBeans"
}'

Código fuente del proyecto

/spring-native-aws-lambda


Si le interesa, puede echar un vistazo a algunos de los otros artículos que he escrito recientemente sobre Laravel:

Recent Post