Los clientes a menudo nos dicen que se están preparando para migrar de MongoDB a Couchbase. Vienen, en parte, porque están cansados de los problemas que han experimentado aprendiendo a consultar MongoDB. Couchbase con N1QL proporciona una alternativa mejor, especialmente para escalar aplicaciones modernas.
Por desgracia, con MongoDB, las opciones de migración pueden ser limitadas. Una sobre la que escribí recientemente implica mover de De MongoDB con Mongoose a Couchbase con Ottoman. El núcleo de ese tutorial giraba en torno al uso de dos herramientas ODM diferentes en Node.js que compartían las mismas APIs, haciendo que la transición fuera casi perfecta. Sin embargo, ¿qué pasa si no estás usando un ODM en MongoDB y no tienes interés en usar uno?
En esta ocasión vamos a echar un vistazo a cómo mover una aplicación Node.js que utiliza MongoDB y el lenguaje de consultas de MongoDB a Couchbase con N1QL. En resumen, N1QL es una tecnología que te permite ejecutar consultas SQL contra datos JSON complejos. Esto hace que no sólo sea fácil de usar, sino también muy limpio en la capa de aplicación. Más información sobre N1QL se puede encontrar en el Portal para desarrolladores de Couchbase.
Vamos a utilizar el mismo problema de ejemplo utilizado en la sección artículo anteriorpero no pasa nada si no lo has visto. Todo aquí se iniciará con una pizarra limpia.
Requisitos
Hay algunos requisitos que deben cumplirse para asegurarse de que tiene éxito con esta guía. Se pueden ver de la siguiente manera:
Dado que vamos a ver tanto el equivalente de MongoDB como el de Couchbase, deberías tener ambas bases de datos a tu disposición. Si ya eres desarrollador de MongoDB, puede que muchas cosas te resulten familiares, pero eso es decisión tuya.
Comprender el modelo de datos NoSQL
Antes de desarrollar la API RESTful con cualquiera de las dos tecnologías, conviene entender primero el modelo de datos que se va a utilizar.
Aunque tanto MongoDB como Couchbase son bases de datos documentales, no son totalmente iguales. MongoDB almacena los datos como BSON mientras que Couchbase lo hace como JSON. Desde una perspectiva de modelado, realmente no nos importará.
Tomemos el modelo de datos de una escuela en la que hay alumnos y cursos. Para cada curso ofrecido en la escuela, usted podría tener un documento que se parece a lo siguiente:
1 2 3 4 5 6 7 8 9 10 |
{ "id": "curso-1", "tipo": "curso", "Nombre": "Informática 101", "término": "F2017", "estudiantes": [ "estudiante-1", "estudiante-2" ] } |
Cada curso mantendrá una lista de todos los alumnos que se hayan matriculado. En este caso, la lista consistirá en valores id que hacen referencia a otros documentos. Estamos estableciendo nuestras propias relaciones entre documentos.
Para cada estudiante de la escuela, pueden tener un documento NoSQL que se parece a lo siguiente:
1 2 3 4 5 6 7 8 9 10 |
{ "id": "estudiante-1", "tipo": "estudiante", "nombre": "Nic", "apellido": "Raboy", "cursos": [ "curso-1", "curso-25" ] } |
Observe que el documento anterior es similar a cómo modelamos nuestros cursos. Cada estudiante mantendrá una lista de todos los cursos en los que está inscrito. Estos cursos son ids que hacen referencia al documento del curso apropiado.
Hay cientos de formas diferentes de modelar nuestros documentos, éste es sólo un ejemplo. Para obtener más información sobre el modelado de datos, echa un vistazo a este documentación.
Ahora que tenemos un modelo para nuestros documentos, podemos centrarnos en construir una API con cada tecnología.
Desarrollo de una API con el lenguaje de consulta de MongoDB
Aunque puede que tengas tu propio código de aplicación MongoDB, vamos a crear uno desde cero para que la migración sea muy fácil de entender.
Empecemos por crear un nuevo proyecto desde nuestro Símbolo del sistema o Terminal:
1 2 |
npm init --y npm instale express cuerpo-analizador mongodb --guardar |
Los comandos anteriores crearán un nuevo paquete.json en su directorio de trabajo actual e instale las dependencias necesarias del framework y la base de datos.
Al final del día, queremos que nuestro proyecto MongoDB tenga la siguiente estructura:
1 2 3 4 5 6 7 8 9 |
app.js rutas/ cursos.js estudiantes.js modelos/ estudiante.js curso.js paquete.json node_modules/ |
Toda la interacción con la base de datos se realizará desde cada uno de los modelos y todo el enrutamiento de la API y la interacción con el cliente se hará desde el rutas archivos. El arranque de la aplicación y la conexión a la base de datos se realizará desde el archivo app.js archivo.
Creación de un modelo de base de datos MongoDB dentro de la aplicación
Echemos un vistazo a uno de nuestros modelos de base de datos. Abra el proyecto modelos/curso.js e incluya el siguiente código:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
var Base de datos = requiere("../../app").base de datos; var ObjectId = requiere("mongodb").ObjectId; función Modelo de curso() { }; Modelo de curso.guardar = función(datos, devolución de llamada) { Base de datos.colección("cursos").insertarUno(datos, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); } Modelo de curso.actualizarEstudiantes = función(id, estudiantes, devolución de llamada) { Base de datos.colección("cursos").updateUno({ "_id": nuevo ObjectId(id) }, { $configure: { "estudiantes": estudiantes } }, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); } ); } Modelo de curso.getById = función(documentId, devolución de llamada) { var cursor = Base de datos.colección("cursos").agregado([ { "$match": { "_id": nuevo ObjectId(documentId) } }, { "$unwind": { "camino": "$students", "preserveNullAndEmptyArrays": verdadero } }, { "$lookup": { "de": "estudiantes", "localField": "estudiantes", "campoextranjero": "_id", "como": "estudianteObjetos" } }, { "$unwind": { "camino": "$studentObjects", "preserveNullAndEmptyArrays": verdadero} }, { "$group": { "_id": { "_id": "$_id", "nombre": "$name" }, "estudiantes": { "$push": "$studentObjects" } }}, { "$project": { "_id": "$_id._id", "nombre": "$_id.name", "estudiantes": "$students" } }, { "$limit": 1 } ]); cursor.toArray(devolución de llamada); }; Modelo de curso.getAll = función(devolución de llamada) { var cursor = Base de datos.colección("cursos").agregado([ { "$unwind": { "camino": "$students", "preserveNullAndEmptyArrays": verdadero} }, { "$lookup": { "de": "estudiantes", "localField": "estudiantes", "campoextranjero": "_id", "como": "estudianteObjetos" } }, { "$unwind": { "camino": "$studentObjects", "preserveNullAndEmptyArrays": verdadero} }, { "$group": { "_id": { "_id": "$_id", "nombre": "$name" }, "estudiantes": { "$push": "$studentObjects" } }}, { "$project": { "_id": "$_id._id", "nombre": "$_id.name", "estudiantes": "$students" } } ]); cursor.toArray(devolución de llamada); }; módulo.exportaciones = Modelo de curso; |
En el modelo de base de datos anterior ocurren muchas cosas. Tenemos que desglosarlo para que sea fácil de entender. Cuanto más entendamos, más fácil será el proceso de migración a Couchbase.
Cuando deseamos guardar un documento, tenemos la opción guardar
método:
1 2 3 4 5 6 7 8 |
Modelo de curso.guardar = función(datos, devolución de llamada) { Base de datos.colección("cursos").insertarUno(datos, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); } |
Utilizando la base de datos abierta podemos insertar un único documento en la colección deseada utilizando la función insertarUno
función. En esta función pasamos datos
que puede ser un objeto JavaScript de cualquier complejidad que deseemos guardar. Una vez guardado, el resultado será devuelto al método padre que lo llamó.
¿Y si queremos actualizar un documento que ya existe? En concreto, ¿qué ocurre si queremos añadir un alumno a un curso ya existente?
En este ejemplo concreto, podemos actualizar todo el archivo estudiantes
que existe en el documento:
1 2 3 4 5 6 7 8 9 10 11 12 |
Modelo de curso.actualizarEstudiantes = función(id, estudiantes, devolución de llamada) { Base de datos.colección("cursos").updateUno({ "_id": nuevo ObjectId(id) }, { $configure: { "estudiantes": estudiantes } }, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); } ); } |
El código anterior utilizará el updateUno
para buscar un documento concreto por su id y sustituir el método estudiantes
con una nueva que proporcionamos mediante la función actualizarEstudiantes
función.
Nada demasiado difícil hasta ahora y ningún estrés real añadido al desarrollador.
Aquí es donde las cosas cambian. Cuando guardamos datos estamos guardando un array de valores id. No son datos que queramos ver en operaciones de consulta. En su lugar, queremos rellenar o expandir estos valores de id en sus equivalentes de documento:
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 |
Modelo de curso.getAll = función(devolución de llamada) { var cursor = Base de datos.colección("cursos").agregado([ { "$unwind": { "camino": "$students", "preserveNullAndEmptyArrays": verdadero} }, { "$lookup": { "de": "estudiantes", "localField": "estudiantes", "campoextranjero": "_id", "como": "estudianteObjetos" } }, { "$unwind": { "camino": "$studentObjects", "preserveNullAndEmptyArrays": verdadero} }, { "$group": { "_id": { "_id": "$_id", "nombre": "$name" }, "estudiantes": { "$push": "$studentObjects" } }}, { "$project": { "_id": "$_id._id", "nombre": "$_id.name", "estudiantes": "$students" } } ]); cursor.toArray(devolución de llamada); }; |
Para que esto sea posible, primero hay que aplanar la matriz mediante una función $unwind
y unidos a través de un 1TP4Búsqueda
operación. La cosa no acaba ahí, porque queremos que nuestros resultados tengan el mismo formato, sustituyendo únicamente los identificadores de los objetos. Debido a esto tenemos que hacer más aplanamiento, agrupación y manipulaciones.
Una explicación completa de la unión de datos en MongoDB frente a la unión de datos en Couchbase se puede ver en un minucioso artículo que escribí anteriormente sobre el tema.
En resumen, cuanto más complicados sean los datos, más complicada será la consulta de agregación. Para un modelo de datos flexible, esto se convierte en poco flexible para un desarrollador de aplicaciones.
Echemos un vistazo rápido a nuestro otro modelo, el que gestionará los datos de los alumnos. Abra el proyecto modelos/estudiante.js e incluya el siguiente código JavaScript:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
var Base de datos = requiere("../../app").base de datos; var ObjectId = requiere("mongodb").ObjectId; función ModeloEstudiante() { }; ModeloEstudiante.guardar = función(datos, devolución de llamada) { Base de datos.colección("estudiantes").insertarUno(datos, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); } ModeloEstudiante.actualizarCursos = función(id, cursos, devolución de llamada) { Base de datos.colección("estudiantes").updateUno({ "_id": nuevo ObjectId(id) }, { $configure: { "cursos": cursos } }, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); } ); } ModeloEstudiante.getById = función(documentId, devolución de llamada) { var cursor = Base de datos.colección("estudiantes").agregado([ { "$match": { "_id": nuevo ObjectId(documentId) } }, { "$unwind": { "camino": "$courses", "preserveNullAndEmptyArrays": verdadero } }, { "$lookup": { "de": "cursos", "localField": "cursos", "campoextranjero": "_id", "como": "courseObjects" } }, { "$unwind": { "camino": "$courseObjects", "preserveNullAndEmptyArrays": verdadero } }, { "$group": { "_id": { "_id": "$_id", "nombre": "$name" }, "cursos": { "$push": "$courseObjects" } }}, { "$project": { "_id": "$_id._id", "nombre": "$_id.name", "cursos": "$courses" } }, { "$limit": 1 } ]); cursor.toArray(devolución de llamada); }; ModeloEstudiante.getAll = función(devolución de llamada) { var cursor = Base de datos.colección("estudiantes").agregado([ { "$unwind": { "camino": "$courses", "preserveNullAndEmptyArrays": verdadero } }, { "$lookup": { "de": "cursos", "localField": "cursos", "campoextranjero": "_id", "como": "courseObjects" } }, { "$unwind": { "camino": "$courseObjects", "preserveNullAndEmptyArrays": verdadero } }, { "$group": { "_id": { "_id": "$_id", "nombre": "$firstname", "apellido": "$lastname", "dirección": "1TP4Dirección" }, "cursos": { "$push": "$courseObjects" } }}, { "$project": { "_id": "$_id._id", "nombre": "$_id.firstname", "apellido": "$_id.lastname", "dirección": "$_id.dirección", "cursos": "$courses" } } ]); cursor.toArray(devolución de llamada); }; módulo.exportaciones = ModeloEstudiante; |
Prácticamente las mismas reglas se aplican al modelo anterior frente al modelo que vimos representando los datos del curso. Esto se debe a que los dos modelos de documentos eran muy similares para empezar. Plano en su mayor parte con un array de valores id.
Esto nos lleva a los puntos finales de la API que hacen uso de estos métodos de base de datos.
Creación de las rutas API RESTful para la aplicación
Esta es la parte fácil, y en realidad la más coherente entre las dos tecnologías de bases de datos porque no depende de la base de datos.
Echemos un vistazo al proyecto de rutas/cursos.js file:
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 |
var Modelo de curso = requiere("../modelos/curso"); var enrutador = función(aplicación) { aplicación.consiga("/cursos", función(solicitar, respuesta) { Modelo de curso.getAll(función(error, resultado) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(resultado); }); }); aplicación.consiga("/curso/:id", función(solicitar, respuesta) { Modelo de curso.getById(solicitar.parámetros.id, función(error, resultado) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(resultado[0]); }); }); aplicación.Correo electrónico:("/cursos", función(solicitar, respuesta) { si(!solicitar.cuerpo.nombre) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "Se requiere un `nombre`"}); } Modelo de curso.guardar(solicitar.cuerpo, función(error, curso) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(curso); }); }); } módulo.exportaciones = enrutador; |
En el código anterior tenemos tres rutas API. Desde un cliente externo podremos listar todos los cursos, encontrar un curso en particular o guardar un nuevo curso.
Dependiendo del punto final que se alcance, se ejecutará la función apropiada del modelo de base de datos. No se hace mucho trabajo pesado en las rutas, que basándose en su nombre, sólo sirven para enrutar.
El otro archivo de enrutamiento va a ser un poco diferente. Abra el archivo rutas/estudiantes.js e incluya el siguiente código JavaScript:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
var ModeloEstudiante = requiere("../modelos/estudiante"); var Modelo de curso = requiere("../modelos/curso"); var enrutador = función(aplicación) { aplicación.consiga("/estudiantes", función(solicitar, respuesta) { ModeloEstudiante.getAll(función(error, resultado) { si(error) { respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(resultado); }); }); aplicación.consiga("/estudiante/:id", función(solicitar, respuesta) { ModeloEstudiante.getById(solicitar.parámetros.id, función(error, resultado) { si(error) { respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(resultado); }); }); aplicación.Correo electrónico:("/estudiantes", función(solicitar, respuesta) { si(!solicitar.cuerpo.nombre) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "Se requiere un `nombre`"}); } si no si(!solicitar.cuerpo.apellido) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "Se requiere un `apellido`"}); } si no si(!solicitar.cuerpo.dirección) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "Se requiere una `dirección`"}); } ModeloEstudiante.guardar(solicitar.cuerpo, función(error, estudiante) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(estudiante); }); }); aplicación.Correo electrónico:("/estudiante/curso", función(solicitar, respuesta) { si(!solicitar.cuerpo.student_id) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "Se requiere un `student_id`" }); } si no si(!solicitar.cuerpo.course_id) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "Se requiere un `course_id`" }); } Modelo de curso.getById(solicitar.cuerpo.course_id, función(error, curso) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } ModeloEstudiante.getById(solicitar.cuerpo.student_id, función(error, estudiante) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } si(curso != null && estudiante != null) { si(!estudiante[0].cursos) { estudiante[0].cursos = []; } si(!curso[0].estudiantes) { curso[0].estudiantes = []; } var cursos = []; var estudiantes = []; para(var i = 0; i < estudiante[0].cursos.longitud; i++) { cursos.pulse(estudiante[0].cursos[i]._id); } para(var i = 0; i < curso[0].estudiantes.longitud; i++) { estudiantes.pulse(curso[0].estudiantes[i]._id); } cursos.pulse(curso[0]._id); estudiantes.pulse(estudiante[0]._id); ModeloEstudiante.actualizarCursos(estudiante[0]._id, cursos, función(error, resultado) {}); Modelo de curso.actualizarEstudiantes(curso[0]._id, estudiantes, función(error, resultado) {}); respuesta.enviar(estudiante[0]); } si no { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "El `student_id` o `course_id` no era válido"}); } }); }); }); } módulo.exportaciones = enrutador; |
Al igual que en la otra ruta, ocurren muchas cosas, pero la mayoría son casi idénticas. Donde hay diferencias es en el /estudiante/curso
responsable de añadir cursos a un estudiante y estudiantes a un curso.
Cuando se accede a este punto final, primero obtenemos la información del estudiante y del curso basándonos en el valor de id pasado. Ambos deben existir, de lo contrario se producirá un error. Si ambos existen, introduciremos el id del estudiante en el campo estudiantes
para el documento del curso y el id del curso en el array cursos
del documento del alumno. Luego llamaremos a nuestro método de actualización y devolveremos un resultado al usuario final.
Reunir todo y poner en marcha la aplicación
La API está lista en este punto. Sólo tenemos que arrancar la aplicación Node.js y conectarnos a la base de datos. Esta es la parte fácil.
Abra el archivo app.js e incluya el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var MongoClient = requiere("mongodb").MongoClient; var Express = requiere("express"); var BodyParser = requiere("body-parser"); var aplicación = Express(); aplicación.utilice(BodyParser.json()); MongoClient.conecte("mongodb://localhost:27017/ejemplo", función(error, base de datos) { si(error) { devolver consola.registro("No se pudo establecer una conexión con MongoDB"); } módulo.exportaciones.base de datos = base de datos; var studentRoutes = requiere("./mongodb/rutas/estudiantes")(aplicación); var rutas del curso = requiere("./mongodb/rutas/cursos")(aplicación); var servidor = aplicación.escuche(3000, función() { consola.registro("Conectado en el puerto 3000..."); }); }); |
En el código anterior estamos importando cada una de las dependencias que hemos descargado e inicializando Express Framework. Antes de empezar a servir la API, necesitamos establecer una conexión con MongoDB. Una vez establecida la conexión, las rutas se conectan y la aplicación comienza a servir.
En este punto se puede acceder a la API RESTful en http://localhost:3000 y probarla con herramientas populares como Cartero o Fiddler.
Desarrollo de una API con Couchbase y N1QL
Así que tenemos un ejemplo con el que trabajar cuando se trata de MongoDB y Node.js. Ese ejemplo utiliza el lenguaje de consulta MongoDB cuando se comunica con la base de datos.
Ahora vamos a tomar esa aplicación y moverla a Couchbase. Hacemos esto porque Couchbase no sólo escala mejor y tiene mejor rendimiento, el lenguaje de consulta es mucho más simple y fácil de mantener dentro de una aplicación.
Al igual que con la aplicación MongoDB, vamos a empezar desde cero, aunque gran parte de lo que veamos será idéntico. Desde el Símbolo del sistema o Terminal, ejecuta lo siguiente:
1 2 |
npm init --y npm install express body-parser couchbase --save |
Los comandos anteriores deberían resultarte familiares. Estamos creando un paquete.json e instalando nuestras dependencias, pero en lugar de MongoDB estamos usando Couchbase.
El proyecto tendrá la misma estructura que el proyecto anterior. Debería tener este aspecto:
1 2 3 4 5 6 7 8 9 |
app.js rutas/ cursos.js estudiantes.js modelos/ curso.js estudiante.js paquete.json node_modules/ |
La misma lógica vista anteriormente terminará en cada uno de estos archivos. La diferencia es la sintaxis para Couchbase.
Creación de un modelo de base de datos Couchbase dentro de la aplicación
Empezando con el mismo orden, vamos a crear las funciones de nuestro modelo de base de datos. Como se mencionó anteriormente, vamos a utilizar N1QL que es un extremo destacado de Couchbase porque te permite escribir consultas SQL. Estas consultas se ejecutan en la base de datos y no dentro de tu aplicación Node.js.
Abra el archivo modelos/curso.js e incluya el siguiente código:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
var Uuid = requiere("uuid"); var Cubo = requiere("../../app").cubo; var N1qlQuery = requiere("couchbase").N1qlQuery; función Modelo de curso() { }; Modelo de curso.guardar = función(datos, devolución de llamada) { datos.id = Uuid.v4(); datos.tipo = "curso"; datos.estudiantes = []; var declaración = "INSERTAR EN `" + Cubo.Nombre + "` (CLAVE, VALOR) VALORES ($1, $2) DEVOLVIENDO `" + Cubo.Nombre + "`.*"; var consulta = N1qlQuery.fromString(declaración); Cubo.consulta(consulta, [datos.id, datos], función(error, resultado) { si(error) { devolución de llamada(error, null); devolver; } devolución de llamada(null, resultado); }); } Modelo de curso.actualizarEstudiantes = función(id, cursos, devolución de llamada) { var declaración = "ACTUALIZAR `" + Cubo.Nombre + "` USE KEYS $1 SET students = $2 RETURNING `" + Cubo.Nombre + "`.*"; var consulta = N1qlQuery.fromString(declaración); Cubo.consulta(consulta, [id, cursos], función(error, resultado) { si(error) { devolución de llamada(error, null); devolver; } devolución de llamada(null, resultado); }); } Modelo de curso.getById = función(documentId, devolución de llamada) { var declaración = "SELECT s.id, s.type, s.name," + "SELECT t.* FROM `" + Cubo.Nombre + "` AS t USE KEYS s.students) AS estudiantes " + "FROM `" + Cubo.Nombre + "AS s" + "WHERE s.type = 'course' AND s.id = $1"; var consulta = N1qlQuery.fromString(declaración); Cubo.consulta(consulta, [documentId], función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); }; Modelo de curso.getAll = función(devolución de llamada) { var declaración = "SELECT s.id, s.type, s.name," + "SELECT t.* FROM `" + Cubo.Nombre + "` AS t USE KEYS s.students) AS estudiantes " + "FROM `" + Cubo.Nombre + "AS s" + "WHERE s.type = 'course'"; var consulta = N1qlQuery.fromString(declaración).coherencia(N1qlQuery.Coherencia.SOLICITUD_PLUS); Cubo.consulta(consulta, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); }; módulo.exportaciones = Modelo de curso; |
En el código anterior tenemos el mismo conjunto de funciones relacionadas con la base de datos que vimos en el ejemplo de MongoDB. Dentro de cada una de estas funciones hay consultas N1QL. No solo consultas N1QL, sino consultas N1QL parametrizadas para ayudar a combatir los ataques de inyección SQL.
Eche un vistazo al getAll
que antes era supercomplicado en la versión de MongoDB:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Modelo de curso.getAll = función(devolución de llamada) { var declaración = "SELECT s.id, s.type, s.name," + "SELECT t.* FROM `" + Cubo.Nombre + "` AS t USE KEYS s.students) AS estudiantes " + "FROM `" + Cubo.Nombre + "AS s" + "WHERE s.type = 'course'"; var consulta = N1qlQuery.fromString(declaración).coherencia(N1qlQuery.Coherencia.SOLICITUD_PLUS); Cubo.consulta(consulta, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); }; |
En esta ocasión tenemos una consulta sencilla que incluye una subconsulta. En caso de que nuestras necesidades de datos se vuelvan más complejas, la consulta puede modificarse sin que aumente significativamente su tamaño o complejidad.
Veamos nuestro otro modelo de base de datos. Abra el proyecto modelos/estudiante.js e incluya el siguiente código JavaScript:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
var Uuid = requiere("uuid"); var Cubo = requiere("../../app").cubo; var N1qlQuery = requiere("couchbase").N1qlQuery; función ModeloEstudiante() { }; ModeloEstudiante.guardar = función(datos, devolución de llamada) { datos.id = Uuid.v4(); datos.tipo = "estudiante"; datos.cursos = []; var declaración = "INSERTAR EN `" + Cubo.Nombre + "` (CLAVE, VALOR) VALORES ($1, $2) DEVOLVIENDO `" + Cubo.Nombre + "`.*"; var consulta = N1qlQuery.fromString(declaración); Cubo.consulta(consulta, [datos.id, datos], función(error, resultado) { si(error) { devolución de llamada(error, null); devolver; } devolución de llamada(null, resultado); }); } ModeloEstudiante.actualizarCursos = función(id, cursos, devolución de llamada) { var declaración = "ACTUALIZAR `" + Cubo.Nombre + "` USE KEYS $1 SET courses = $2 RETURNING `" + Cubo.Nombre + "`.*"; var consulta = N1qlQuery.fromString(declaración); Cubo.consulta(consulta, [id, cursos], función(error, resultado) { si(error) { devolución de llamada(error, null); devolver; } devolución de llamada(null, resultado); }); } ModeloEstudiante.getById = función(documentId, devolución de llamada) { var declaración = "SELECT t.id, t.type, t.firstname, t.lastname, t.address, " + "SELECT s.* FROM `" + Cubo.Nombre + "` AS s USE KEYS t.courses) AS courses " + "FROM `" + Cubo.Nombre + "AS t" + "WHERE t.type = 'student' AND t.id = $1"; var consulta = N1qlQuery.fromString(declaración); Cubo.consulta(consulta, [documentId], función(error, resultado) { si(error) { consola.registro(error); devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); }; ModeloEstudiante.getAll = función(devolución de llamada) { var declaración = "SELECT t.id, t.type, t.firstname, t.lastname, t.address, " + "SELECT s.* FROM `" + Cubo.Nombre + "` AS s USE KEYS t.courses) AS courses " + "FROM `" + Cubo.Nombre + "AS t" + "WHERE t.type = 'student'"; var consulta = N1qlQuery.fromString(declaración).coherencia(N1qlQuery.Coherencia.SOLICITUD_PLUS); Cubo.consulta(consulta, función(error, resultado) { si(error) { devolver devolución de llamada(error, null); } devolución de llamada(null, resultado); }); }; módulo.exportaciones = ModeloEstudiante; |
¿Le resulta familiar? Los dos modelos de bases de datos son similares porque los dos modelos de documentos son similares. De nuevo, si la complejidad cambia, la capa de aplicación seguirá siendo fácil de gestionar con consultas N1QL.
Como las rutas API no tienen ninguna dependencia de la base de datos, el código entre la aplicación MongoDB y la aplicación Couchbase puede ser compartido. Para ser claros, me refiero a los archivos que se encuentran en el directorio rutas directorio.
Arranque de la aplicación y conexión a Couchbase
Con los endpoints en su lugar y los modelos de base de datos comunicándose a Couchbase a través de N1QL, podemos terminar la aplicación.
Abra el archivo app.js e incluya el siguiente código JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var Couchbase = requiere("couchbase"); var Express = requiere("express"); var BodyParser = requiere("body-parser"); var aplicación = Express(); aplicación.utilice(BodyParser.json()); módulo.exportaciones.cubo = (nuevo Couchbase.Grupo("couchbase://localhost")).openBucket("ejemplo"); var studentRoutes = requiere("./couchbase/rutas/estudiantes")(aplicación); var rutas del curso = requiere("./couchbase/rutas/cursos")(aplicación); var servidor = aplicación.escuche(3000, función() { consola.registro("Conectado en el puerto 3000..."); }); |
En el código anterior estamos importando las dependencias que habíamos instalado e inicializando Express Framework. Luego estamos estableciendo una conexión a la base de datos, juntando nuestras rutas e iniciando el servidor Node.js.
Como estamos usando N1QL, no olvides crear al menos un índice en tu Couchbase Bucket. Puede ser tan simple como lo siguiente:
1 |
CREATE PRIMARY INDEX ON `ejemplo`; |
En este punto deberías poder acceder a tu aplicación de la misma forma que a MongoDB. Visita http://localhost:3000 desde tu navegador web o con una herramienta como Postman o Fiddler.
Conclusión
Acabas de ver cómo tomar una aplicación Node.js que utiliza MongoDB y el Lenguaje de Consulta MongoDB y convertirla a Couchbase con N1QL en su lugar. Esta es una alternativa al Mongoose a Ottoman ODM sobre la que escribí anteriormente.
Entonces, ¿por qué querrías cambiar de MongoDB a Couchbase? Bueno, Couchbase es mucho más rápido y fácil de escalar, pero N1QL también es increíblemente sencillo cuando se trabaja con datos complejos. Podrás reducir tu código drásticamente y hacerlo más mantenible. Echa un vistazo a la consulta Couchbase vs MongoDB tutorial que escribí en términos de unión de datos entre las dos bases de datos.
Para más información sobre cómo usar Couchbase en una aplicación Node.js:
-
- Echa un vistazo a la Portal para desarrolladores de Couchbase.
- Pruebe uno de los IDEs para desarrolladores de Couchbase-JetBrains, VSCode-para los que tenemos plugins.
[...] sobre cómo convertir tus aplicaciones Node.js con MongoDB a Couchbase. Estos incluyen un tutorial de MongoDB Query Language a N1QL así como un tutorial de Mongoose a Ottoman. Estos fueron grandes tutoriales de migración de un [...]