Migrar microservicio Spring Boot a una función AWS Lambda

En el post de hoy vamos a explicar cómo se puede migrar un microservicio clásico a una función Lambda de AWS. Esto podría aplicar en un escenario donde, por ejemplo, ya dispongamos de un microservicio ubicado en un contenedor Docker y para cuya ejecución se utiliza un motor Fargate. El objetivo será trasladar ese microservicio a una arquitectura Serverless, migrando el software desde Fargate a una función AWS Lambda. Para ello, aparte de Spring Boot, vamos a apoyarnos en las facilidades proporcionadas por el proyecto Spring Cloud. En el ejemplo vamos a utilizar Visual Studio Code para desarrollar la función, pero el proceso sería equivalente utilizando Eclipse.

 

Hay que tener en cuenta que trasladar un microservicio desde una arquitectura tradicional a una arquitectura Serverless requiere algunas adaptaciones previas. No son cambios complejos, pero conviene saber que no es un cambio directo.

 

Migrar microservicio Spring Boot a una función AWS Lambda

 

Como punto de partida de nuestro proyecto, vamos a suponer que ya tenemos creada una aplicación Spring Boot, denominada "cloud-function-migration". Para crear la aplicación hemos utilizado la siguiente configuración en el Spring Initializr.

 

Una vez generado el proyecto y cargado en nuestro Visual Studio Code, la estructura de nuestra aplicación debería quedar algo así como la siguiente: 


💦 El contenido de nuestro "CloudFunctionMigrationController.java" es el siguiente. Ahí tenemos dos rutas de acceso que ejecutan diferentes funcionalidades (una de ellas, como vemos, recibe un parámetro de entrada). 

/api/function/prueba

/api/function/{value}

 
@RestController
@RequestMapping("/api")
public class CloudFunctionMigrationController {

    @GetMapping("/function/{value}")
    public String function(@PathVariable String value) {
        return "Hola, desde AWS Lambda - Version " + value;
    }

    @GetMapping("/function/prueba")
    public String prueba() {
        return "Hola, prueba correcta desde Lambda";
    }
}


 

En nuestro fichero "application.properties" tenemos lo siguiente.

spring.application.name=cloud-function-migration
spring.cloud.function.definition=function

 

💦 Por lo que respecta a nuestro "pom.xml", estas son las dependencias que tenemos incluidas en los apartados <dependencies> y <dependencyManagement>

 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-function-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-web</artifactId>
        </dependency>
    </dependencies>
 
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
 

 

Finalmente, por lo que respecta al paquete JAR a generar posteriormente, en nuestro "pom.xml" tendremos lo siguiente en el apartado <build>:

 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <outputDirectory>target/deploy</outputDirectory>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.boot.experimental</groupId>
                        <artifactId>spring-boot-thin-layout</artifactId>
                        <version>1.0.28.RELEASE</version>
                    </dependency>
                </dependencies>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <shadedArtifactAttached>true</shadedArtifactAttached>
                    <shadedClassifierName>aws</shadedClassifierName>
                </configuration>
            </plugin>
        </plugins>
    </build>
 


Generación de paquete JAR con el microservicio

 

Los pasos a seguir para migrar nuestro microservicio serían los siguientes.

 

1º) En primer lugar, es importante mencionar que hay que añadir la dependencia Serverless de AWS en nuestro "pom.xml". Esto es necesario para que nuestro paquete se genere con la estructura correcta para permitir la posterior ejecución de un microservicio clásico en una función AWS Lambda.

aws-serverless-java-container-springboot3


        <dependency>
            <groupId>com.amazonaws.serverless</groupId>
            <artifactId>aws-serverless-java-container-springboot3</artifactId>
            <version>2.0.1</version>
        </dependency>


2º) A continuación, tendremos que crear la clase RequestHandler para que gestione las invocaciones recibidas desde la función Lambda. Para ello, nos creamos una clase denominada "LambdaHandler.java" con el siguiente contenido.

 
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {

    private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;

    static {
        try {
            handler = SpringBootLambdaContainerHandler
                .getAwsProxyHandler(CloudFunctionMigrationApplication.class);
        } catch (ContainerInitializationException e) {
            throw new RuntimeException("Failed to initialize Spring Boot AWS Lambda container", e);
        }
    }

    @Override
    public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
        return handler.proxy(awsProxyRequest, context);
    }
}

 

3º) A continuación, generamos el paquete de la aplicación mediante Maven. Lanzamos el comando en el terminal de Visual Studio Code.

mvn clean package 


5º) Si el Build finaliza sin problemas, en la carpeta "target" de nuestra aplicación aparecerá el nuevo paquete JAR generado. En nuestro caso, se habrá generado un JAR con la estructura requerida para su despliegue en una función Lambda de AWS.

cloud-function-0.01-SNAPSHOT-aws.jar


Con esto ya tendríamos preparado el paquete de nuestro microservicio.

 

Creación de AWS Lambda para el microservicio

 

A continuación, tenemos que ir a la consola AWS para proceder a la creación de una función Lambda en la que subiremos el paquete JAR que acabamos de generar.


Los pasos a seguir para configurar nuestra Lambda serían los siguientes.


6º) Dentro de la consola AWS, nos vamos al apartado de funciones Lambda.


 

7º) Pulsamos el botón CREATE FUNCTION y vamos al detalle de la función Lambda.


 

8º) Vamos al apartado de "ADDITIONAL CONFIGURATION" y seleccionamos la opción "Enable function URL".  


9º) Revisamos la configuración global de la función Lambda.


10º) Pulsamos CREATE FUNCTION. Si no hay problemas, la función quedará creada correctamente. 


👉 Si nos fijamos en el apartado "FUNCTION URL", ahí podremos ver el endpoint que ha quedado asignado a la función Lambda. En nuestro ejemplo es el siguiente:

https://rygsklpbbc4j3ynw5b3bptjxha0mijhm.lambda-url.us-east-1.on.aws/

 

11º) A continuación, pulsamos el botón UPLOAD FROM y seleccionamos la opción ".zip or .jar file".


 12º) Pulsamos UPLOAD y subimos el fichero JAR que previamente habíamos generado.


13º) Pulsamos SAVE. Si no hay ningún problema, el fichero JAR quedará asociado a la función Lambda que acabamos de crear.


 

Configuración del Handler de la función Lambda

 

 14º) A continuación, nos vamos al apartado "RUNTIME SETTINGS".


 

  15º) Pulsamos el botón EDIT y vamos a la ventana de "Runtime settings".


 

16º) En el apartado "HANDLER" tenemos que indicar el endpoint de acceso a la funcionalidad de nuestro microservicio. En nuestro ejemplo es el siguiente:

com.example.cloud_function_migration.LambdaHandler::handleRequest

 


  17º) Pulsamos SAVE y la función Lambda quedará actualizada.



Crear API Gateway para conectar con la función Lambda


👿 A continuación, necesitamos crear una API Gateway en AWS que sirva como punto de entrada a nuestra función Lambda. Esto es debido a que los microservicios tradicionales esperan peticiones HTTP y Lambda no dispone de una interfaz HTTP. Así que el API Gateway básicamente lo que hará será enrutar las peticiones HTTP hacia la función Lambda.


18º) Para ello, desde la consola AWS nos vamos al servicio API Gateway.


 

19º) Pulsamos CREATE API y nos aparece una ventana con los tipos de API disponibles.


 

20º) Seleccionamos la opción "REST API" y pulsamos el botón BUILD. Saltamos a la ventana de detalle de REST API.


 

21º) Pulsamos CREATE API. Si no hay ningún error, la API Gateway quedará creada.

 

Creación de los Recursos de la API Gateway 

 

Una vez creada la API Gateway, ahora tenemos que proceder a crearnos la estructura de endpoints de nuestro microservicio. Recordemos que las rutas que teníamos definidas en nuestro @RestController eran las siguientes:

/api/function/prueba

/api/function/{value}

 

22º) Para ello, pulsamos el botón CREATE RESOURCE.


 

23º) Introducimos en RESOURCE NAME el valor "api", correspondiente a la primera parte de nuestros endpoints. Pulsamos CREATE RESOURCE.


 

24º) A continuación, nos situamos sobre el recurso "/api" y volvemos a pulsar CREATE RESOURCE.


 

25º) Introducimos en RESOURCE NAME el valor "function", correspondiente a la segunda parte de nuestros endpoints. Pulsamos CREATE RESOURCE.

 

26º) A continuación, nos situamos sobre el recurso "/function" y volvemos a pulsar CREATE RESOURCE para crearnos el primer endpoint.


27º) Introducimos en RESOURCE NAME el valor "prueba", correspondiente a la tercera parte de nuestro primer endpoint. Pulsamos CREATE RESOURCE.


28º) A continuación, nos situamos otra vez sobre el recurso "/function" y volvemos a pulsar CREATE RESOURCE para crearnos el segundo endpoint.


29º) Introducimos en RESOURCE NAME el valor "{value}", correspondiente a la tercera parte de nuestro segundo endpoint. Pulsamos CREATE RESOURCE.


 

Así quedaría creada la estructura completa de los endpoints de nuestra aplicación.

 

Creación de Métodos de la API Gateway

 

👿 A continuación, tendríamos que crearnos los Métodos necesarios para acceder a los dos endpoints que tenemos configurados en nuestra aplicación.  

/api/function/prueba

/api/function/{value}

Para no alargarnos demasiado, vamos a indicar los pasos para hacerlo con el endpoint acabado en "{value}". La configuración del otro método para "prueba" se haría de forma análoga.

 

30º) Seleccionamos el recurso "/{value}" y pulsamos el botón CREATE METHOD. Saltamos a la ventana de detalle de Método.



31º) En los detalles del método marcamos lo siguiente:

  • Method type: GET
  • Integration type: Lambda function 
  • Lambda proxy integration: ON
  • Lambda function: arn:aws:lambda:us-east-1:905418213440:function:cloud-migration-prueba 


 

⛔ Ten en cuenta que en "Lambda function" yo he puesto el arn de la función Lambda que me he creado previamente. En tu caso ahí tendrás que indicar el nombre de tu propia función Lambda.


32º) Pulsamos CREATE METHOD. Si todo ha ido bien, ahora nuestra API Gateway estará linkada con la función Lambda a través del método GET que nos acabamos de crear.

 

Dentro de la pestaña METHOD REQUEST, si nos vamos al apartado "REQUEST PATHS" podremos ver que el método creado lleva incorporado un parámetro en el path.


Prueba del API Gateway y de la función Lambda


33º) Nos vamos a la pestaña TEST. Ahí, entre otras cosas, nos aparecerá un campito para que introduzcamos el valor del atributo "value". Introducimos cualquier número.


34º) Pulsamos el botón TEST. Nos aparecerá el resultado de la ejecución de la prueba.


👉 Si todo se ha configurado correctamente, la salida de la prueba debería devolvernos un Status 200. En todo caso, debería ser algo similar a lo siguiente:

 

-----------------------------------------------------------------------------
/api/function/{value} - GET method test results
Request
/api/function/100
Latency ms
6010
Status
200
Response body
Hola, desde AWS Lambda - Version 100
Response headers
{
  "Content-Length": "36",
  "Content-Type": "text/plain; charset=UTF-8",
  "X-Amzn-Trace-Id": "Root=1-67acd415-efc5b418e59e013caada4136;Parent=72c167d3dd104fdc;Sampled=0;Lineage=1:4e1ac11b:0"
}

-----------------------------------------------------------------------------

 

Publicar API Gateway en un Stage

 

35º) Ahora necesitamos publicar nuestra URL para que sea accesible desde el exterior. Para ello, desde la API pulsamos el botón DEPLOY API.


 

 36º) En el campo STAGE seleccionamos la opción "New stage". A continuación indicamos un nombre de Stage. Por ejemplo, ponemos "dev". 

Pulsamos DEPLOY. Si todo va bien, nuestros endpoints se publicarán en el stage "dev".

 

👉 Como vemos, asociado al stage se está indicando una URL de acceso que podrá ser invocada públicamente. En nuestro ejemplo, esta URL es la siguiente:

https://jyf8hts8yk.execute-api.us-east-1.amazonaws.com/dev

 

Prueba de la URL de la API Gateway

 

37º) Ahora nos vamos a Postman para realizar la prueba integral del API Gateway y de la función Lambda. Nos creamos un Request de tipo GET con la siguiente URL:

https://jyf8hts8yk.execute-api.us-east-1.amazonaws.com/dev/api/function/100

Si pulsamos SEND, la respuesta debería ser la siguiente:

Hola, desde AWS Lambda - Version 100


😈 Con esto ya habríamos completado la prueba de la funcionalidad "/api/function/{value}" de nuestro microservicio. La implementación de la llamada a "/api/function/prueba" sería análoga a lo que acabamos de hacer. Puedes intentar configurarlo por ti mismo como ejercicio personal.

 

Conclusión

 

Y con esto ya hemos podido revisar cómo podemos trabajar con un microservicio clásico y migrarlo a una función Lambda para incorporarlo a una arquitectura Serverless. Recuerda que para ello, entre otras cosas, hemos tenido que hacer uso de la dependencia Maven mencionada al principio.

aws-serverless-java-container-springboot3

 

Pues eso es todo sobre este asunto, no me queda nada más que comentaros al respecto. Ah, bueno, sí... como siempre, ya sabéis que podéis dejarme aquí abajo cualquier duda que os surja acerca de este tema.


¡Nos vemos en el siguiente post!

Saludos.

 

Comentarios

Entradas populares de este blog

Creación de Webservice SOAP mediante Anotaciones

Configurar Apache Tomcat en Eclipse

Componentes y Ventanas de Java Swing