En estos últimos tiempos, con el crecimiento a pasos agigantados del Cloud Computing, aparece el Serverless o computación sin servidor, el cual nos ofrece una opción más para construir nuestras aplicaciones.

Como digo siempre, “para aprender, lo mejor es empezar a hacerlo”, y, en este artículo, nos enfocaremos en una implementación básica con Spring Cloud Function y Java para, posteriormente, desplegarlo en AWS Lambda y API Gateway.

Introducción al Serverless

Como una breve introducción de lo que es Serverless podríamos decir que es un modelo de ejecución de aplicaciones en la nube, en el cual el proveedor administra dinámicamente la creación y asignación de recursos de la máquina.

Tenemos a los principales proveedores con sus respectivos productos como AWS Lambda, Azure Function, Google Cloud Functions, etc.

Spring Cloud Function es un componente del stack de Spring el cual nos permite hacer aplicaciones Serverless siendo agnósticos al lugar donde despleguemos. Una de sus principales ventajas es que la curva de aprendizaje es relativamente baja, dado que habilita funcionalidades como inyección de dependencias, trazabilidad, starters, etc., del ecosistema de Spring para que se pueda centralizar la mayor parte del esfuerzo en implementar la lógica de la aplicación.

Configuración

Ahora, con esta introducción, vamos a crear nuestra aplicación Serverless con Spring Cloud Function.

Ingresamos a start.spring.io para generar nuestro arquetipo con las siguiente dependencias:

Si quieres tener el código, lo comparto en git.

Implementación

Como podemos ver, es un arquetipo como los que ya conocemos con su Main Application y su anotación @SpringBootApplication.

Spring Cloud Function soporta las 3 tipos de interfaces de java 8 y son:

  1. Function: acepta un request (input) de entrada y retorna un response (output) de salida. Normalmente se utiliza este.
  2. Consumer: acepta un input y no retorna nada.
  3. Supplier: no acepta ningún tipo de input, solo retorna una respuesta.

Function

Para la opción Function la aplicación será sencilla. La lógica consistirá que, mediante la llamada a un servicio rest con el nombre de una empresa, se pueda retornar el id, nombre y la fecha de registro.
Vamos a crear los modelos de request y response respectivamente para eso vamos a crear la clase CompanyRq.java.

package com.function.app.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Data
@AllArgsConstructor
public class CompanyRq {

    private String name;

}

Seguidamente de la siguiente clase de respuesta CompanyRs.java.

package com.function.app.model;

import java.time.LocalDate;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class CompanyRs { 

    private String id;
    private String name;
    private LocalDate date;

}

Como segundo paso vamos a crear el servicio que realizará nuestra lógica de negocio, crearemos la clase CompanyServiceImpl.java.
Simplemente obtendremos el request asignaremos un UUID como id aplicaremos un toUpperCase al name y asignaremos como último una fecha.

package com.function.app.service.impl;

import java.time.LocalDate;
import java.util.UUID;

import org.springframework.stereotype.Service;

import com.function.app.model.CompanyRq;
import com.function.app.model.CompanyRs;
import com.function.app.service.CompanyService;

@Service
public class CompanyServiceImpl implements CompanyService {

    @Override
    public CompanyRs createCompany(CompanyRq companyRq) {
        final String companyId = generateId();
        final String companyName = getCompanyName(companyRq.getName());

        return CompanyRs.builder().id(companyId).name(companyName).date(LocalDate.now()).build();
    }

    private String generateId() {
        return UUID.randomUUID().toString();
    }

    private String getCompanyName(final String companyName) {
        return companyName.toUpperCase();
    }

}

Ahora procedemos a crear la clase CompanyFunction.java que lo registramos en el contexto de Spring con un @Component e implementaremos la interfaz Function que, como anteriormente habíamos dicho, requiere de un Input y Output para funcionar.

package com.function.app.service.function;

import java.util.function.Function;

import org.springframework.stereotype.Component;

import com.function.app.model.CompanyRq;
import com.function.app.model.CompanyRs;
import com.function.app.service.CompanyService;

import lombok.AllArgsConstructor;

@Component(value = "company")
@AllArgsConstructor
public class CompanyFunction implements Function<CompanyRq, CompanyRs> {

    private CompanyService companyService;

    @Override
    public CompanyRs apply(CompanyRq request) {
        return companyService.createCompany(request);

    }

}

Como último paso, vamos a probar en modo local. Para eso vamos a iniciar nuestra aplicación Serverless desde la clase Application.java.

Ahora, ejecutamos un curl para ver si está realizando la lógica con un CURL:

curl -H "Content-Type: application/json" localhost:8080/company -d '{"name": "Coca Cola"}'

Y, efectivamente, sí está devolviendo la información de la Empresa como lo habíamos pensado antes.

Consumer

Para el tipo Consumer vamos a recibir un input y lo mostraremos en consola para eso crearemos la clase CompanyConsumer.java.

package com.function.app.service.consumer;

import java.util.function.Consumer;

import org.springframework.stereotype.Component;

import com.function.app.model.CompanyRq;
import com.function.app.service.CompanyService;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Component
@AllArgsConstructor
@Slf4j
public class CompanyConsumer implements Consumer<CompanyRq> {

    private CompanyService companyService;

    @Override
    public void accept(CompanyRq companyRq) {
        log.info("Company Information: {}", companyService.findCompanyByName(companyRq).toString());
    }

}

Iniciamos la aplicación y ejecutamos el CURL:

Visualizamos el log y nos muestra el toString del objeto.

Supplier

Para el tipo Supplier solo vamos a devolver una respuesta y para eso crearemos la clase CompanySupplier.java.

package com.function.app.service.supplier;

import java.util.function.Supplier;

import org.springframework.stereotype.Component;

import com.function.app.model.CompanyRq;
import com.function.app.model.CompanyRs;
import com.function.app.service.CompanyService;

import lombok.AllArgsConstructor;

@Component
@AllArgsConstructor
public class CompanySupplier implements Supplier<CompanyRs> {

    private CompanyService companyService;

    @Override
    public CompanyRs get() {

        CompanyRq companyRq = new CompanyRq();
        companyRq.setName("Coca Cola");
        return companyService.findCompanyByName(companyRq);
    }

}

Iniciamos la aplicación y ejecutamos el CURL:

El nombre del recurso en la aplicación con el cual vamos a acceder será el nombre del Bean con el cual se está creando. Actualmente Spring no dispone de la customización del nombre del recurso.

Implementando y desplegando en AWS Lambda

Implementando para AWS Lambda

Para poder desplegar en AWS Lambda, Spring nos ofrece adaptadores para los principales proveedores Cloud.

En este caso, vamos a crear una clase que la llamaremos CompanyFunctionAWSHandler.java que extenderemos de la clase SpringBootRequestHandler.

package com.function.app.handler;

import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler;

import com.function.app.model.CompanyRq;
import com.function.app.model.CompanyRs;

public class CompanyFunctionAWSHandler extends SpringBootRequestHandler<CompanyRq, CompanyRs> {
}

Posteriormente, tenemos que agregar un plugin gradle con el nombre de ShadowJar que una de sus funciones, básicamente, es realizar el empaquetado el jar con todas sus dependencias en la ruta build/libs/***.jar, a este Jar le denominaremos fat-jar.

Para Maven tenemos un plugin que se llama Shade plugin que realiza la misma función.

Describiremos brevemente las propiedades del plugin ShadowJar:

Finalmente, usamos el siguiente comando gradle para generar el fat-jar:

./gradlew shadowJar

Desplegando en AWS Lambda

Iniciamos sesión en AWS Console vamos a Servicios > Lambda > Crear una Función.

Crear desde Cero > Ingresamos un nombre para nuestra Function, en este caso “company-function,” y en Tiempo de Ejecución Java8 > Crear una Función.

Como vemos en la imagen, se crea la Function con el nombre que ingresamos. Ahora, procedemos a cargar el fat-jar con nombre “function-microservice-0.0.1-SNAPSHOT-aws.jar” >. En la casilla de controlador ingresamos el nombre del Handler “com.function.app.handler.CompanyFunctionAWSHandler” > En el apartado de Variables de Entorno incluimos las siguientes variables: “FUNCTION_NAME : company” y “MAIN_CLASS : com.function.app.Application”. Y, finalmente, le damos a Guardar.

Ahora, lo probaremos. Vamos a dar click en el botón Probar > Crear Evento de prueba - companyFunctionTest - En el payload de prueba ingresamos el json de prueba > Guardar.

Como paso último, vamos a realizar el Test final.Hacemos click en el botón Probar y listo. ¡Lo que tanto estábamos esperando!

Se puede ver que el json de respuesta es correctamente lo que habíamos probado anteriormente en local.

Integrando con Api Gateway

API Gateway es el producto de AWS para API Management. Vamos a exponer nuestro Serverless en una API.

Buscamos API Gateway > API Rest > Seleccionar en Protocolo Rest, Api nueva, y en Nombre de API ingresamos CompanyLambdaApi y pinchamos en crear API.

Lo siguiente será crear un recurso de la API. Click en la Lista desplegable Acciones y Crear Recurso > Nombre del recurso “Company” > Crear Recurso.

Vamos a crear el método asociado al endpoint /company. En este caso será un POST, click en el recurso /company > click en la lista desplegable de Acciones > Crear Método > le asignaremos un POST y aplicar los cambios.

En la configuración del método seleccionamos lo siguiente y le damos a guardar:

Ingresamos a Solicitud de Integración y configuramos los siguiente en Plantillas de Mapeo:

Y, como último paso de la implementación, pinchamos en Lista desplegable de Acciones > Implementar API > Seleccione la Etapa de implementación de preferencia y click en el botón Implementación.

Podemos ver que nos genera una url que es nuestra API ya implementada. Ahora, con un CURL o utilizando, como en este caso, POSTMAN realizamos una petición POST con el payload correcto.

Conclusiones

Este artículo pretende dar una introducción al desarrollo de aplicaciones Serverless con Spring Cloud Function orientado al proveedor AWS.

Las aplicaciones desarrolladas en la arquitectura Serverless reducen el tiempo y costo de todo lo correspondiente a la creación y mantenimiento de la infraestructura (servidores).

Hay que tener en consideración que la monitorización de los logs como las métricas son necesarias en una aplicación y deberíamos utilizar componentes adicionales para gestionarlas.

La opción implementada con el adaptador de Spring Cloud Function para AWS nos brinda esa abstracción y agosticidad que necesitamos para que se puedan desarrollar aplicaciones sin grandes complejidades técnicas e intuitivamente.

Referencias

Puedes seguir profundizando en el tema con algunos artículos de nuestro blog y en la documentación de referencia:

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.