Cuando se habla con desarrolladores de Node.js, es habitual oír hablar de NoSQL como la base de datos elegida para el desarrollo. JavaScript y JSON vienen de la mano porque después de todo JSON significa JavaScript Object Notation. Este es un formato más común en las bases de datos orientadas a documentos que los desarrolladores de Node.js tienden a utilizar.
Una pila muy popular de tecnologías de desarrollo es la pila de MongoDB, Express Framework, Angular y Node.js (MEAN), pero también está la pila de Couchbase, Express Framework, Angular y Node.js (CEAN). Ahora no me malinterpretes, cada tecnología que he enumerado es genial, pero cuando tus aplicaciones necesitan escalar y mantener su rendimiento, es posible que tengas mejor suerte con Couchbase debido a su funcionamiento por diseño.
¿Y si ya estás utilizando MongoDB en tu aplicación Node.js?
Lo más probable es que utilices Mangosta que es un Object Document Model (ODM) para interactuar con la base de datos. Couchbase también tiene un ODM y se llama Otomano. Lo bueno de estas dos tecnologías ODM es que comparten prácticamente el mismo conjunto de API, lo que facilita enormemente cualquier transición.
Vamos a ver cómo aprovechar la API REST de MongoDB para hacer la transición de una aplicación Node.js usando Mongoose y migrarla a Couchbase, presentando Ottoman.
Requisitos
Este tutorial va a ser un poco diferente debido a todas las tecnologías involucradas. Vamos a construir todo desde cero para simplificar, por lo que los siguientes son los requisitos y recomendaciones:
- Servidor Couchbase 4.5+
- MongoDB 3.4+
- Node.js 6.0+
Vamos a empezar construyendo una API REST de MongoDB en Node.js usando Mongoose, de ahí el requisito de Node.js y MongoDB. Luego vamos a tomar esta aplicación y migrarla a Couchbase.
En este ejemplo, no veremos cómo configurar Node.js, MongoDB o Couchbase Server.
Comprender nuestro modelo de datos NoSQL
Tanto MongoDB como Couchbase son bases de datos documentales. Una almacena datos BSON y la otra almacena JSON, sin embargo desde la perspectiva del desarrollador son increíblemente similares. Dicho esto, vamos a diseñar algunos modelos basados en los estudiantes que asisten a cursos en una escuela. El primer modelo que creamos podría ser para cursos reales, donde un solo curso podría parecerse 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" ] } |
En la imagen anterior, observe que el curso tiene un identificador único y que lo hemos definido como un curso. El curso tiene información sobre el nombre, así como una lista de los estudiantes que están inscritos.
Supongamos ahora que queremos definir nuestro modelo para los documentos de los estudiantes:
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 modelo anterior tiene un formato similar al de los cursos. Lo que estamos diciendo aquí es que ambos documentos están relacionados, pero siguen siendo semiestructurados. Estamos diciendo que cada curso lleva la cuenta de sus alumnos y que cada alumno lleva la cuenta de sus cursos. Esto es útil cuando intentamos consultar los datos.
Hay posibilidades ilimitadas cuando se trata de modelar tus datos NoSQL. De hecho, hay probablemente más de cien maneras de definir un modelo de "cursos y estudiantes", frente a lo que yo había decidido. Depende totalmente de ti, y esa es la flexibilidad que aporta NoSQL. Más información sobre el modelado de datos se puede encontrar aquí.
Con un modelo de datos en mente, podemos crear un conjunto sencillo de puntos finales de API utilizando cada uno MongoDB con Mongoose y Couchbase con Ottoman.
Desarrollo de una API con Express Framework y MongoDB
Dado que, en teoría, estamos migrando de MongoDB a Couchbase, tendría sentido averiguar primero qué queremos en una aplicación MongoDB.
Cree un nuevo directorio en algún lugar de su ordenador para representar la primera parte de nuestro proyecto. Dentro de este directorio, ejecute lo siguiente:
1 2 |
npm init --y npm instale express cuerpo-analizador mongodb mangosta --guardar |
Los comandos anteriores crearán un archivo llamado paquete.json que hará un seguimiento de cada una de las cuatro dependencias del proyecto. El sitio express
es para el framework Express y la dependencia body-parser
permite que existan cuerpos de solicitud en peticiones POST, PUT y DELETE, todas ellas comunes para alterar datos. A continuación, mongodb
y mangosta
son necesarios para trabajar con la base de datos.
El proyecto que construyamos tendrá la siguiente estructura:
1 2 3 4 5 6 7 8 |
app.js rutas/ cursos.js estudiantes.js modelos/ modelos.js paquete.json node_modules/ |
Siga adelante y cree esos directorios y archivos si aún no existen. La página app.js será el controlador de la aplicación, mientras que el archivo rutas contendrá nuestros puntos finales de la API y el modelos contendrá las definiciones de la base de datos para nuestra aplicación.
Definición de los esquemas de Mongoose
Así que vamos a trabajar hacia atrás, comenzando con el modelo Mongoose que se comunicará con MongoDB. Abre el proyecto modelos/models.js 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 |
var Mangosta = requiere("mangosta"); var Esquema = Mangosta.Esquema; var ObjectId = Mangosta.Tipos de esquema.ObjectId; var Esquema del curso = nuevo Mangosta.Esquema({ nombre: Cadena, plazo: Cadena, estudiantes: [ { tipo: ObjectId, ref: StudentSchema } ] }); var StudentSchema = nuevo Mangosta.Esquema({ nombre: Cadena, apellido: Cadena, cursos: [ { tipo: ObjectId, ref: Esquema del curso } ] }); módulo.exportaciones.Modelo de curso = Mangosta.modelo("Curso", Esquema del curso); módulo.exportaciones.ModeloEstudiante = Mangosta.modelo("Estudiante", StudentSchema); |
En lo anterior estamos creando esquemas de documentos MongoDB y luego creando modelos a partir de ellos. Fíjate en lo similares que son los esquemas a los modelos JSON que habíamos definido previamente fuera de la aplicación. No estamos declarando un id
y tipo
porque el ODM se encarga de ello por nosotros. En cada una de las matrices usamos una referencia a otro esquema. Lo que veremos al guardar es un id de documento, pero podemos aprovechar las tecnologías de consulta para cargar ese id en datos reales.
¿Y cómo utilizamos esos modelos?
Creación de rutas API REST
Ahora queremos crear la información de enrutamiento, o en otras palabras, los puntos finales de la API. Por ejemplo, vamos a crear todos los puntos finales CRUD para la información del curso. En el proyecto rutas/cursos.js añada 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 32 33 34 35 |
var Modelo de curso = requiere("../modelos/modelos").Modelo de curso; var enrutador = función(aplicación) { aplicación.consiga("/cursos", función(solicitar, respuesta) { Modelo de curso.encontrar({}).rellenar("estudiantes").entonces(función(resultado) { respuesta.enviar(resultado); }, función(error) { respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); }); }); aplicación.consiga("/curso/:id", función(solicitar, respuesta) { Modelo de curso.findOne({"_id": solicitar.parámetros.id}).rellenar("estudiantes").entonces(función(resultado) { respuesta.enviar(resultado); }, función(error) { respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); }); }); aplicación.Correo electrónico:("/cursos", función(solicitar, respuesta) { var curso = nuevo Modelo de curso({ "nombre": solicitar.cuerpo.nombre }); curso.guardar(función(error, curso) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(curso); }); }); } módulo.exportaciones = enrutador; |
En el ejemplo anterior tenemos tres puntos finales. Podemos ver todos los cursos disponibles, ver cursos por id, y crear nuevos cursos. Cada punto final es alimentado por Mongoose.
1 2 3 4 5 6 7 8 9 10 11 |
aplicación.Correo electrónico:("/cursos", función(solicitar, respuesta) { var curso = nuevo Modelo de curso({ "nombre": solicitar.cuerpo.nombre }); curso.guardar(función(error, curso) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(curso); }); }); |
Al crear un documento, los datos POST de la solicitud se añaden a una nueva instanciación del modelo. Una vez que guardar
se guarda en MongoDB. Algo similar ocurre cuando se leen datos de la base de datos.
1 2 3 4 5 6 7 |
aplicación.consiga("/cursos", función(solicitar, respuesta) { Modelo de curso.encontrar({}).rellenar("estudiantes").entonces(función(resultado) { respuesta.enviar(resultado); }, función(error) { respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); }); }); |
En este caso, el encontrar
y se introducen los parámetros. Si no hay parámetros, se devuelven todos los documentos de la función Curso
si no, los datos se consultan por las propiedades pasadas. La dirección rellenar
permite cargar las referencias de los documentos, de modo que en lugar de devolver valores de id, se devuelven los documentos reales.
Ahora echemos un vistazo a la otra ruta.
La segunda ruta se encarga de crear los datos de los alumnos, pero aquí hay una excepción. Aquí también vamos a gestionar las relaciones entre documentos. Abra el proyecto rutas/estudiantes.js e incluya el siguiente código fuente:
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 Modelo de curso = requiere("../modelos/modelos").Modelo de curso; var ModeloEstudiante = requiere("../modelos/modelos").ModeloEstudiante; var enrutador = función(aplicación) { aplicación.consiga("/estudiantes", función(solicitar, respuesta) { ModeloEstudiante.encontrar({}).rellenar("cursos").entonces(función(resultado) { respuesta.enviar(resultado); }, función(error) { respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); }); }); aplicación.consiga("/estudiante/:id", función(solicitar, respuesta) { ModeloEstudiante.findOne({"_id": solicitar.parámetros.id}).rellenar("cursos").entonces(función(resultado) { respuesta.enviar(resultado); }, función(error) { respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); }); }); aplicación.Correo electrónico:("/estudiantes", función(solicitar, respuesta) { var estudiante = nuevo ModeloEstudiante({ "nombre": solicitar.cuerpo.nombre, "apellido": solicitar.cuerpo.apellido }); estudiante.guardar(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) { Modelo de curso.findOne({"_id": solicitar.cuerpo.course_id}).entonces(función(curso) { ModeloEstudiante.findOne({"_id": solicitar.cuerpo.student_id}).entonces(función(estudiante) { si(curso != null && estudiante != null) { si(!estudiante.cursos) { estudiante.cursos = []; } si(!curso.estudiantes) { curso.estudiantes = []; } estudiante.cursos.pulse(curso._id); curso.estudiantes.pulse(estudiante._id); estudiante.guardar(); curso.guardar(); respuesta.enviar(estudiante); } si no { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": "El `student_id` o `course_id` no era válido"}); } }, función(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); }); }, función(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); }); }); } módulo.exportaciones = enrutador; |
Los tres primeros puntos finales de la API deberían resultarle familiares. El nuevo punto final estudiante/curso
es responsable de añadir estudiantes a un curso y cursos a un estudiante.
Lo primero que ocurre es que se encuentra un curso basado en un identificador de solicitud. A continuación, se busca un estudiante a partir de otro identificador de solicitud. Si se encuentran ambos documentos, los identificadores se añaden a cada una de las matrices correspondientes y los documentos se guardan de nuevo.
El paso final aquí es crear nuestro controlador de aplicación. Este se conectará a la base de datos y servirá la aplicación para ser consumida por los clientes.
Conectar a MongoDB y servir la aplicación
Abra el archivo app.js y añada el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var Mangosta = requiere("mangosta"); var Express = requiere("express"); var BodyParser = requiere("body-parser"); var aplicación = Express(); aplicación.utilice(BodyParser.json()); Mangosta.Promesa = Promesa; var studentRoutes = requiere("./rutas/estudiantes")(aplicación); var rutas del curso = requiere("./rutas/cursos")(aplicación); Mangosta.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"); } 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 instalamos previamente. Luego estamos inicializando Express y diciéndole que acepte cuerpos JSON en las peticiones.
Las rutas que se crearon previamente necesitan estar vinculadas a Express, así que las importamos y pasamos la instancia de Express. Por último, se realiza una conexión a MongoDB con Mongoose y la aplicación comienza a servir.
No es especialmente difícil, ¿verdad?
Desarrollo de una API con Express Framework y Couchbase
Hemos visto cómo crear una API con Node.js, Mongoose y MongoDB, así que ahora tenemos que hacer lo mismo con Node.js, Ottoman y Couchbase. De nuevo, esto es para mostrar lo fácil que es pasar de MongoDB a Couchbase y obtener todos los beneficios de una potente base de datos NoSQL preparada para empresas.
Crea un nuevo directorio en algún lugar de tu ordenador y dentro de él, ejecuta lo siguiente para crear un nuevo proyecto:
1 2 |
npm init --y npm instale express cuerpo-analizador couchbase otomana --guardar |
Los comandos anteriores son similares a los que vimos anteriormente, con la excepción de que ahora estamos usando Couchbase y Ottoman. El proyecto que construyamos tendrá exactamente la misma estructura, y como refresco, se parece a lo siguiente:
1 2 3 4 5 6 7 8 |
app.js rutas/ cursos.js estudiantes.js modelos/ modelos.js paquete.json node_modules/ |
Todos los modelos otomanos existirán en el modelos todos los puntos finales de la API y la lógica Ottoman existirán en el directorio rutas y toda la lógica del controlador existirá en el directorio app.js archivo.
Definición de los modelos otomanos
Vamos a trabajar en la misma dirección que hicimos para la aplicación MongoDB para mostrar la facilidad de la transición. Esto significa empezar con los modelos Ottoman que representarán nuestros datos en Couchbase Server.
Abra el archivo modelos/models.js 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 |
var Otomano = requiere("otomana"); var Modelo de curso = Otomano.modelo("Curso", { nombre: { tipo: "cadena" }, plazo: { tipo: "cadena" }, estudiantes: [ { ref: "Estudiante" } ] }); var ModeloEstudiante = Otomano.modelo("Estudiante", { nombre: { tipo: "cadena" }, apellido: { tipo: "cadena" }, cursos: [ { ref: "Curso" } ] }); módulo.exportaciones.ModeloEstudiante = ModeloEstudiante; módulo.exportaciones.Modelo de curso = Modelo de curso; |
Lo anterior debería resultarte familiar, pero tienes que darte cuenta de que se trata de dos ODMs muy diferentes. En lugar de diseñar esquemas MongoDB a través de Mongoose podemos ir directamente a diseñar modelos JSON para Couchbase con Ottoman. Recuerda que no hay esquemas en Couchbase Buckets.
Cada modelo otomano tiene un conjunto de propiedades y una matriz que hace referencia a otros documentos. Aunque la sintaxis es ligeramente diferente, se consigue lo mismo.
Esto nos lleva a los puntos finales de la API que utilizan estos modelos.
Creación de los puntos finales de la API REST
El primer conjunto de endpoints que queremos crear está relacionado con la gestión de cursos. Abra el archivo rutas/cursos.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 |
var Modelo de curso = requiere("../modelos/modelos").Modelo de curso; var enrutador = función(aplicación) { aplicación.consiga("/cursos", función(solicitar, respuesta) { Modelo de curso.encontrar({}, {carga: ["estudiantes"]}, 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, {carga: ["estudiantes"]}, función(error, resultado) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(resultado); }); }); aplicación.Correo electrónico:("/cursos", función(solicitar, respuesta) { var curso = nuevo Modelo de curso({ "nombre": solicitar.cuerpo.nombre }); curso.guardar(función(error, resultado) { 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 endpoints estructurados de forma casi idéntica a lo que vimos con MongoDB y Mongoose. Sin embargo, hay algunas diferencias menores. Por ejemplo, en lugar de usar promesas estamos usando callbacks.
Una de las diferencias más visibles es cómo se realizan las consultas. No sólo tenemos acceso a un encontrar
como vimos en Mongoose, pero también tenemos acceso a una función getById
función. En ambos escenarios podemos pasar información sobre cómo esperamos que se realice una consulta. En lugar de utilizar una función rellenar
podemos utilizar carga
y proporcionar qué documentos de referencia deseamos cargar. Los conceptos entre Mongoose y Ottoman son muy parecidos.
Esto nos lleva a nuestro segundo conjunto de rutas. Abra el proyecto 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 |
var ModeloEstudiante = requiere("../modelos/modelos").ModeloEstudiante; var Modelo de curso = requiere("../modelos/modelos").Modelo de curso; var enrutador = función(aplicación) { aplicación.consiga("/estudiantes", función(solicitar, respuesta) { ModeloEstudiante.encontrar({}, {carga: ["cursos"]}, función(error, resultado) { si(error) { devolver 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, {carga: ["cursos"]}, función(error, resultado) { si(error) { devolver respuesta.estado(401).enviar({ "éxito": falso, "mensaje": error}); } respuesta.enviar(resultado); }); }); aplicación.Correo electrónico:("/estudiantes", función(solicitar, respuesta) { var estudiante = nuevo ModeloEstudiante({ "nombre": solicitar.cuerpo.nombre, "apellido": solicitar.cuerpo.apellido }); estudiante.guardar(función(error, resultado) { 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) { 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(!estudiante.cursos) { estudiante.cursos = []; } si(!curso.estudiantes) { curso.estudiantes = []; } estudiante.cursos.pulse(Modelo de curso.ref(curso._id)); curso.estudiantes.pulse(ModeloEstudiante.ref(estudiante._id)); estudiante.guardar(función(error, resultado) {}); curso.guardar(función(error, resultado) {}); respuesta.enviar(estudiante); }); }); }) } módulo.exportaciones = enrutador; |
Ya sabemos que los tres primeros endpoints van a tener el mismo formato. Queremos prestar atención al último endpoint que gestiona nuestras relaciones.
Con este endpoint estamos obteniendo un curso por su valor id y un alumno en base a su valor id. Mientras ambos devuelvan un documento, podemos añadir una referencia de cada uno a cada uno de sus arrays y volver a guardar el documento. Lo mismo y casi el mismo código se encontró en la versión de Mongoose.
Ahora podemos ver la lógica para empezar a servir la aplicación después de conectarse a la base de datos.
Conectarse a Couchbase y servir la aplicación
Abra el archivo app.js e incluya el siguiente JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var Couchbase = requiere("couchbase"); var Otomano = requiere("otomana"); var Express = requiere("express"); var BodyParser = requiere("body-parser"); var aplicación = Express(); aplicación.utilice(BodyParser.json()); var cubo = (nuevo Couchbase.Grupo("couchbase://localhost")).openBucket("ejemplo"); Otomano.tienda = nuevo Otomano.CbStoreAdapter(cubo, Couchbase); var studentRoutes = requiere("./rutas/estudiantes")(aplicación); var rutas del curso = requiere("./rutas/cursos")(aplicación); var servidor = aplicación.escuche(3000, función() { consola.registro("Conectado en el puerto 3000..."); }); |
¿Le resulta familiar? Pues debería. Sólo estamos cambiando la información de conexión de Mongoose por la de Couchbase. Después de conectarnos a la base de datos podemos empezar a servir la aplicación.
Conclusión
La migración desde Mongoose y MongoDB es más sencilla de lo que se piensa. De hecho, yacabamos de ver cómo construir una API REST con Node.js, Mongoose y MongoDB, y luego llevarla a Couchbase de una manera muy fluida. La intención de esto era demostrar que el proceso de migración no es nada de lo que asustarse, especialmente si estás usando Node.js como tu tecnología backend.
Con Couchbase tienes una base de datos NoSQL distribuida de alto rendimiento que funciona a cualquier escala. La necesidad de usar caché delante de tu base de datos se elimina porque está integrada en Couchbase. Para más información sobre el uso de Ottoman, puedes consultar una blog anterior que escribí. Más información sobre el uso de Couchbase con Node.js se puede encontrar en el Portal para desarrolladores de Couchbase.
[...] hace demasiado tiempo escribí sobre la migración 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 [...]
gracias
nic raboy
por tan buen artículo. Sólo tengo una cosa que preguntar que cuando voy a couchbase UI en el puerto 8091 y ver los documentos de mi cubo hay un campo que falta 'firstName', que es el primer campo de mi esquema, Se está guardando todos los datos con éxito, pero el primer campo 'firstName' no está presente en allí. alguna idea de por qué está sucediendo esto? todos los demás campos se muestran sólo que no éste. ¿Hay algo que pueda estar relacionado con couchbase u ottoman?