Cuando se trabaja con proyectos serverless y sobre todo lambdas en AWS, existe la posibilidad de tener servicios en diferentes lenguajes de programación y utilizar diferentes estructuras de proyecto, hay dos enfoques principales mono-repo y multi-repo Ver Figura 1, los dos enfoques tienen diferentes ventajas, en este post nos centraremos en las ventajas del mono-repo, principalmente en como podemos aprovechar el código compartido
Código compartido
Cuando se trabaja con lambdas y mono-repo, una de las ventajas es construir código en común y poder compartirlo entre las diferentes lambdas, hay dos formas de compartir el código en los mono-repos
- Módulo global que se importan en el código y se utilizan: este caso es ideal para JavaScript por su forma de importar modulos, esto permite localizar el módulo fuera de la carpeta del proyecto y encontrar el código en una carpeta diferente, un ejemplo se podría ver así en la siguiente estructura de proyecto:
/
package.json
config.js
serverless.common.yml
libs/
services/
notes-api/
package.json
serverless.yml
handler.js
billing-api/
package.json
serverless.yml
handler.js
notify-job/
serverless.yml
handler.js
se podría crear dentro de libs un archivo llamado aws-sdk.js con el siguiente código
import aws from "aws-sdk";
import xray from "aws-xray-sdk";
// Do not enable tracing for 'invoke local'
const awsWrapped = process.env.IS_LOCAL ? aws : xray.captureAWS(aws);
export default awsWrapped;
y luego importarlo en cualquier handler de la siguiente forma
import AWS from '../../libs/aws-sdk';
esta forma de compartir código tiene algunas desventajas
- El código, cuando se despliega, se agrega dentro del código de cada proyecto, es decir, existirán en cada lambda el mismo módulo de js, lo que agrega más peso al despliegue del código
- No se puede aplicar en python, y esto porque python tiene un dolor de cabeza llamada el PYTHONPATH que es una variable de entorno global que utiliza el intérprete para buscar los módulos, no pueden existir más y es una ubicación en donde se instalan todas las librerías, por tanto, como cada proyecto tiene su entorno virtual, en desarrollo esta variable es modificada apuntando a ese entorno
- Otra opción es utilizar layers, esto es un concepto propio de AWS que permite a cada lambda agregarle una capa de código y librerías que se pueden invocar dentro de la ejecución del código, una desventaja de este enfoque es que la capa se inyecta cuando ya está construida la lambda y en desarrollo se pierde la posibilidad de tener acceso a ella en python, para ello veremos como solucionarlo
Layers en python
Utilizar layers en serverless ayuda entre muchas cosas a permitir reutilizar código, reducir el tamaño del proyecto cuando se despliegue y versionar las actualizaciones del código en el despliegue, este enfoque es muy útil para poder compartir código en python, como ya se vio en JS es mucho más fácil, lo primero que haré es generar un proyecto que contenga la siguiente estructura
La carpeta user contendrá un proyecto de un código lambda y la carpeta shared/libs contendrá el layer, para ello en la carpeta libs agregaré los siguientes archivos
Hemos definido la configuración del layer, el archivo importante es el serverless.yml en donde se define el layer, el path que empaqueta y se define una variable de CloudFormation, esto es muy importante porque los layers se versionan como se ve en la Figura, entonces cada vez que despliegue un cambio se actualizara la versión haciendo que cambie el arn y los proyectos o lambdas seguirán apuntando a una versión antigua aun así cuando se vuelvan a desplegar, tocaría actualizar esto manualmente, al utilizar esta variable se actualiza automáticamente cuando se despliegue esa lambda
bien, para desplegar se ejecuta el comando de siempre sls deploy y obtenemos el siguiente resultado
ahora para referenciar ese layer en el folder user, debemos hacerlo de forma local y cuando se despliegue, para esto se necesitaran dos configuraciones, una en el archivo serverless y otra en el archivo pyproject.toml como se aprecia a continuación
La configuración importante, de nuevo, radica en serverless.yml para el despliegue y para poder importar la librería en desarrollo se configura el pyproject.toml, en el archivo serverlees.yml se pone a apuntar al layer desplegado anteriormente
layers:
- ${cf:libs-dev.SharedLibsExport}
cf referencia una variable de CloudFormation que exporte anteriormente, esto con el fin de lograr una actualización de versiones de este layer de forma automática, igualmente también podría haber puesto directamente con el arn, seguidamente agrego la variable PYTHONPATH
environment:
PYTHONPATH: /opt
esto debido a que el layer se agrega a una carpeta /opt no directamente a las libs de python, con este fix los layers que se creen automáticamente quedan en los módulos que se importan en el código, y aquí vemos como se importa en el módulo register.py
from auxiliary import auxiliary
esta importación en desarrollo vendrá del módulo local, en donde en el pyprojet.toml se le indica en la siguiente línea
[tool.poetry.dev-dependencies]
libs = {path = "../shared/libs" }
donde el path es la ubicación del módulo a empaquetar localmente, y en despliegue del layer queda referenciado en la configuración del .yml, ahora despliego y veo lo siguiente
Aquí se puede apreciar el despliegue y se aprecia el layer, ahora pruebo por medio del API
como se aprecia se realiza la conversión del campo age de int a str correctamente, y aquí ya se ha conectado el código de la lambda con el layer, ahora si desea utilizar junto a layers de terceros, te dará error porque PYTHONPATH no puede ser /opt para layers de terceros, esto lo solucionaremos en un siguiente artículo
Conclusiones
Como se pudo apreciar, compartir código en Python requiere de layers; sin embargo, también requiere en desarrollo el empaquetado y la instalación local de ese paquete, y poetry realmente lo hace muy fácil porque automáticamente gestiona este proceso, sin obligarme a agregar ningún archivo adicional, por eso poetry sigue ganando puntos, al final si se quieren utilizar layers propios mezclados con layers de terceros no funcionara por el python path, esto definitivamente tiene solución, donde en el próximo artículo espero hablar de ello