Creando Un API-Rest Con AWS Lambda y API-Gateway - Paradigma
Creando Un API-Rest Con AWS Lambda y API-Gateway - Paradigma
Gateway - Paradigma
Clip source: Creando un API-Rest con AWS Lambda y API-Gateway - Paradigma
En este post voy a contar cómo ha sido mi experiencia montando un API-Rest totalmente con
arquitectura ServerLess.
El proveedor cloud que hemos escogido para este proyecto es Amazon, que nos ofrece los siguientes
productos:
Vamos a ver un poco más en detalle todas las piezas que componen nuestro puzle:
Qué es ServerLess
ServerLess es un tipo de arquitectura que**, como su propio nombre indica, no precisa de un
servidor**. ¿Qué quiere decir esto?, ¿dónde se va a ejecutar nuestro código si no hay un servidor?
Estos eventos pueden ser tanto eventos web como eventos provenientes de otras partes de la
infraestructura.
Analicemos en este ejemplo cómo sería la subida de un fichero al servidor. Nuestra función se
ejecutaría de la siguiente forma:
Google también está apostando fuerte con Google Cloud Functions y Azure con Azure Functions.
Qué es AWS Lambda
Como ya hemos visto en el punto anterior, las lambdas son funciones que se caracterizan por
desempeñar un objetivo muy concreto, como reaccionar ante la subida de un fichero o un evento
web.
En el caso de la plataforma de Amazon son multilenguaje, se pueden escribir en Java, Phyton, Node.js
y más recientemente han incorporado GO, del que dicen es el lenguaje con la mejor performance.
Como podemos ver, es muy simple. Solo necesitamos un handler, que es el que ejecutará nuestro
código. Esta función recibe dos parámetros event y context y, opcionalmente, callback.
El event es el evento que desencadena (trigger) la ejecución de una función lambda. El context nos da
información acerca de la lambda: tiempo de ejecución pendiente, alias de la función lambda
invocada...
Veamos algunos ejemplos de triggers. Una función lambda puede ser invocada entre otros triggers
por uno de estos:
Cada petición concurrente que llega, si hablamos de un API-Rest, levanta uno de estos contenedores.
Por defecto AWS ha determinado que el número de contenedores concurrentes es 1000 pudiendo
configurarse para aumentar este número.
Los contenedores se reutilizan, es decir, cuando la ejecución de una lambda finaliza no se destruye su
contenedor inmediatamente, sino que se queda levantado a la espera de nuevas peticiones, pero no
tenemos control sobre el ciclo de vida de este contenedor.
Cada función lambda se puede configurar para que disponga de más memoria. A más memoria más
coste y mejor será el contenedor sobre el que se despliegue.
En el caso de Node, el desplegable de una lambda es un zip que incluye el handler y los ficheros .js
necesarios que se requieran desde el handler y la carpeta node_modules con las dependencias
necesarias para ejecutar nuestra lambda.
Además en el API-Gateway gestionamos los entornos (stages), definimos variables por entorno,
nos permite documentar nuestros endpoints, mockearlos por ejemplo cuando la integración no
está lista, securizarlos...
Después de bastantes días de investigación y diversas pruebas, optamos por el patrón mono-repo.
Este patrón, como su nombre indica, es un único repositorio que alberga todos los paquetes.
Para ello usamos una herramienta llamada Lerna, que nos facilita la vida a la hora de ejecutar
acciones como:
Gestión de la configuración
Comencemos por explicar qué información manejamos en la configuración de nuestro proyecto.
En nuestro caso, en el módulo config manejamos la información que nos conecta con las bases de
datos, configuraciones de seguridad, etc… que varían según el entorno en el que nos movamos.
Más adelante hablaremos de los entornos y cómo hemos resuelto este escollo en ServerLess, ya que
como hemos dicho anteriormente aquí no hay maquinas.
Para solucionar esta problemática típica de arquitecturas basadas en microservicios, optamos en
primera instancia por una función lambda, que sería invocada cada vez que se llamase a una de las
otras funciones.
Los beneficios fueron muchos, entre otros poder cambiar la configuración en caliente, sin afectar
al resto de funciones, ya que cada vez que una función fuese llamada, invocará a la lambda de
configuración para obtener la información que nos permite, entre otras cosas, conectar con la base
de datos correspondiente.
Este primer aproach lo descartamos, ya que nos dimos cuenta que muy pocas veces íbamos a
cambiar esa configuración. Por contra, estamos aumentando el coste, ya que por cada llamada a un
endpoint íbamos a realizar dos.
Finalmente optamos por una dependencia interna, creamos un módulo npm que sería inyectado
como una dependencia interna en nuestros paquetes. Así solventamos el problema del coste, pero
sin embargo, un cambio en este módulo nos obligó a re-desplegar todos los módulos que
dependían de él.
Para nuestra integración continua, hemos definido diferentes niveles de modelos, con un sistema de
pesos que instala las dependencias siguiendo un orden establecido.
De esta forma tenemos el modulo config; paquetes denominados services, que se utilizan para
extraer código común y que pueda ser reutilizable entre lambdas; y, por último, muy recientemente
hemos introducido el concepto de controller.
Un controller es una lambda en la que manejamos un CRUD sobre un resource. Imaginad que
tenemos una entidad libros, como sabéis en un API Rest esta entidad se explota con los siguientes
endpoints:
Esto hizo, entre otras cosas, que se incrementasen los tiempos de despliegue y fue por lo que
decidimos introducir el concepto de controller, que agrupa en una única lambda todas las
operaciones sobre una entidad en concreto, pasando de 5 funciones lambda a una única.
Como todo tiene su lado bueno y su lado malo. El lado bueno es evidente: menos paquetes. El
malo es que perdemos en independencia entre métodos, escalado de un método en concreto,
que tenga un uso más exhaustivo; y también perdemos la independencia de los logs.
En conclusión, con Lerna gestionamos todas las dependencias, definimos módulos que no son
lambdas, sino librerías npm locales, que nos permiten reutilizar código, y hacer menos código
repetido.
Gestión de Entornos
Hemos definido diferentes entornos para nuestro proyecto dev, pre y pro, pero la integración
continua queda fuera del ámbito de este artículo. Lo que quiero destacar es que se pueden generar
diferentes entornos partiendo del mismo código, os explico cómo a continuación.
En las lambdas tenemos alias y versiones. Una versión es cada actualización que hacemos de
nuestro código. En lambda son secuenciales 1,2,3… y no admite versionados custom. Por otro lado,
alias son punteros a versiones.
Vamos a poner un ejemplo. Tenemos una función lambda “myLambda” y el siguiente escenario:
Lo que nos indica este escenario es que dev apunta a la versión 10. que es un .zip con la versión más
reciente.
En pre hemos estado probando la versión 8 y, tras validarla, la hemos subido a pro. Tanto en pre
como en pro apuntamos a la misma versión, pero ¿cómo hacemos para que accedan a diferentes
bases de datos, por ejemplo?
Para solucionar este problema, lo que tenemos que hacer es parametrizar el API-Gateway. Es decir,
en nuestro API-Gateway lo que hacemos es una template de nuestros endpoint, de forma que en
lugar de apuntar a una lambda en concreto apuntamos a una plantilla que será de la forma
myLambda:${myStageVariable}.
Aquí vemos, los diferentes Stages (entornos) que hemos creado y como se definen variables para
ellos.
myStageVariable se define a nivel de cada uno de los diferentes stages. En el caso de la foto de arriba,
nuestra variable se define como lambdaAlias y su valor es acceptance.
Concluyendo...
Hemos creado un API-Rest definiendo nuestros endpoints con AWS API-Gateway y hemos
conseguido generar diferentes entornos en nuestro API.
Además, invocamos a las funciones lambda cuyo alias se corresponde con el entorno, permitiendo
tener diferentes configuraciones y diferentes versiones de código por entorno.
Estos son los pasos básicos que necesitas para crear un API-Rest con AWS Lambda y API-Gateway.
¿Te atreves a probar tú?