Enero de 2018

Volumen 33, número 1

Puntos de datos: creación de Azure Functions para interactuar con Cosmos DB

Por Julie Lerman

Julie LermanEn mi última columna, expliqué cómo compilar una aplicación para la Plataforma universal de Windows (UWP) simple, un juego llamado CookieBinge, que simplemente era una excusa para explorar el uso de Entity Framework Core 2.0 (EF Core) en una aplicación de Windows 10 enlazada a un dispositivo. La adición de proveedores de bases de datos portátiles, como SQLite, completan la imagen para hacerlo posible.

En su estado actual, la aplicación almacena puntuaciones de juegos localmente en el dispositivo de juego mediante EF Core 2.0 para mantener los datos en una base de datos de SQLite. En ese último artículo, prometí que la siguiente iteración de este juego permitiría a los usuarios compartir esos datos con otros jugadores en Internet. Para lograr ese objetivo, usaré dos características geniales de Azure: Azure Cosmos DB y Azure Functions.

Algunos datos sobre Azure Cosmos DB

Azure Cosmos DB es la nueva generación de Azure Document DB, una tecnología sobre la que he escrito muchas veces en esta columna. A "Una introducción a Microsoft Azure DocumentDB" (msdn.com/magazine/mt147238) de junio de 2015 le siguieron dos artículos más donde la usé como back-end de una aplicación web de Aurelia mediante una API de servidor de Node.js.

DocumentDB evolucionó para convertirse en una base de datos distribuida globalmente con algunas características extraordinarias. Además de la facilidad con la que se puede distribuir globalmente, sus modelos de coherencia se han realineado para que ofrezca más opciones entre las que elegir, aparte de la coherencia fuerte y la posible. Entre estos dos extremos, ahora los usuarios pueden elegir las opciones de obsolescencia limitada, sesión o prefijos coherentes. Existen muchas más características importantes para la compatibilidad con el almacén de datos, y la base de datos de documentos está unida ahora por varios modelos de datos diferentes [documentos accesibles a través de las API de MongoDB, bases de datos de gráficos, tablas (pares clave-valor) y almacenamiento de datos en columnas]. Todos estos modelos están al amparo de Cosmos DB. De hecho, si tenía una instancia de Azure DocumentDB, está cambió automáticamente a una instancia de Azure Cosmos DB, lo que permite que su base de datos de documentos existente use todas las nuevas características de Cosmos DB. Para obtener más información sobre Cosmos DB, puede empezar por cosmosdb.com.

Más información sobre Azure Functions

Usaré Cosmos DB para almacenar puntuaciones de CookieBinge. No obstante, en lugar de escribir todo el código yo mismo con las API de Document DB, aprovecharé otra característica relativamente nueva de Azure: Azure Functions. Azure Functions es la oferta de "informática sin servidor" de Microsoft. Siempre he sido escéptica en cuanto a esta expresión, ya que la informática sigue estando en un servidor... solo que no es el mío. No obstante, al final tuve la oportunidad de trabajar con Azure Functions y ahora siento un gran respeto por el concepto. Azure Functions le permite centrarse en la lógica real que quiere ejecutar en la aplicación, mientras se encarga de problemas transversales, como la implementación, la compatibilidad de las API y las conexiones con otras funcionalidades, como el almacenamiento de datos o el envío de correos electrónicos. No lo comprendí totalmente hasta que lo hice yo misma, de modo que espero que, si sigue mi camino mientras preparo esta característica para la aplicación CookieBinge, también tendrá su momento de revelación.

Preparación para compilar la primera función de Azure Functions

Existen tres maneras de compilar funciones de Azure Functions. Una son las herramientas de Visual Studio 2017. Otra es directamente en Azure Portal. También puede usar Visual Studio Code junto con la interfaz de la línea de comandos (CLI) de Azure. Decidí iniciar mi aprendizaje mediante el portal, porque me guiaba en todos los pasos que necesitaba. Otra ventaja es que, sin la ayuda de las herramientas de Visual Studio 2017, me veía obligado a pensar un poco más en todas las partes móviles. Creo entenderlo mucho mejor de esa manera. Obviamente, existen montones de recursos fabulosos para hacerlo también en Visual Studio 2017, pero otro aspecto que me encanta del portal es su opción basada en web y, por tanto, multiplataforma. Tenga en cuenta que, aunque puede implementar su código de función y sus recursos desde el control de código fuente de Azure, todo lo que compile directamente en el portal deberá descargarse en la máquina (una tarea simple) y, desde allí, insertarse en el repositorio.

Si quiere continuar y todavía no tiene ninguna suscripción de Azure, me complace informarle de que puede obtener una gratuita, que no solo es para una prueba breve. Algunos productos de Azure serán gratuitos durante un año y algunas decenas lo serán para siempre. Diríjase a azure.com/free para prepararse. Uso la cuenta que obtengo con parte de mi suscripción a Visual Studio, que presenta una concesión de crédito mensual para experimentar con Azure.

Antes de crear las funciones, debo definir mis objetivos. Quiero que mi aplicación pueda hacer lo siguiente:

  • Almacene las puntuaciones de usuario en la Web, y mantenga no solo algunos datos del usuario y la fecha junto con la puntuación, sino también el tipo de dispositivo de juego.
  • Permita a los usuarios recuperar sus máximas puntuaciones en todos los dispositivos donde jueguen.
  • Permita que un usuario recupere las máximas puntuaciones de todos los usuarios.

No voy a encallarme en esta lección con cuestiones como la creación y autenticación de cuentas, aunque obviamente serán acciones necesarias en el mundo real. Mi objetivo es mostrarle Azure Functions y, al final, la interacción desde la aplicación para UWP.

Creación de la función de Azure en Azure Portal

Azure Functions es un servicio agrupado en una aplicación de función que le permite definir y compartir la configuración en su conjunto de funciones. Por tanto, empezaré creando una nueva aplicación de función. En Azure Portal, haga clic en Nuevo y filtre por "aplicación de función" para buscar fácilmente esa opción. Haga clic en Function App en la lista de resultados y, a continuación, en Crear. Se le pedirá que rellene algunos metadatos, como un nombre para la aplicación. Asigné el nombre cookiebinge.azurewebsites.net a la mía. Dado que es una simple demo, aceptaré el resto de valores predeterminados en la página Crear. Para facilitar el acceso futuro a su nueva aplicación de función, active la opción Anclar al panel y haga clic en el botón Crear. La implementación de mi nueva aplicación de función tardó solo unos 30 segundos en completarse.

Ahora puede agregar algunas funciones a la aplicación de función. Mis funciones estarán compiladas para respaldar la lista completa de objetivos mencionados anteriormente. El servicio Azure Functions tiene un conjunto de eventos predefinido (y bastante completo) al que puede responder, incluida una solicitud HTTP, un cambio en una base de datos de Cosmos DB o un evento de un blob o una cola. Dado que quiero llamar a estas funciones desde la aplicación para UWP, necesito funciones que respondan a solicitudes recibidas a través de HTTP. El portal proporciona un montón de plantillas en distintos lenguajes: Bash, Batch, C#, F#, JavaScript, PHP, PowerShell, Python y TypeScript. Usaré C#.

Para crear la primera función dentro de la aplicación de función, haga clic en el signo más junto al encabezado de Functions. Verá botones para crear funciones predefinidas, pero si se desplaza debajo de estos, encontrará un vínculo para crear una función personalizada. Elija esa opción y verá una cuadrícula desplazable con opciones de plantilla, como se muestra en la Figura 1. Desencadenador HTTP: C# debe estar al principio de esa lista y es la opción que debe seleccionar.

Fragmento de la lista de plantillas para crear funciones de Azure Functions nuevas y personalizadas
Figura 1 Fragmento de la lista de plantillas para crear funciones de Azure Functions nuevas y personalizadas

Asigne un nombre a la función y, a continuación, haga clic en el botón Crear. A la mía la denominé StoreScores.

El portal creará una función con algún código predeterminado para que pueda ver cómo se estructura. La función se integra en un archivo denominado run.csx (consulte la Figura 2). También puede tener lógica adicional en archivos complementarios, pero es un uso más avanzado del que se requiere para esta introducción.

Lógica de la función predeterminada para un nuevo desencadenador HTTP
Figura 2 Lógica de la función predeterminada para un nuevo desencadenador HTTP

El único método del ejemplo se denomina Run, que es al que llamará Azure en respuesta a una solicitud HTTP a esta función. Tiene un parámetro para capturar la solicitud y otro para transmitir información a un registro.

En el ejemplo, puede ver que la función busca datos entrantes que representen un nombre, y la función es lo suficientemente flexible para buscarlos en los parámetros de consulta y en el cuerpo de la solicitud. Si el nombre no se encuentra, la función devolverá una clase Http­ResponseMessage con un mensaje de error cordial; de lo contrario, devolverá "Hello [name]" en la respuesta.

Personalización de la función para interactuar con Cosmos DB

El objetivo de la función es almacenar los datos entrantes en una base de datos de Cosmos DB. Aquí empieza la magia. No es necesario crear conexiones, comandos ni más código para llevar a cabo esta tarea. Azure Functions tiene la capacidad de integrarse fácilmente con algunos otros productos de Azure, y Cosmos DB es uno de ellos.

En la lista Funciones, debería ver la nueva función y tres elementos bajo esta. Uno de estos elementos es Integrar. Selecciónelo y verá el formato que se muestra parcialmente en la Figura 3. Observe que indica que el desencadenador es una solicitud HTTP y que la salida devuelve algo a través de HTTP. Dado que quiero devolver un mensaje de éxito o fracaso, decido mantener esa salida HTTP. Pero también quiero agregar una salida que tenga una colección de Cosmos DB como destino.

Definición de puntos de integración de funciones
Figura 3 Definición de puntos de integración de funciones

Para realizar esta acción, haga clic en Nueva salida, que mostrará una lista de iconos. Desplácese hacia abajo hasta el icono denominado Azure Cosmos DB, selecciónelo y, a continuación, más abajo en la página verá el botón SELECT. Ya sabe qué hacer. (Haga clic en el botón).

La pantalla para configurar esta integración aparece rellenada previamente con los valores predeterminados. El valor de Nombre de parámetro de documento representa el parámetro que usará en el archivo run.csx. Dejaré el nombre predeterminado, outputDocument. A continuación, se encuentran los nombres de la base de datos de Cosmos DB y la colección de dicha base de datos, así como la conexión a la cuenta de Cosmos DB donde reside la base de datos. También verá una casilla para que la base de datos se cree automáticamente. Ya tengo algunas cuentas de Cosmos DB creadas, de modo que usaré una, pero dejo que mi función cree una nueva base de datos denominada CookieBinge con una colección denominada Binges en esa cuenta. En la Figura 4 se muestra cómo he rellenado este formulario antes de guardar la definición de salida. Dado que activé la casilla para crear la base de datos y la colección, estas se crearán automáticamente, pero no si guardo esta salida. Cuando la función intente por primera vez almacenar datos en la base de datos y vea que no existe, la función creará la base de datos sobre la marcha.

Definición de una instancia de Cosmos DB como salida para la función
Figura 4 Definición de una instancia de Cosmos DB como salida para la función

Personalización de Run.csx

Ha llegado la hora de redefinir el código de la función. La nueva versión de la función espera un objeto JSON pasado que se alinee con esta clase BingeRequest, que agregué en el archivo run.csx debajo del método Run:

public class BingeRequest{
  public string userId {get;set;}
  public string userName {get;set;}
  public string deviceName {get;set;}
  public DateTime dateTime {get;set;}
  public int score{get;set;}
  public bool worthit {get;set;}
}

No obstante, esta no es la misma estructura que los datos que quiero almacenar, ya que quiero capturar una propiedad más (la fecha y hora de registro de los datos en la base de datos). Lo haré con una segunda clase, BingeDocument, heredada de BingeRequest, que hereda así todas sus propiedades, además de agregar una propiedad más denominada logged. El constructor toma una clase BingeRequest rellenada y después de configurar el valor de logged, transfiere los valores de BingeRequest a sus propias propiedades:

public class BingeDocument:BingeRequest
  {     
    public BingeDocument(BingeRequest binge){
    logged=System.DateTime.Now;
    userId=binge.userId;
    userName=binge.userName;
    deviceName=binge.deviceName;
    dateTime=binge.dateTime;
    score=binge.score;
    }
    public DateTime logged{get;set;}
  }

Con estos tipos implementados, el método Run puede aprovecharlos. En la Figura 5 se muestra la lista modificada de run.csx, incluidos los marcadores de posición de las clases BingeRequest y BingeDocument descritas anteriormente.

Vamos a analizar el nuevo método Run. Su signatura toma una solicitud y una propiedad TraceWriter igual que hacía la signatura original, pero ahora también tiene un parámetro de salida asincrónica denominado outputDocument. El resultado del parámetro de salida es lo que se insertará en la salida de Cosmos DB que he definido. Observe que su nombre se alinea con el nombre del parámetro de salida en la configuración de salida de la Figura 4. La propiedad TraceWriter me permite emitir mensajes a la ventana de registro que se encuentra debajo de la ventana de código. A final, los eliminaré, pero es como en otros tiempos, sin los IDE que permiten realizar la depuración. Pero no me malinterprete. La ventana de código es sorprendente a la hora de analizar el lenguaje con el que trabaja y, al guardar, los errores del compilador, que son muy detallados, también se muestran en la ventana de depuración. También realiza acciones, como insertar una llave de cierre al escribir una llave de apertura. De hecho, existen varias características del editor impresionantes. Por ejemplo, haga clic con el botón derecho en la ventana del editor para ver una extensa lista de características del editor que puede usar.

Figura 5 Nuevo archivo run.csx que captura el objeto Binge y lo almacena en la salida, Cosmos DB

using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req,
  TraceWriter log,    IAsyncCollector<object> outputDocument)
{
  BingeRequest bingeData =  await req.Content.ReadAsAsync<BingeRequest>();
  log.Verbose("Incoming userId:" + bingeData.userId);
  var doc=new BingeDocument(bingeData,log);
  log.Verbose("Outgoing userId:" + doc.userId);
  await outputDocument.AddAsync(doc);
  if (doc.userId !=" " ){
    return req.CreateResponse(HttpStatusCode.OK,$"{doc.userId} was created" );
  }
  else {
    return req.CreateResponse(HttpStatusCode.BadRequest,
      $"The request was incorrectly formatted." );
  }
}
public class BingeRequest{ . . . }
public class BingeDocument { . . . }

La primera línea de código de Run lee de manera asincrónica la solicitud y convierte su resultado en un objeto BingeRequest.

A continuación, creo la instancia de un nuevo objeto BingeDocument y paso el objeto que acabo de crear desde la solicitud, lo que da lugar a un objeto BingeDocument completamente rellenado, junto con la propiedad registrada rellenada.

A continuación, uso la propiedad TraceWriter para mostrar algunos datos de la solicitud en los registros, de modo que, al realizar la depuración, pueda indicar si el objeto BingeDocument obtuvo los datos del objeto de la solicitud.

Finalmente, agrego de manera asincrónica el objeto BingeDocument que acabo de crear al objeto outputDocument asincrónico.

El resultado de ese objeto outputDocument es lo que la función envía a Cosmos DB. Se almacena en DocumentDB con el formato JSON (la función lo convierte nuevamente en segundo plano por usted). Dado que lo conecté todo con la configuración de la integración, no tengo que escribir ningún código para que eso suceda.

Una vez dicho y hecho todo, devuelvo un mensaje a través de HttpResponse, que transmite el éxito o fracaso de la función.

Compilación y prueba de la función

Las funciones se compilan cuando se guardan. Voy a eliminar de manera aleatoria algo importante en el código para que pueda ver el compilador en acción, con los resultados visibles en la ventana de registro debajo de la ventana de código. En la Figura 6 se muestra la salida del compilador y se resalta el caos que creé al eliminar una llave de apertura de la línea 13. Incluso sin un depurador, he comprobado que ver estos errores me ayuda a trabajar con el código que he escrito sin la ayuda de IntelliSense ni de otras herramientas de codificación en mis IDE. Al mismo tiempo, he aprendido cuánto dependo de esas herramientas.

Ventana Registro que muestra información del compilador con errores incluidos
Figura 6 Ventana Registro que muestra información del compilador con errores incluidos

Cuando se corrige el código y el compilador está satisfecho, el registro muestra el mensaje "Reloading" seguido de "Compilation succeeded".

Ha llegado el momento de probar la función, lo que puede hacer directamente en la misma ventana donde la ha codificado. A la derecha del editor de código existen dos paneles con pestañas. Uno muestra la lista de archivos relacionados con la función. De manera predeterminada, solo aparecen dos, el archivo run.csx que estoy viendo en este momento y un archivo function.json que contiene toda la configuración definida en la interfaz de usuario.  La otra pestaña está destinada a ejecutar pruebas. Esta interfaz de usuario de prueba integrada es como una miniaplicación de Postman o Fiddler para crear solicitudes HTTP con mucho menos esfuerzo, porque ya conoce la función que se va a probar. Todo lo que debe hacer es insertar un objeto JSON para representar la solicitud entrante. La interfaz de usuario de prueba está establecida de manera predeterminada para enviar un método HTTP POST, de modo que no tiene que cambiar este valor para la prueba. Introduzca el siguiente objeto JSON en el cuadro de texto Cuerpo de la solicitud. El esquema es importante, pero puede usar los valores que quiera:

{
  "userId": "54321",
  "userName": "Julie",
  "deviceName" : "XBox",
  "dateTime": "2017-10-25 15:26:00",
  "score" : "5",
  "worthit" : "true",
  "logged": ""
}

A continuación, haga clic en el botón Ejecutar en el panel Probar. La prueba llamará a la función, pasará el cuerpo de la solicitud y, a continuación, mostrará todos los resultados HTTP en la ventana Salida. En la Figura 7, puede ver la salida "54321 was created", así como la salida del registro de la función en la ventana Registros.

Panel de prueba después de ejecutar una prueba en la función
Figura 7 Panel de prueba después de ejecutar una prueba en la función

Visualización de los nuevos datos en la base de datos de Cosmos DB

Aquí puede ver que, como resultado de esta primera prueba correcta, se creó la base de datos de CookieBinge de Cosmos DB y, en esta, la colección Binge donde se almacenó este documento. Veámoslo antes de encapsular esta entrega de mi columna de varias partes.

Para hacerlo, puede abrir primero la cuenta de Cosmos DB en el portal donde creó esta base de datos. La mía se denomina datapointscosmosdb, así que voy a Todos los recursos y escribo datapoints en el filtro para buscarla. Cuando abro la cuenta, puedo ver todas las colecciones y bases de datos, aunque la única que tengo es mi colección Binges en la base de datos de CookieBinge, como se muestra en la Figura 8. Esto es lo que acaba de crear la función.

Colección Binges enumerada en la cuenta datapointscosmosdb
Figura 8 Colección Binges enumerada en la cuenta datapointscosmosdb

Haga clic en Binges para abrir el Explorador de datos de esta colección. Ejecuté la prueba dos veces, de modo que puede ver en la Figura 9 que se almacenaron dos documentos en la colección. Las primeras siete propiedades del documento son las que definí. El resto son metadatos que Cosmos DB y las API pertinentes usan para tareas como las de indexación, búsqueda, creación de particiones, etc.

Observación de documentos almacenados en Cosmos DB en el portal
Figura 9 Observación de documentos almacenados en Cosmos DB en el portal

Mirando hacia el futuro

Si vuelve a observar la Figura 7, verá el vínculo Obtener la dirección URL de la función sobre la ventana de código. Esa dirección URL es la que usaré en la aplicación CookieBinge para enviar datos a la nube.

Ahora que ha visto cómo se crea la función y cómo se conecta a Cosmos DB, en mi próxima columna le mostraré cómo compilar dos funciones más para recuperar distintas vistas de los datos. En la entrega final le mostraré cómo llamar a las funciones desde la aplicación CookieBinge y cómo mostrar sus resultados.


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 en @julielerman y vea sus cursos de Pluralsight en juliel.me/PS-Videos.

Gracias al siguiente experto técnico de Microsoft por su ayuda en la revisión de este artículo: Jeff Hollan


Discuta sobre este artículo en el foro de MSDN Magazine