El programador políglota

Optar por NoSQL con MongoDB

Ted Neward

Descargar el ejemplo de código

Durante la última década, aproximadamente, desde el anuncio de Microsoft .NET Framework en 2000 y su primera versión en 2002, los desarrolladores de .NET han luchado por mantenerse al día con todas las cosas nuevas que Microsoft les ha ofrecido. Como si esto fuera poco, “la comunidad” (vale decir, tanto desarrolladores que usan .NET diariamente como los que no lo hacen) se ha desatado y ha creado algunas cosas más para llenar los espacios que Microsoft no cubre, o sólo para crear caos y confusión, como se quiera ver.

Una de estas cosas “nuevas” que han emergido de la comunidad desde fuera de la égida de Microsoft es el movimiento NoSQL, un grupo de desarrolladores que desafía abiertamente la idea de que todos los datos se deban almacenar o necesariamente se vayan a almacenar en un sistema de base de datos relacional de alguna forma. Tablas, pilas, columnas, claves principales, restricciones de claves externas y argumentos sobre nulos y cualquier clave principal debe ser natural o no natural… ¿no existe nada sagrado?

En este artículo y en sus sucesores, examinaré una de las principales herramientas defendidas por las personas que participan en el movimiento NoSQL: MongoDB, cuyo nombre proviene de “humongous” (“descomunal” en inglés), según el sitio web de MongoDB (no lo estoy inventando). Abordaré casi todo lo que tiene relación con MongoDB: instalación, exploración y trabajo con esta herramienta desde .NET Framework, incluida la compatibilidad con LINQ que se ofrece; su uso desde otros entornos (aplicaciones de escritorio, aplicaciones web y servicios); y cómo configurarla de manera que los administradores de producción de Windows no lo detesten.

Problema (u, otra vez, ¿qué me importa?)

Antes de profundizar demasiado en los detalles de MongoDB, es justo preguntarse por qué cualquier desarrollador de .NET Framework debería sacrificar su siguiente media hora aproximadamente en leer este artículo y seguirlo en sus portátiles. Después de todo, SQL Server viene en una edición Express gratuita y redistribuible que ofrece una opción de almacenamiento de datos más liviana que las bases de datos relacionales tradicionales limitadas a la empresa o centro de datos y, desde luego, existen muchas bibliotecas disponibles para proporcionar un acceso más fácil que incluyen LINQ y Entity Framework propios de Microsoft.

El problema es que la fortaleza del modelo relacional, su modelo relacional en sí mismo, es también su mayor debilidad. La mayoría de los desarrolladores, ya sean de .NET, Java o de algo completamente distinto, pueden describir, con la experiencia de sólo algunos años, en dolorosos detalles, cómo no todo encaja perfectamente en un modelo “cuadrado” de tablas, filas y columnas. El intento de modelar datos jerárquicos puede deschavetar completamente hasta al desarrollador más experimentado, tanto así que Joe Celko escribió un libro (“SQL for Smarties, Third Edition, Morgan-Kaufmann, 2005) completamente acerca del concepto de modelar datos jerárquicos en un modelo relacional. Si a esto se agrega el “supuesto” básico de que las bases de datos relacionales asumen una estructura inflexible respecto de los datos (el esquema de base de datos), el intento de admitir “adicionales” ad hoc para los datos se vuelve difícil (encuesta rápida: levanten la mano las personas que trabajen con bases de datos que tengan una columna “Notes” o, incluso mejor, Note1, Note2, Note3…).

Nadie al interior del movimiento NoSQL va a sugerir que el modelo relacional no tenga sus fortalezas o que la base de datos relacional vaya a desaparecer, pero un hecho básico de la vida de los desarrolladores en las últimas dos décadas es que a menudo tienen datos que no son inherentemente (o, a veces, ni remotamente) de naturaleza relacional, almacenados en bases de datos relacionales.

La base de datos orientada a documentos almacena “documentos” (colecciones cohesionadamente tejidas de datos que generalmente no están conectadas con otros elementos de datos del sistema) en lugar de “relaciones”. Por ejemplo, las entradas de blog en un sistema de blog están completamente desconectadas entre sí, y aunque una haga referencia a otra, lo más común es que la conexión sea a través de un hípervínculo destinado a que lo desreferencie el explorador del usuario, no a que se desreferencie internamente. Los comentarios en la entrada de blog se abarcan completamente hasta dicha entrada de blog, y rara vez los usuarios desean ver la agregación de todos los comentarios, independientemente de la entrada sobre la cual comentaron.

Por otra parte, las bases de datos orientadas a documentos tienden a sobresalir en entornos de alto rendimiento o alta simultaneidad. MongoDB está orientado particularmente hacia alto rendimiento, mientras que su primo cercano, CouchDB, se orienta más hacia escenarios de alta simultaneidad. Ambos renuncian a cualquier tipo de compatibilidad con transacción multiobjeto, lo que significa que, a pesar de que son compatibles con la modificación simultánea de un único objeto en una base de datos, cualquier intento de modificar más de uno a la vez deja muy poco tiempo durante el cual estas modificaciones se pueden ver “en forma incidental”. Los documentos se actualizan automáticamente, pero no existe un concepto de transacción que abarque actualizaciones de varios documentos. Esto no significa que MongoDB no tenga durabilidad; sólo significa que la instancia MongoDB no puede sobrevivir un error de alimentación de la misma forma que la instancia SQL Server. Los sistemas que exigen semántica de atomicidad, coherencia, aislamiento y durabilidad (ACID) completa funcionan mejor con sistemas de base de datos relacionales tradicionales, por lo que es poco probable que veamos datos esenciales dentro de una instancia MongoDB muy pronto, excepto, quizás, como datos replicados o en caché dentro de un servidor web.

En general, MongoDB funciona bien para aplicaciones y componentes que necesitan almacenar datos a los que se pueda tener acceso rápidamente y que se usen a menudo. Los análisis de sitios web, las preferencias de usuarios y las configuraciones (y cualquier tipo de sistema en el cual los datos no estén plenamente estructurados o necesiten ser estructuralmente flexibles) son candidatos naturales para MongoDB. Esto no significa que MongoDB no esté plenamente preparado para ser un almacén de datos principal para datos operativos; sólo significa que MongoDB funciona bien en áreas en que los RDBMS no lo hacen, así como varias áreas en las que ambos pueden funcionar bien.

Introducción

Como mencioné anteriormente, MongoDB es un paquete de software de código abierto, que se descarga fácilmente del sitio web de MongoDB, mongodb.com. Con abrir el sitio web en un explorador debe bastar para buscar los vínculos hacia el lote binario descargable de Windows; busque los vínculos de descarga a la derecha la página o, si prefiere vínculos directos, use mongodb.org/display/DOCS/Downloads. A la fecha de este artículo, la versión estable es la 1.2.4. No es más que un lote de archivos .zip, por lo que la instalación es, en términos comparativos, ridículamente fácil: sólo descomprima los contenidos donde lo desee.

En serio, eso es todo.

Del archivo .zip salen tres directorios: bin, include y lib. El único directorio de interés es bin, que contiene ocho ejecutables. No es necesaria ninguna otra dependencia binaria (o runtime) y, de hecho, sólo dos de esos ejecutables son de interés por el momento. Éstos son mongod.exe, el proceso mismo de base de datos de MongoDB, y mongo.exe, el cliente de shell de línea de comando, que generalmente se usa de la misma manera que el antiguo cliente de shell de línea de comando isql.exe SQL. Para asegurarse de que todo esté instalado y de que funcione correctamente; busque los datos directamente y realice tareas administrativas.

La verificación de que todo esté instalado correctamente es tan fácil como iniciar mongod desde un cliente de línea de comando. De manera predeterminada, MongoDB tiende a almacenar datos en la ruta de sistema de archivos predeterminada, c:\data\db, pero esto se puede configurar con un archivo de texto pasado por nombre en la línea de comando mediante --config. Suponiendo que exista un subdirectorio llamado db siempre que se inicie mongod, la verificación de que todo esté permitido es tan fácil como lo que se ve en la Figura 1.

image: Firing up Mongod.exe to Verify Successful Installation

Figura 1 Inicio de Mongod.exe para verificar la instalación correcta

Si el directorio no existe, MongoDB lo creará. Tenga en cuenta que en mi cuadro de Windows 7, cuando se inicia MongoDB, aparece el cuadro de diálogo habitual “Esta aplicación desea abrir un puerto”. Asegúrese de que se tenga acceso al puerto (27017 de manera predeterminada); de lo contrario, el establecimiento de la conexión con éste será difícil, a lo menos (abordaré más acerca de este tema en un artículo subsiguiente, cuando hable de poner MongoDB en un entorno de producción).

Cuando el servidor se esté ejecutando, la conexión con éste a través del shell será igual de fácil: la aplicación mongo.exe inicia un entorno de línea de comando que permite la interacción directa con el servidor, como se muestra en la Figura 2.

image: Mongo.exe Launches a Command-Line Environment that Allows Direct Interaction with the Server

Figura 2 Mongo.exe inicia un entorno de línea de comando que permite la interacción directa con el servidor

De manera predeterminada, el shell se conecta con la base de datos “test”. Dado que el objetivo ahora es sólo verificar que todo funcione, con test es suficiente. Desde luego, a partir de este momento es bastante fácil crear algunos datos de ejemplo para trabajar con MongoDB, como un objeto rápido que describe a una persona. Es un vistazo rápido a la manera en que MongoDB ve los datos para arrancar, como vemos en la Figura 3.

image: Creating Sample Data

Figura 3 Creación de datos de ejemplo

Esencialmente, MongoDB usa la notación de objetos JavaScript (JSON) como su notación de datos, lo que explica tanto su flexibilidad como la manera en que los clientes interactúan con ella. Internamente, MongoDB almacena cosas en BSON, un superconjunto binario de JSON, para almacenamiento e indización más fáciles. JSON, sin embargo, sigue siendo el formato de entrada y salida preferido de MongoDB y, generalmente, es el formato documentado que se usa en todo el sitio web de MongoDB y en wiki. Si no está familiarizado con JSON, es conveniente repasarlo antes de entrar de lleno a MongoDB. Mientras tanto, sólo por diversión, mire dentro del directorio en el cual mongod almacena los datos y verá que han aparecido un par de archivos con el nombre “test”.

Basta de jugar, vamos a escribir código. Salir del shell es tan fácil como escribir “exit”, y para apagar el servidor sólo se requiere Ctrl+C en la ventana o cerrarla; el servidor captura la señal de cierre y apaga todo correctamente antes de salir del proceso.

El servidor de MongoDB (y el shell, aunque no es tan problemático) está escrito como aplicación C++ nativa, ¿se acuerda de ellas?, por lo que tener acceso a éste exige algún tipo de controlador de .NET Framework que sepa cómo conectarse a través del socket abierto para entregarle comandos y datos. La distribución de MongoDB no incluye un controlador .NET Framework, pero afortunadamente, la comunidad ha proporcionado uno, y por “la comunidad” en este caso me refiero a un desarrollador llamado Sam Corder, que ha creado un controlador .NET Framework y compatibilidad con LINQ para tener acceso a MongoDB. Su trabajo está disponible en formulario de origen y binario, desde github.com/samus/mongodb-csharp. Descargue los formularios binarios de esa página (busque en la esquina superior derecha) o de origen y compílelo. De cualquier forma, el resultado son dos ensamblados: MongoDB.Driver.dll y MongoDB.Linq.dll. Un rápido Add Reference en el nodo References del proyecto, y .NET Framework está listo.

Escritura de código

Fundamentalmente, abrir una conexión para ejecutar el servidor MongoDB no es muy diferente a abrir una conexión con cualquier otra base de datos, como se muestra en la Figura 4.

Figura 4 Apertura de una conexión con el servidor MongoDB

using System;
using MongoDB.Driver; 

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port
      db.Disconnect();
    }
  }
}

Detectar el objeto creado anteriormente no es muy difícil, sólo… diferente… de lo que los desarrolladores de .NET Framework han usado antes (consulte la Figura 5).

Figura 5 Detección de un objeto de Mongo creado

using System;
using MongoDB.Driver; 

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port.
      Database test = db.getDB("test");
      IMongoCollection things = test.GetCollection("things");
      Document queryDoc = new Document();
      queryDoc.Append("lastname", "Neward");
      Document resultDoc = things.FindOne(queryDoc);
      Console.WriteLine(resultDoc);
      db.Disconnect();
    }
  }
}

Si esto parece un tanto abrumador, relájese: está escrito “de la manera larga”, dado que MongoDB almacena cosas de manera diferente de las bases de datos tradicionales.

Para empezar, recuerde que los datos insertados anteriormente tenían tres campos: firstname, lastname y age, y cualquiera de éstos son elementos por los cuales se pueden recuperar datos. Pero, lo que es más importante, la línea donde se almacenaron, escrita de manera más bien caballerosa, fue “test.things.save()”, que implica que los datos se están almacenando en algo llamado “things”. En terminología de MongoDB, “things” es una colección e, implícitamente, todos los datos se almacenan en una colección. Las colecciones, a su vez, contienen documentos, que contienen pares clave-valor en que los valores puedan ser colecciones adicionales. En este caso, “things” es una colección almacenada dentro de una base de datos que, como mencioné anteriormente, es la base de datos test.

Como resultado, capturar los datos significa conectarse primero con el servidor MongoDB; después, con la base de datos de pruebas y, por último, buscar la colección “things”. Esto es lo que hacen las primeras cuatro líneas de la Figura 5: crear un objeto de Mongo que represente la conexión, se conecte con el servidor, se conecte con la base de datos test y, a continuación, obtenga la colección “things”.

Cuando se encuentre la colección, el código puede emitir una consulta para buscar un único documento a través de la llamada FindOne. Pero, al igual que con todas las bases de datos, el cliente no desea capturar todos los documentos de la colección y, a continuación, buscar el documento en el que está interesado; de alguna forma, es necesario restringir la consulta. En MongoDB, esto se hace creando un “Document”, que contenga los campos y los datos para buscar en los campos, un concepto conocido como consulta según ejemplo, o QBE, por sus siglas en inglés. Dado que el objetivo es buscar el documento que contenga un campo lastname, cuyo valor esté configurado en “Neward”, se crea un Document que contenga un campo lastname y cuyo valor se cree y se traspase a FindOne. Si la consulta tiene éxito, arroja otro Document que contiene todos los datos de la consulta a (más otro campo); de lo contrario, arroja null.

A propósito, la versión corta de esta descripción puede ser tan breve como:

Document anotherResult = 
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward"));
       Console.WriteLine(anotherResult);

Al ejecutarse, no sólo aparecen los valores originales enviados, sino también uno nuevo, un campo _id que contiene un objeto ObjectId. Éste es el único identificador del objeto, insertado silenciosamente por la base de datos cuando se almacenaron los nuevos datos. Cualquier intento de modificar este objeto debe preservar dicho campo o, de lo contrario, la base de datos supondrá que se está enviando un nuevo objeto. Generalmente, esto se realiza modificando el Document que arrojó la consulta:

anotherResult["age"] = 39;
       things.Update(resultDoc);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

Sin embargo, siempre es posible crear una nueva instancia de Document y llenar manualmente el campo _id para que coincida con ObjectId, si eso tiene más sentido:

Document ted = new Document();
       ted["_id"] = new MongoDB.Driver.Oid("4b61494aff75000000002e77");
       ted["firstname"] = "Ted";
       ted["lastname"] = "Neward";
       ted["age"] = 40;
       things.Update(ted);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

Desde luego, si ya se conoce _id, también se puede usar como el criterio de consulta.

Tenga en cuenta que Document, de hecho, no está escrito: se puede almacenar casi cualquier cosa en cualquier campo con cualquier nombre, incluidos algunos tipos de valor de .NET Framework fundamentales, tales como DateTime. Técnicamente, según mencioné, MongoDB almacena datos de BSON, que incluyen algunas extensiones a tipos de JSON tradicionales (cadena, entero, booleano, doble y nulo, aunque los nulos sólo se permiten en objetos, no en colecciones), como los anteriormente mencionados ObjectId, datos binarios, expresiones regulares y código JavaScript incrustado. Por el momento, no abordaremos los últimos dos. El hecho de que BSON pueda almacenar datos binarios significa que cualquier cosa que se pueda reducir a una matriz de bytes se puede almacenar, lo que, de hecho, significa que MongoDB puede almacenar cualquier cosa, aunque no pueda hacer consultas dentro de dicho blob binario.

¡Aún no está muerto (o terminado)!

Hay mucho más que abordar acerca de MongoDB, incluidos la compatibilidad con LINQ; la realización de consultas más complejas del lado servidor que superan las capacidades de consulta de estilo QBE mostradas hasta ahora; y lograr que MongoDB exista tranquilamente en una granja de servidores de producción. Pero, por ahora, este artículo y un examen cuidadoso de IntelliSense deben ser suficientes para que el programador políglota comience a trabajar.

A propósito, si existe un tema en particular que le gustaría abordar, no dude en enviarme una nota. Después de todo, se trata de su columna. ¡Feliz codificación!

Ted Neward es un director de Neward & Associates, una empresa independiente que se especializa en sistemas empresariales de .NET Framework y plataformas Java. Ha escrito más de 100 artículos, es un MVP de C#, orador de INETA y ha sido autor y coautor de decenas de libros, incluido el próximo “Professional F# 2.0” (Wrox). Regularmente asesora y asiste como mentor. Puede comunicarse con él en ted@tedneward.com o leer su blog en blogs.tedneward.com.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Kyle Banker y Sam Corder