Febrero de 2019

Volumen 34, número 2

[Puntos de datos]

Exploración de la funcionalidad multimodelo de Azure Cosmos DB mediante su API para MongoDB

Por Julie Lerman

Julie LermanEn la columna del mes pasado (msdn.com/magazine/mt848702), y bastantes antes de eso, describí distintas formas de trabajar con Azure Cosmos DB, el servicio de base de datos multimodelo distribuido globalmente que admite diversas API de NoSQL. En todos los trabajos que he hecho hasta ahora he usado solo una API determinada: la API de SQL, que permite interactuar con los datos usando SQL como lenguaje de consulta. Uno de los otros modelos permite interactuar con una base de datos de Cosmos DB con la mayoría de las herramientas y API disponibles para MongoDB. Este modelo también se beneficia del formato de documento BSON, un formato de serialización binario que es compacto y eficaz, y proporciona una programación concisa del tipo de datos. ¿Pero cómo es posible que se acceda a una sola base de datos a través de estos y otros modelos (Cassandra, Gremlin y Table)? La respuesta se encuentra en el motor de base de datos subyacente, que se basa en lo que se conoce como modelo de datos de secuencia de registro de átomos (ARS), que permite admitir de forma nativa varias API y modelos de datos. Cada una de las API es compatible con el protocolo de transferencia en una popular estructura de datos y motor NoSQL, como JSON, que permite interactuar con los datos.

Es importante entender que las API multimodelo, actualmente, no son intercambiables. Las bases de datos de Cosmos DB se encuentran en una cuenta de Cosmos DB. Puede tener varias cuentas de Cosmos DB en su suscripción a Azure, pero cuando se crea una cuenta, se selecciona qué API va a usar para las bases de datos de esa cuenta. Una vez seleccionada, es la única API que puede usar para sus bases de datos. La documentación de Azure usa términos como "actualmente" al tratar esto, por lo que la expectativa es que, en algún momento, haya más flexibilidad en estas líneas.

Tengo curiosidad por probar otra API. A excepción de cierta exploración de Azure Table Storage que hice hace tiempo (consulte mi artículo de julio de 2010 en msdn.com/magazine/ff796231) y que se alinea con Table API en Azure Cosmos DB, nunca he usado otros tipos de bases de datos de ningún modo. MongoDB es una de las API más usadas, así que es la que he decidido investigar, y me he divertido con mis primeras exploraciones.  Compartiré aquí algunas cosas que he aprendido, pero tenga en cuenta que esto no está pensado como un artículo de introducción a MongoDB. También le recomendaría explorar los cursos de Pluralsight de Nuri Halperin (bit.ly/2SI2Vxw), que incluyen contenido de MongoDB para principiantes y expertos. También encontrará vínculos a muchos otros recursos en el artículo.

La compatibilidad de MongoDB está más orientada a desarrolladores y sistemas que ya usan MongoDB, ya que Azure ofrece numerosas ventajas. Incluso hay una guía para migrar datos desde bases de datos MongoDB existente a Azure Cosmos DB (bit.ly/2FhzmPi).

La primera ventaja que detecté es que usar MongoDB permite tener una instancia local de la base de datos en el equipo portátil para trabajar con ella durante el desarrollo. Aunque esto es posible en Windows para las API de SQL y MongoDB mediante el emulador de Cosmos DB (bit.ly/2sHNsAn), ese emulador solo se ejecuta en Windows. Pero se puede instalar MongoDB (estoy usando la edición Community) en una variedad de sistemas operativos, incluido macOS. Esto le permite emular las características básicas de una base de datos de Cosmos DB basada en la API de Mongo DB localmente. Para empezar, trabajaré de forma local, me familiarizaré con MongoDB y, a continuación, cambiaré a una base de datos de Azure.

Puede encontrar instrucciones de instalación para todas las plataformas admitidas en los documentos oficiales de MongoDB en bit.ly/2S96ywj. Como alternativa, puede extraer una imagen de Docker para ejecutar MongoDB en macOS, Linux o Windows desde hub.docker.com/_/mongo.

Una vez instalado, inicie el proceso mediante el comando mongod.exe. A menos que se especifique lo contrario, MongoDB supone que tiene el directorio /data/db creado y que tiene permiso para acceder. Puede crearlo en la ubicación predeterminada (por ejemplo, c:\data\db en Windows o macOS, en la carpeta raíz donde que se encuentran las carpetas de aplicación, biblioteca y usuarios) o especificar la ubicación como parámetro del comando "mongod". Dado que solo estoy probando cosas en mi máquina de desarrollo, he usado "sudo mongod" para asegurarme de que el servicio tenía los permisos necesarios. La edición Community se ejecutará, de forma predeterminada, en el host local, puerto 27017, es decir, 127.0.0.1:27017.

Hay varias formas de interactuar con MongoDB. Aunque la que más me interesa es la API de C#, me parece útil empezar trabajando lo más cerca posible de "metal" y, a continuación, cambiar a una de las API. Si no está familiarizado con MongoDB, es recomendable empezar por usar su shell (que se instala junto con el servicio) para hacer cierto trabajo en la línea de comandos. Para iniciar el shell, escriba "mongo". MongoDB instala tres bases de datos del sistema: local, administración y configuración. En el shell, escriba "show dbs" para verlas.

MongoDB no tiene ningún comando explícito para crear una nueva base de datos en el shell u otras API. En su lugar, utilice una base de datos y, la primera vez que inserte datos en ella, si no existe, se creará. Pruebe con:

use myNewDatabase

Si llama a "show dbs" justo después, aún no verá myNewDatabase.

El shell utiliza una "db" de acceso directo para trabajar con el objeto de base de datos actual, aunque aún no exista la base de datos real.

Ahora tiene que insertar un documento. Los documentos se crean como JSON y la API los almacena en MongoDB en su formato BSON binario. Este es un documento de ejemplo:

{
  firstname : "Julie",
  lastname : "Lerman"
}

Pero los documentos no van directamente a la base de datos; deben almacenarse en una colección. MongoDB sigue el mismo comportamiento con las colecciones que con la base de datos: Si se hace referencia a una colección que aún no existe, la crea automáticamente cuando se insertan datos en esa colección. Por lo tanto, puede hacer referencia a una nueva colección al insertar un nuevo documento. Tenga en cuenta que se distinguen mayúsculas de minúsculas. Usaré el método insertOne en una nueva colección denominada MyCollection para insertar el documento. Se devuelve otro documento que confirma que el documento se ha insertado y MongoDB proporciona una clave de identificador única para mi documento insertado mediante su propio tipo de datos, ObjectId:

> db.MyCollection.insertOne({firstname:"Julie",lastname:"Lerman"})
{
  "acknowledged" : true,
  "insertedId" : ObjectId("5c169d4f603846f26944937f")
}

Ahora, "show dbs" incluirá myNewDatabase en su lista y puedo consultar la base de datos con el comando "find" del objeto de colección. No paso ningún parámetro de ordenación ni de filtrado, por lo que devolverá todos los documentos (en mi caso, solo uno):

>db.MyCollection.find()
{ "_id" : ObjectId("5c169d4f603846f26944937f"), "firstname" : "Julie", 
  "lastname" : "Lerman" }

Apenas he arañado la superficie de las funcionalidades, pero ha llegado la hora de avanzar. Para salir del shell de mongo, escriba "exit" en el símbolo del sistema.

Uso de la extensión de Cosmos DB de Visual Studio Code

Ahora que ha creado una nueva base de datos y ha hecho un poco de trabajo en el shell, puede beneficiarse de algunas herramientas. En primer lugar, usaré la extensión de Azure Cosmos DB en Visual Studio Code. Si no tiene VS Code, instálelo desde code.visualstudio.com. A continuación, puede instalar la extensión desde el panel de extensiones de VS Code.

Para abrir la instancia local de MongoDB en la extensión, haga clic con el botón derecho del mouse en Attached Database Accounts (Cuentas de base de datos adjuntas) y elija Attach Database Account (Adjuntar cuenta de base de datos). Se le pedirá que elija entre las API de Cosmos DB. Seleccione MongoDB. El siguiente mensaje es para la dirección donde se debe encontrar la base de datos. Su valor predeterminado sera el de MongoDB: mongodb://127.0.0.1:27017. Una vez conectado, debe poder ver las bases de datos en el Explorador de Cosmos DB, incluida la creada en el shell con su colección y documento, como se muestra en la figura 1.


Figura 1 Explorar el servidor, las bases de datos y los datos de MongoDB

Si edita un documento abierto, la extensión le pedirá que lo actualice en el servidor. Esto sucederá tanto si está conectado a una base de datos local como en la nube.

MongoDB en una aplicación .NET Core

Ahora vamos a subir de nivel y vamos a usar la base de datos MongoDB en una aplicación .NET Core. Uno de los distintos controladores disponibles es un controlador de .NET (bit.ly/2BvUEFl). La versión actual, 2.7, es compatible con .NET Framework 4.5 y 4.6, así como con .NET Core 1.0 y 2.0 (incluidas las versiones secundarias). Estoy usando el SDK de .NET Core más reciente (2.2) y, para empezar, crearé un nuevo proyecto de consola en una carpeta que denominé MongoTest mediante el comando de la interfaz de línea de comandos (CLI) de dotnet:

dotnet new console

A continuación, usaré la CLI para agregar una referencia al controlador de .NET para MongoDB, llamado mongocsharpdriver, en el proyecto:

dotnet add package mongocsharpdriver

Crearé una aplicación sencilla basada en el modelo del mes pasado, con algunas clases relacionadas con el libro y la serie "La expansión". Tengo dos clases, Ship y Character:

public Ship()
{
  Crew=new List<Character>();
}
  public string Name { get; set; }
  public Guid Id { get; set; }
  public List<Character> Crew{ get; set;}
}
public class Character
{
  public string Name { get; set; }
  public string Bio {get;set;}
}

Tenga en cuenta que Ship tiene un GUID denominado Id. Por convención, MongoDB lo asociará con su propiedad _id necesaria. Character no tiene ningún identificador. He tomado una decisión de modelado. En el contexto de la interacción con los datos de la nave, siempre quiero ver los personajes de la nave. Almacenarlos juntos facilita su recuperación. Sin embargo, quizás tenga más detalles sobre los personajes en otro sitio. Puede insertar un objeto que solo tenga una referencia a los identificadores de los personajes; por ejemplo:

public List<Guid> Crew{ get; set;}

Pero eso significa tener que ir a buscarlos cuando necesite una nave con una lista de personajes. Una alternativa híbrida sería agregar una propiedad Id a Character, lo que me permitiría hacer las referencias cruzadas que necesite. Como subdocumento, los valores de Id de los personajes serían una propiedad de aleatoria. MongoDB solo requiere un documento raíz para tener un identificador. Pero he decidido no preocuparme por el identificador de los personajes para mis primera exploraciones.

A la hora de modelar, es necesario tomar muchas decisiones. Lo más importante, si su cerebro tiende a conceptos de base de datos relacional, donde tiene que hacer una gran cantidad de traducción entre los datos relacionales y los objetos, es detenerse, y considerar las cualidades y los patrones de base de datos de los documentos. Considero muy útil esta guía sobre el modelado de datos del documento para la base de datos NoSQL de la documentación de Azure Cosmos DB: bit.ly/2kpF46A.

Hay unos cuantos puntos más que debe entender acerca de los identificadores. Si no proporciona ningún valor a la propiedad del Id de la nave, MongoDB creará el valor automáticamente, tal y como haría SQL Server u otra bases de datos. Si el documento raíz no tiene ninguna propiedad que se asigne al valor de _id del documento almacenado (por convención o sus propias reglas de asignación), se producirá un error al intentar serializar resultados donde se incluya _id.

Trabajar con la API mongocsharpdriver

La API del controlador .NET comienza con una instancia de MongoClient y se trabaja a partir de aquí para interactuar con la base de datos, la colección y los documentos. La API refleja algunos de los conceptos que ya he mostrado con el shell. Por ejemplo, una base de datos y una colección pueden crearse sobre la marcha mediante la inserción de datos. Este es un ejemplo de ello con la nueva API:

private static void InsertShip () {
  var mongoClient = new MongoClient ();
  var db = mongoClient.GetDatabase ("ExpanseDatabase");
  var coll = db.GetCollection<Ship> ("Ships");
  var ship = new Ship { Name = "Donnager" };
  ship.Characters.dd(new Character { Name = "Bobbie Draper", 
    Bio="Fierce Marine"});  
  coll.InsertOne (ship);
}

Donde utilizaba el comando "use database" en el shell, ahora llamo a GetDatabase en el objeto de MongoClient. El objeto de base de datos tiene un método GetCollection<T> genérico. Estoy especificando Ship como tipo. La cadena "Ships" es el nombre de la colección de la base de datos. Una vez definida, puedo usar InsertOne o InsertMany, como en el shell. La API de .NET también proporciona equivalentes asincrónicos, como InsertOneAsync.

La primera vez que ejecuté el método InsertShip, se creó la nueva base de datos y la colección, junto con el nuevo documento. Si no hubiera insertado el nuevo documento y solo hubiera hecho referencia a la base de datos y la colección, no se habrían creado sobre la marcha. Respecto al shell, no hay ningún comando explícito para crear una base de datos.

Este es el documento que se creó en la base de datos:

{
  "_id": {
    "$binary": "TbKPi3+tLUK9b68lJkGaww==",
    "$type": "3"
  },
  "Name": "Donnager",
  "Characters": [
    {
      "Name": "Bobbie Draper",
      Bio: "Fierce Marine"
    }
  ]
}

Sin embargo, lo que me resulta más interesante es la colección tipada (GetCollection<Ship>). La documentación de MongoDB describe una colección como "análoga a las tablas de las bases de datos relacionales" (bit.ly/2QZcOcD), lo que es una descripción interesante para una base de datos del documento, donde puede almacenar documentos aleatorios y no relacionados en una colección. Aun así, enlazar una colección con un tipo determinado, al igual que con la colección "ships", sugiere que estoy aplicando el esquema de tipo de nave en esta colección. Pero esto es para la instancia de la colección, no para la colección física de la base de datos. Informa a la instancia particular cómo serializar y deserializar objetos, dado que puede almacenar datos de cualquier objeto en una sola colección. A partir de la versión 3.2, MongoDB agregó una característica que aplica las reglas de validación de esquemas, aunque no es el valor predeterminado.

Puedo usar la misma colección Ships para otros tipos:

var collChar = db.GetCollection<SomeOtherTypeWithAnId> ("Ships");

Sin embargo, esto supondría un problema a la hora de recuperar los datos. Necesitaría una manera de identificar los tipos de documento en la colección. Si leyó el artículo del mes pasado acerca del proveedor de Cosmos DB para EF Core (que usa la API de SQL), es posible que recuerde que, cuando EF Core inserta documentos en Cosmos DB, agrega una propiedad Discriminator para que siempre pueda saber con qué tipo se alinea un documento. Podría hacer lo mismo para la API de MongoDB, pero eso sería una pequeña trampa, ya que MongoDB usa discriminadores de tipo para especificar la herencia de objetos (bit.ly/2sbHvgA). He agregado una nueva clase, DecommissionedShip, que hereda de Ship:

public class DecomissionedShip : Ship {
  public DateTime Date { get; set; }
}

La API tiene una clase denominada BsonClassMap que se usa para especificar asignaciones personalizadas, incluido su método SetDiscriminatorIsRequired. Esto insertará el nombre de clase de forma predeterminada. Dado que se va a reemplazar la asignación predeterminada, deberá agregar también el método Automap.

He agregado un nuevo método, ApplyMappings, en program.cs y lo llamo en la primera línea del método Main. Esto indica a la API que agregue discriminadores para Ship y DecommissionedShip:

private static void ApplyMappings () {
    BsonClassMap.RegisterClassMap<Ship> (cm => {
      cm.AutoMap ();
      cm.SetDiscriminatorIsRequired (true);
    });
    BsonClassMap.RegisterClassMap<DecommissionedShip> (cm => {
      cm.AutoMap ();
      cm.SetDiscriminatorIsRequired (true);
    });
  }

He modificado el método InsertShip para crear, adicionalmente, un nueva clase DecommissionedShip. Dado que hereda de Ship, puedo usar una única instancia de la colección Ship y su comando InsertMany para agregar ambas naves a la base de datos:

var decommissionedShip=new DecommissionedShip{Name="Canterbury", 
  Date=new DateTime(2350,1,1)};
coll.InsertMany(new[]{ship,decommissionedShip});

Ambos documentos se insertan en la colección Ships y cada uno tiene un discriminador agregado como propiedad "_t". Esta es la clase DecommissionedShip:

{
  "_id": {
    "$binary": "D1my7H9MrkmGzzJGSHOZfA==",
    "$type": "3"
  },
  "_t": "DecommissionedShip",
  "Name": "Canterbury",
  "Characters": [],
  "Date": {
    "$date": "2350-01-01T05:00:00.000Z"
  }
}

Al recuperar los datos de una colección tipada en Ship, como en este método GetShip:

private static void GetShips ()
{
  var coll = db.GetCollection<Ship> ("Ships");
  var ships = coll.AsQueryable ().ToList ();
}

La API lee los discriminadores y materializa los objetos Ship y DecommissionedShip con todos sus datos intactos, incluida la fecha asignada a DecommissionedShip.

Otra ruta de acceso para la asignación es usar un objeto de colección tipado BsonDocument que no dependa de ningún tipo concreto. Consulte mi entrada de blog A Few Coding Patterns with the MongoDB C# API (Algunos patrones de programación con la API de C# de MongoDB) para ver cómo usar BsonDocuments, así como la forma de encapsular MongoClient, la base de datos y la colección para que el código resulte más legible.

Usar LINQ para realizar consultas

Puede recuperar documentos con la API de .NET mediante métodos de API o LINQ. La API usa un sofisticado método Find (similar al método de búsqueda del shell), que devuelve un cursor. Puede pasar filtros y propiedades del proyecto, y devolver objetos mediante uno de sus métodos de ejecución o agregación, muchos de los cuales son similares a los métodos LINQ. El método Find de la API de .NET requiere un filtro, por lo que, para obtener todos los documentos, puede filtrar por un elemento BsonDocument nuevo (vacío), que es un filtro de coincidencia de cualquier documento. Para LINQ, primero deberá transforma una colección en un elemento IQueryable (mediante AsQueryable()) y, a continuación, usar los métodos conocidos de LINQ para filtrar, ordenar y ejecutar. Si no incluye ni asigna la propiedad _id en sus clases, deberá usar la lógica de proyección para tenerlo en cuenta, ya que se devolverá de la consulta. Puede hacer referencia a la documentación o a otros artículos (por ejemplo, la fantástica serie de Peter Mbanugo en bit.ly/2Lqqw2J) para obtener más información sobre estos detalles.

Cambio a Azure Cosmos DB

Después de trabajar la lógica de persistencia localmente con la instancia de MongoDB, en algún momento querrá moverla a Cosmos DB en la nube. La extensión de Azure Cosmos DB de Visual Studio Code facilita la creación de una nueva cuenta de Cosmos DB si aún no tiene ninguna, aunque, probablemente, querrá ajustar su configuración en el portal. Si usa Visual Studio, la extensión de Cloud Explorer para VS2017 tiene características de exploración, pero no de creación de bases de datos, por lo que, en ese caso, deberá usar la CLI de Azure o trabajar en el portal.

Así es como puede crear una nueva instancia con la API de Azure Cosmos DB para MongoDB desde cero mediante la extensión de VS Code.

En primer lugar, necesitará conectar VS Code a su suscripción de Azure. Esto requiere la extensión de la cuenta de Azure (bit.ly/2k1phdp), que, una vez instalada, le permitirá conectarse. Y una vez conectado, la extensión de Cosmos DB mostrará las suscripciones y las bases de datos existentes. Igual que con la conexión local de MongoDB que se muestra en la figura 1, puede profundizar en las cuentas, las bases de datos, las colecciones y los documentos de Cosmos DB (los términos de Cosmos DB para estos son contenedores y elementos). Para crear una cuenta nueva, haga clic con el botón derecho del mouse en el signo más de la parte superior del explorador de la extensión. El flujo de trabajo será similar a la creación de una base de datos MongoDB local, tal como hice anteriormente. Se le pedirá que escriba un nombre de cuenta. Usaré datapointsmongodbs. A continuación, elija MongoDB en la lista de API disponibles y cree un nuevo recurso de Azure o seleccione uno existente para vincularlo a la cuenta. He creado uno nuevo para poder eliminar limpiamente el recurso y probar la base de datos según sea necesario. Después de esto, tendrá que seleccionar entre las regiones de centro de datos dónde se debe hospedar este. Vivo en el Este de Estados Unidos, por lo que elegiré la ubicación Este de EE. UU. Dado que Cosmos DB es una base de datos global, puede controlar el uso de las regiones en el portal u otras aplicaciones, pero no necesito eso para mi demostración. En este momento, deberá esperar unos minutos mientras se crea la cuenta.

Una vez creada, se mostrará en el explorador. Haga clic con el botón derecho en la cuenta y seleccione la opción Copy Connection String (Copiar cadena de conexión). Puede usar esta opción para cambiar el controlador de MongoDB para que apunte a la instancia de Azure Cosmos DB, en lugar de apuntar a la instancia local predeterminada, tal como he hecho aquí:

var connString=
  "mongodb://datapointsmongodbs:****.documents.azure.com:10255/?ssl=true";
ExpanseDb=new MongoClient(connString).GetDatabase("ExpanseDatabase");

Ejecutaré una versión refactorizada del método que inserta un nuevo elemento Ship y DecommissionedShip en la colección Ships de ExpanseDatabase. Después de actualizar la base de datos en el explorador, este muestra la base de datos recién creada en mi cuenta de Azure, colección y documentos en la base de datos de Cosmos DB, como se muestra en la figura 2.

Base de datos, colección y documentos recién creados en la base de datos de Cosmos DB
Figura 2 Base de datos, colección y documentos recién creados en la base de datos de Cosmos DB

No se trata en hacerse experto en Mongo DB, sino en comprender mejor el concepto de multimodelo

La disponibilidad de esta API no está pensada para convencer a los usuarios como yo, con décadas de experiencia con SQL, para cambiar a MongoDB para mis bases de datos Cosmos DB. Significaría tener que aprender demasiado. El verdadero objetivo consiste en habilitar a la gran cantidad de desarrolladores y equipos que ya usan MongoDB para que tengan una experiencia familiar y, al mismo tiempo, se beneficien de las ventajas que ofrece Azure Cosmos DB. He llevado a cabo mi propia exploración de la API de Azure Cosmos DB para MongoDB a fin de comprender mejor la funcionalidad multimodelo, así como para divertirme un poco descubriendo una nueva base de datos. Espero que mi experiencia aquí sirva de orientación de alto nivel para otros desarrolladores o clientes en el futuro.


Julie Lerman es directora regional de Microsoft, MVP de Microsoft, instructora y consultora del equipo de software. Vive en las colinas de Vermont. Puede encontrarla haciendo presentaciones sobre el acceso a datos y otros temas en grupos de usuarios y en conferencias en todo el mundo. Su blog es thedatafarm.com/blog y es la autora de "Programming Entity Framework", así como de una edición de Code First y una edición de DbContext, de O’Reilly Media. Sígala en Twitter: @julielerman.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Nuri Halperin (Plus N Consulting)


Comente este artículo en el foro de MSDN Magazine