Ha habido un gran revuelo en torno a las funciones como servicio (FaaS), comúnmente denominadas sin servidor. Un proveedor popular para estas funciones es Amazon con su AWS Lambda servicio. Se puede crear una función en cualquiera de las tecnologías de Lambda compatibles, por ejemplo Node.js, implementarla en AWS Lambda, obtener acceso a ella a través de AWS API Gateway y escalarla por sí sola para satisfacer la demanda.
Vamos a ver cómo utilizar Node.js con AWS Lambda para crear funciones que puedan comunicarse con documentos NoSQL del estilo de Servidor Couchbase.
Se asume que tienes un clúster o una instancia única de Couchbase Server ejecutándose en algún lugar de la nube. En otras palabras, este proyecto no funcionará si estás ejecutando Couchbase en tu máquina local. Debido a que estamos ejecutando esto en AWS, puede tener sentido que Couchbase exista como una instancia EC2. Esto se puede hacer por utilizando una AMI disponible.
Creación de un proyecto FaaS para Lambda y API Gateway con Serverless Framework
Si alguna vez has trabajado con AWS Lambda y AWS API Gateway antes, sabrás que no es la experiencia más agradable cuando se empieza algo desde cero y luego se despliega. Para hacernos la vida un poco más fácil, vamos a utilizar la herramienta Framework sin servidorque se encargará de gran parte del trabajo pesado, sin cambiar la forma en que desarrollamos las funciones Lambda.
Asumiendo que ya tienes Node.js instalado, ejecuta lo siguiente para obtener la CLI de Serverless Framework:
1 |
npm instale -g sin servidor |
Con la CLI, podemos generar nuevos proyectos basados en plantillas disponibles, no limitadas a AWS. Para crear un nuevo proyecto con la CLI, ejecute lo siguiente:
1 |
sin servidor crear --plantilla aws-nodejs --ruta ./mi-proyecto |
Vamos a crear una serie de funciones dentro de la función mi-proyecto que se ha creado. Aquí es donde las cosas pueden ponerse un poco raras.
Nuestro proyecto Node.js va a requerir el SDK de Couchbase, un paquete para generar valores UUID, y un paquete para validación de datos. El problema es que estoy usando un Mac y Lambda está usando un sabor especial de Linux. Si intento descargar las dependencias en mi Mac, no serán compatibles con Linux. En su lugar, tenemos que saltar a través de algunos aros.
Hay un artículo en Internet titulado, Uso de dependencias nativas con AWS Lambdaque explica cómo se puede utilizar Docker para llevar a cabo la tarea de las dependencias nativas del proyecto que funcionan para Lambda.
En resumen, querrás ejecutar lo siguiente con Docker instalado y con el CLI en el directorio actual de tu proyecto:
1 2 |
docker tire de amazonlinux docker ejecute -v $(pwd):/lambda-proyecto -it amazonlinux |
Los comandos anteriores extraerían la imagen de Amazon Linux y desplegarían un contenedor, mapeando el directorio actual del proyecto como un volumen contenedor. Dentro del contenedor, Node.js puede ser instalado y nuestras dependencias de paquetes pueden ser obtenidas. Para ver cómo hacer esto, echa un vistazo a un artículo anterior que escribí titulado, Implementación de dependencias de Node.js nativo en AWS Lambda.
Necesitarás instalar las siguientes tres dependencias para el proyecto:
1 2 3 |
npm instale couchbase --guardar npm instale joi --guardar npm instale uuid --guardar |
No te desanimes de continuar por el requisito de Amazon Linux. Esencialmente solo estás ejecutando los tres comandos desde dentro del contenedor y como el contenedor tiene un volumen mapeado, cualquier archivo obtenido terminará directamente en tu proyecto.
Desarrollo de funciones para operaciones CRUD contra la base de datos NoSQL
Con el proyecto Serverless Framework listo, podemos centrarnos en lo que realmente cuenta. El objetivo aquí es crear cuatro funciones diferentes, una para cada creación, recuperación, actualización y eliminación de documentos NoSQL de Couchbase. Así es, estamos haciendo un conjunto de funciones CRUD.
Abra el archivo handler.js porque necesitamos arrancarlo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
'use strict'; const Couchbase = requiere("couchbase"); const UUID = requiere("uuid"); const Joi = requiere("joi"); var grupo = nuevo Couchbase.Grupo("couchbase://tu-servidor-aquí"); grupo.autentifique("demo", "123456"); var cubo = grupo.openBucket("ejemplo"); cubo.en("error", error => { consola.dir(error); }); módulo.exportaciones.crear = (evento, contexto, devolución de llamada) => { }; módulo.exportaciones.recuperar = (evento, contexto, devolución de llamada) => { }; módulo.exportaciones.actualización = (evento, contexto, devolución de llamada) => { }; módulo.exportaciones.borrar = (evento, contexto, devolución de llamada) => { }; |
En el código anterior hemos importado las dependencias de nuestro proyecto, conectado a un cluster de Couchbase, autenticado con dicho cluster, y abierto un Bucket en particular. Asegúrate de cambiar la información de Couchbase para reflejar tu información real.
También hemos creado las cuatro funciones que se mencionaron anteriormente. Sin embargo, por ahora estas funciones no hacen nada.
Algo importante a tener en cuenta aquí. Nuestra lógica de conexión a la base de datos está ocurriendo fuera de nuestras funciones. Es una tarea costosa conectarse a la base de datos con cada invocación de una función. Al mover esta información fuera, sigue siendo accesible por cualquier contenedor de funciones Lambda.
Empezando por el crear
función:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
módulo.exportaciones.crear = (evento, contexto, devolución de llamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var esquema = Joi.objeto().llaves({ nombre: Joi.cadena().obligatorio(), apellido: Joi.cadena().obligatorio(), tipo: Joi.cadena().prohibido().por defecto("persona") }); var datos = JSON.analizar(evento.cuerpo); var respuesta = {}; var validación = Joi.valide(datos, esquema); si(validación.error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify(validación.error.detalles) }; devolver devolución de llamada(null, respuesta); } var id = UUID.v4(); cubo.insertar(id, validación.valor, (error, resultado) => { si(error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify({ código: error.código, mensaje: error.mensaje }) }; devolver devolución de llamada(null, respuesta); } datos.id = id; respuesta = { statusCode: 200, cuerpo: JSON.stringify(datos) }; devolución de llamada(null, respuesta); }); }; |
En el código anterior ocurren muchas cosas, así que tenemos que desglosarlo.
Primero añadiremos la siguiente línea:
1 |
contexto.callbackWaitsForEmptyEventLoop = falso; |
Al añadir la línea anterior, estamos cambiando la forma en que la función espera para responder. Si no lo establecemos en false, las cosas asíncronas a seguir probablemente agotarán el tiempo de espera antes de completarse.
1 2 3 4 5 6 7 8 |
var esquema = Joi.objeto().llaves({ nombre: Joi.cadena().obligatorio(), apellido: Joi.cadena().obligatorio(), tipo: Joi.cadena().prohibido().por defecto("persona") }); var datos = JSON.analizar(evento.cuerpo); var respuesta = {}; var validación = Joi.valide(datos, esquema); |
Dado que vamos a crear datos basados en la entrada del usuario, es una buena idea validar dicha entrada del usuario. Utilizando Joipodemos crear un esquema de validación y usarlo para validar el cuerpo de datos pasado con el evento. Usted puede reconocer que Joi es un marco de validación que he utilizado en artículos anteriores, tales como, Crear una API RESTful con Node.js, Hapi y Couchbase NoSQL.
1 2 3 4 5 6 7 |
si(validación.error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify(validación.error.detalles) }; devolver devolución de llamada(null, respuesta); } |
Si hay un error de validación, enviaremos un error 500 como respuesta. Si no, procederemos a generar una nueva clave de documento, y guardarla en Couchbase Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var id = UUID.v4(); cubo.insertar(id, validación.valor, (error, resultado) => { si(error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify({ código: error.código, mensaje: error.mensaje }) }; devolver devolución de llamada(null, respuesta); } datos.id = id; respuesta = { statusCode: 200, cuerpo: JSON.stringify(datos) }; devolución de llamada(null, respuesta); }); |
Tenga en cuenta cómo espera Lambda que se formateen las respuestas. Las respuestas deben tener un statusCode
y un cuerpo
.
Ahora echemos un vistazo al recuperar
función:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
módulo.exportaciones.recuperar = (evento, contexto, devolución de llamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var respuesta = {}; var declaración = "SELECT META().id, `" + cubo.Nombre + "`.* FROM `" + cubo.Nombre + "WHERE type = 'person'"; var consulta = Couchbase.N1qlQuery.fromString(declaración); cubo.consulta(consulta, (error, resultado) => { si(error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify({ código: error.código, mensaje: error.mensaje }) }; devolver devolución de llamada(null, respuesta); } respuesta = { statusCode: 200, cuerpo: JSON.stringify(resultado) }; devolución de llamada(null, respuesta); }); }; |
El objetivo con el recuperar
es usar N1QL para consultar todos los documentos en Couchbase y devolverlos. Dependiendo del resultado, formateamos la respuesta Lambda apropiadamente. Ten en cuenta que estamos alterando el callbackWaitsForEmptyEventLoop
valor. Esto ocurrirá en cada una de nuestras funciones.
Cómo estructuramos el actualización
será similar a cómo estructuramos la función crear
función:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
módulo.exportaciones.actualización = (evento, contexto, devolución de llamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var esquema = Joi.objeto().llaves({ nombre: Joi.cadena().opcional(), apellido: Joi.cadena().opcional() }); var datos = JSON.analizar(evento.cuerpo); var respuesta = {}; var validación = Joi.valide(datos, esquema); si(validación.error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify(validación.error.detalles) }; devolver devolución de llamada(null, respuesta); } var constructor = cubo.mutateIn(evento.pathParameters.id); si(datos.nombre) { constructor.sustituir("nombre", datos.nombre); } si(datos.apellido) { constructor.sustituir("apellido", datos.apellido); } constructor.ejecutar((error, resultado) => { si(error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify({ código: error.código, mensaje: error.mensaje }) }; devolver devolución de llamada(null, respuesta); } respuesta = { statusCode: 200, cuerpo: JSON.stringify(datos) }; devolución de llamada(null, respuesta); }); }; |
Fíjate que empezamos haciendo una validación de datos basada en nuestro esquema. Si los datos se consideran válidos, podemos proceder a realizar operaciones de subdocumento contra Couchbase:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var constructor = cubo.mutateIn(evento.pathParameters.id); si(datos.nombre) { constructor.sustituir("nombre", datos.nombre); } si(datos.apellido) { constructor.sustituir("apellido", datos.apellido); } constructor.ejecutar((error, resultado) => { si(error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify({ código: error.código, mensaje: error.mensaje }) }; devolver devolución de llamada(null, respuesta); } respuesta = { statusCode: 200, cuerpo: JSON.stringify(datos) }; devolución de llamada(null, respuesta); }); |
Esencialmente estamos creando un constructor de mutaciones basado en la información encontrada en la solicitud. Las mutaciones añadidas ocurrirán contra una clave cuando se ejecuten. El resultado de la ejecución será devuelto como una respuesta de Lambda.
Toma nota de lo siguiente:
1 |
evento.pathParameters.id |
Estos datos no proceden del cuerpo. Se configurará en uno de los próximos pasos.
Ahora nos queda borrar
función:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
módulo.exportaciones.borrar = (evento, contexto, devolución de llamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var esquema = Joi.objeto().llaves({ id: Joi.cadena().obligatorio() }); var datos = JSON.analizar(evento.cuerpo); var respuesta = {}; var validación = Joi.valide(datos, esquema); si(validación.error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify(validación.error.detalles) }; devolver devolución de llamada(null, respuesta); } cubo.eliminar(datos.id, (error, resultado) => { si(error) { respuesta = { statusCode: 500, cuerpo: JSON.stringify({ código: error.código, mensaje: error.mensaje }) }; devolver devolución de llamada(null, respuesta); } respuesta = { statusCode: 200, cuerpo: JSON.stringify(datos) }; devolución de llamada(null, respuesta); }); }; |
Si te has mantenido al tanto de todo hasta ahora, el borrar
no le parecerá nada nuevo. Estamos validando datos y ejecutándolos contra la base de datos.
Con las funciones creadas, podemos centrarnos en la información de configuración del Serverless Framework. Abre el archivo serverless.yml e incluya lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
servicio: couchbase-lambda proveedor: nombre: aws tiempo de ejecución: nodojs6.10 funciones: crear: manipulador: manipulador.crear eventos: - http: ruta: crear método: Correo electrónico: recuperar: manipulador: manipulador.recuperar eventos: - http: ruta: recuperar método: consiga actualización: manipulador: manipulador.actualización eventos: - http: ruta: actualización/{id} método: poner borrar: manipulador: manipulador.borrar eventos: - http: ruta: borrar método: borrar |
La parte más importante del documento YAML anterior son las funciones. Hemos nombrado cada función y las hemos vinculado a la función manejadora apropiada. La dirección eventos
son la forma en que se invocarán estas funciones desde un navegador. La dirección eventos
son configuraciones para AWS API Gateway.
En el eventos
definimos la información de la ruta, cómo se puede acceder a cada endpoint, si es desde una petición GET o una petición POST o algo más, y cualquier tipo de variables de ruta.
¿Recuerdas la frase que te dije que recordaras?:
1 |
evento.pathParameters.id |
Esto se asigna al id
valor en el actualizar/{id}
camino.
En este momento el proyecto está listo para ser desplegado en Amazon Web Services.
Implementación de las funciones en AWS Lambda con soporte de API Gateway
No vamos a entrar en el meollo de la cuestión cuando se trata de configurar Serverless Framework con sus claves públicas y privadas de Amazon, pero nos preocuparemos por el despliegue, suponiendo que estén configuradas.
Desde la CLI de Serverless Framework, ejecute lo siguiente:
1 |
sin servidor despliegue |
El comando anterior tomará el código de las funciones así como el node_modules que se generaron a partir del contenedor Docker de Amazon Linux, y empujarlos a la nube de AWS. Serverless Framework se preocupará de configurar AWS Lambda y API Gateway basándose en lo que había en el archivo serverless.yml archivo.
Conclusión
Acaba de ver cómo comunicarse con Servidor Couchbase a partir de un conjunto de funciones controladas por AWS Lambda y AWS API Gateway. Esto es útil si quieres crear aplicaciones que se facturen por el tiempo que se utilizan en lugar de por el tiempo que están activas. Incluir Couchbase no fue muy diferente a incluirlo en cualquier otra aplicación Node.js, con la excepción de las dependencias nativas de Node.js.
Si decides usar Couchbase en la nube, asegúrate de que los puertos correctos están abiertos para Lambda. Yo cometí el error de olvidarme de abrir todos los puertos que requería el SDK y me retrasó un poco.
Para obtener más información sobre el uso de Couchbase Server, consulte la página Portal para desarrolladores de Couchbase.
Hola Nic,
He estado mirando en conseguir esto en marcha con un ejemplo que sólo se conecta a un servidor couchbase (en AWS) y tiene una función que devuelve texto de prueba.
Da este error:
"errorMessage": "/var/lang/lib/libstdc++.so.6: versión `CXXABI_1.3.9′ no encontrada (requerida por /var/task/node_modules/couchbase/build/Release/couchbase_impl.node)",
y mi google-fu me está fallando. Sabes cuál es el problema?
¿Necesito actualizar c++ en la imagen docker?
Sin embargo, parece que se trata de un problema de versión del compilador en AWS.