Autenticación de Azure Key Vault con Azure Active Directory

Azure Key Vault es un repositorio/colección de llaves criptograficas (JWKs) que se resguardan sobre Azure. Expone un API para tener acceso y realizar operaciones sobre estas llaves.

La documentación no es clara sobre como tenemos acceso a esta API para hacer la integración con la aplicación que estamos desarrollando.

El control de acceso hacia el API de Azure Key Vault se establece en las políticas de acceso hacia un AAD Identities (Azure AD as a Third-Party Identity). Es una alternativa para autorizar el acceso hacia Azure Key Vault.

Vamos a ver como realizar esto por medio de 2 alternativas con Azure AD:

1. Creando una App dentro de Azure AD

2. Creando un Certificado en Azure AD (preferible)

 

Authenticate Azure Key Vault

Registrando aplicación con Azure AD

Para estro primero debemos ir al tenant de nuestra subscripcion desde el portal de Azure. Y entrar a la opción que dice “Azure Active Directory”.

Aquí seleccionamos crear nueva aplicación:

Se abrirá un formulario que nos pide nombre, tipo de aplicación y la URL de ingreso (Sign-on URL).

Nombre: AzureKeyVaultApp

Tipo Aplicación: Web app / API

URL Ingreso: http://AzureKeyVaultApp

Para el caso de la URL de ingreso puede ser cualquier cosa mientras sea una URL valida.

Damos click en Aceptar/Guardar y se creara la aplicación. Ahora debemos de seleccionar la aplicación que acabamos de crear y recuperar el ID (Application ID), y adicional crear una llave sobre la aplicación, que sera nuestro Secret Id. Estos valores los vamos a ocupar mas adelante.

Creamos una llave para el valor de Secret Id:

Bien, ahora debemos ir a nuestro contenedor de llaves de Azure Key Vault y establecer la aplicación que acabamos de crear como identidad de acceso. Esto lo hacemos dentro de la sección de políticas de acceso. Previamente ya debemos tener nuestro contenedor/almacén de llaves, aquí las instrucciones para hacerlo.

En la sección de politicas de acceso creamos una nueva política de acceso y como identidad buscamos la aplicación que creamos con el nombre de “AzureKeyVaultApp“, y para este caso seleccionamos todos lo permisos.

Una vez hecho esto ya podemos invocar el API desde nuestra aplicación.

En el código de arriba vemos como con los valores de la aplicación que creamos previamente inicializamos 3 variables keyVaultUrlclientId y secret. Después con el método CreateKeyVaultWithSecret creamos nuestro cliente que expone los métodos del API. Si ocupáramos un KeyVaultKeyResolverCachingKeyResolver podemos hacerlo con la instancia de KeyVaultClient.

Registrando Certificado

Primero debemos crear un certificado de prueba. Podemos hacer esto ejecutando los siguientes comandos en la consola de Visual Studio.

En resumen lo que hace las lineas que ejecutamos es crear un certificado con su respectiva llave privada.

Después debemos asociar el certificado a una aplicación de Azure AD. Esto no se puede hacer desde el portal, pero podemos ejecutar el siguiente script sobre Power Shell:

Una vez que se ejecuta exitosamente el script anterior podemos validar en el portal que la aplicación se haya creado, debe tener el nombre de “AzureKeyVaultCert”:

De ahí solo debemos de recupera el Application Id y asociar la aplicación como identidad de acceso a nuestro contenedor de Azure Key Vault, como ya lo hicimos con la aplicación anterior.

Ahora podemos probar esa forma de autenticación en nuestro código. Para hacer pruebas locales debemos instalar el certificado pues lo buscaremos por su thumbprint.

A diferencia del método anterior aquí definimos una variable que contiene el thumbprint del certificado con el que los buscaremos en el método FindCertificateByThumbprint. Lo demás en el código es igual.

Si queremos probar el código locamente debemos instalar el certificado. Si vamos a hacer la instalación sobre un AppService o CloudService debemos subir el certificado que generamos por medio del portal en la sección de “certificados”.

 

Analizador Fonético con Azure Search

Azure Search permite definir analizadores (análisis léxico sobre los términos de consulta) sobre las propiedades que vamos a indexar, existen muchas variedades (algoritmos) de analizadores, existe un analizador fonético que como su nombre lo dice busca coincidencias basadas en fonemas. Ejemplo: zapato y “sapato” coinciden fonéticamente.

Vamos a crear un indice basado en este tipo de analizadores con el SDK de Azure Search (.NET Core C#).

Azure Search

Este servicio de búsqueda que se ofrece dentro de Azure PaaS permite realizar indices (colecciones) de documentos (entidades/ objetos) para realizar búsquedas aplicando full-text search sobre las propiedades de nuestros documentos. La forma de tokenizar el contenido de las propiedades varia según el analizador. Es una buena alternativa para realizar búsquedas cuando se ocupa SQL Azure o algún otro motor de persistencia que no tiene búsquedas basadas en full-text search.

Internamente se “asume” que ocupa ElasticSearch y Lucene.

Los pasos para crear el servicio desde el portal de Azure aquí.

Índice

La definición de nuestro indice se basa en la definición de nuestra clase que vamos a indexar (o agregar a la colección) por medio de atributos indicamos con que criterios se debe generar el índice.

Nuestro índice se refiere a un catalogo de productos con 4 propiedades: id, name, description y country. La clase que lo define se ve así:

Los atributos que vemos sobre las propiedades establecen la definición del índice, que en principio debe coincidir con las entidades que vamos a indexar. Veamos a que se refiere cada uno de los atributos.

IsSearchable: Este atributo indica que la propiedad debe ser tratada para realizar búsquedas sobre full-texte search. Esto implica que el valor que contenga la propiedad se va a tokenizar aplicando un analizador. Entonces si la propiedad tuviera el valor “Blog de Azure” internamente lo tokeniza (separa) formando “Blog” y “Azure”. Esto permite aplicar mejores condiciones de búsqueda.

IsFilterable: Este atributo establece que la propiedad que lo implementa no se realizaran búsquedas por medio de full-text search. Es decir, sobre el ejemplo anterior, si yo busco con la condición “Blog” == “Blog de Azure” no va haber coincidencia, las búsquedas deben ser por frases completas/exactas.

IsSortable: El resultado de cada búsqueda queda ordenado por un valor que va de mayor a menor coincidencia (se genera por medio de TF/IDF), llamado score. Este atributo indica si la propiedad debe afectar el score de los resultados.

Analyzer: Este atributo nos permite indicar que analizador va aplicar a cada propiedad. Como podemos ver estamos aplicando 2 tipos de analizadores. AnalyzerName.AsString.EnLucene este analizador ya esta predefinido y se refiere a que va aplicar un analizador léxico de palabras en ingles de Lucene“PhoneticCustomnAnalyzer” se refiere al analizador fonético que vamos a aplicar, este no es un analizador ya predefinido, este es un analizador customizado que se declara en otro momento.

Key: Solo para definir cual es la llave única para nuestro indice (tiene que ser string), esto es importante pues se ocupa para hacer actualizaciones o eliminar un documento en la colección.

Analizador Fonético

Ahora veamos como crear nuestro analizador personalizado. Como ya habíamos comentado antes, un analizador internamente aplica distintos algoritmos al momento de tokenizar el texto, lo que debemos hacer en nuestro propio analizador es agregar un tipo de tokenizador (tokenizer) y filtros. Aquí la lista.

Un analizador léxico esta definido por:

A tokenizer: divide el texto de entrada en tokens, esto es separar todas la palabras de una frase y remueve palabras que no afectan las búsquedas (stopwords).

A token filters: al momento de generar los tokens se aplican filtros por ejemplo convertir todo los caracteres a lowercase.

Podemos definir cualquiera que se nos ocurra de acuerdo a los resultados que queremos lograr. Definimos un nombre en la propiedad Name y es el que podemos utilizar sobre el atributo Analyzer en la definición de nuestro indice como ya lo vimos.

Creando el Índice

Ocupando los objetos que expone la librería de Azure Search (nuget: Microsoft.Azure.Search) creamos el indice con todo lo que hicimos previamente.

Primero con el método CreateIndexDefinition creamos la definición de nuestro índice, esto incluye obtener la definición de nuestro analizador fonético. Debemos indicar el nombre del índice en la propiedad Name, que lo obtenemos desde la configuración del proyecto, debemos indicar las propiedades del índice con sus atributos en Fields y finalmente indicamos que debe crear un analizador en la propiedad Analyzers.

El método CreateIndexAndGetClient invoca los métodos necesarios para crear el índice. Requerimos primero el nombre del servicio y una key que obtenemos desde el portal de Azure. Validamos si existe previamente el índice, si no, lo creamos.

Una vez que se crea el índice ya no es posible modificar su definición. Para hacer eso hay que crear uno nuevo (o eliminar) y volver a cargar los datos.

Este método también genera el objeto cliente con el que realizaremos las operaciones de búsqueda.

Datos

Necesitamos datos para poder probar nuestro índice, tenemos un archivo json donde tenemos 1,000 objetos que corresponden a la misma estructura del objeto que define nuestro índice.

Estos datos son los que vamos a insertar de forma masiva o en batch. Esto lo permite también el API de Azure Search.

Lo que hacemos con el método CreateBatchData es crear grupos de documentos/entidades para insertar, esto es porque el tamaño máximo de documentos por batch es de 1,000. Aquí el detalle.

Después en el método UploadData creamos la carga de estos grupos en paralelo.

Aquí esta el repositorio del código para ver mas detalle.

Realizando Búsquedas

Aplicar un analizador fonético para nuestras búsquedas hace sentido cuando queremos encontrar resultados aun con faltas de ortografía o probablemente con omisión de letras. También existen mas alternativas para lograr el mismo resultado como ocupar el operador Fuzzy de Lucene o establecer sinónimos.

Dentro de los datos que cargamos tenemos dos propiedades que contienen el mismo valor Name y NamePhonetic, a la primera establecimos un simple analizador léxico natural del lenguaje y en la otra un analizador fonético. El valor que estamos agregando a estas propiedades son marcas de automóviles (Kia, Honda, Mitsubishi, Dodge, Mercedes-Benz, etc).

Vamos a realizar búsquedas y veremos como hace diferencia los analizadores que colocamos.

El método con el que realizamos las búsquedas es SearchPhrase, internamente tiene algunas condiciones para poder ver las diferencias de resultados. PrintResult solo imprime el resultado.

Hagamos el primer ejercicio con la búsqueda de “kia” y “qia” sobre la columna Name.

En el código de arriba invocamos el método SearchPhrase una vez con la frase “kia” y después con la frase “qia” para comparar los resultados. En los datos no existe ningún registro con el valor “qia”. Estos son los resultados:

Sin analizador fonético

Podemos ver que para la frase “qia” no encontró ningún resultado. Ahora hagamos la prueba apuntando hacia la propiedad que tiene el analizador fonético NamePhonetic.

Con analizador fonético

Podemos ver que ahora la frase “qia” si obtuvo el resultados. Esto es porque las frases “kia” y “qia” son iguales fonéticamente.

Los analizadores léxicos de lenguaje si dependen del idioma, es decir cambia entre ingles y español. Para el analizador fonético no en necesario indicar el idioma en el que esta el texto pues Azure Search utiliza un analizador “genérico”.

Como ya lo había dicho antes, esto se podría resolver también con el operador Fuzzy con Lucene. Este operador aplica un algoritmo de aproximación sobre las frases (basado en Damerau-Levenshtein Distance), esto se logra agregando “~” a la frase. Aquí el ejemplo.

Con Fuzzy

Esta vez se hizo con la frase “Jeep” y “Geep” sobre el propiedad Name, y podemos ver que se encontraron los mismos resultados, sin embargo, sobre la búsqueda de “Geep~” encontró un ultimo resultado que fue “Geo” con un score menor, que hace sentido.

Podemos beneficiarnos de ambos analizadores:

 

El código completo de lo que se hace lo pueden ver aqui.

Troubleshooting

Por alguna razón en .NET Core da problemas la versión de 11.* de Newtonsoft.Json al momento de indexar documentos. Hay que agregar la versión 10.* de Newtonsoft.Json y agregar también Microsoft.Rest.ClientRuntime.Azure.

ASP .NET Core Identity en Cosmos DB

La mejor forma de manejar la autorización y autenticación dentro de una aplicación web con ASP .NET Core es por medio de ASP.NET Core Identity. Es la evolución de lo que se conocía como Membership Provider en ASP .NET.

Entre otras mejoras Identity permite interactuar con múltiples protocolos de autorización y autenticación sobre HTTP.

Dentro del flujo de autorización es necesario interactuar con el sistema de persistencia que tenemos implementado en nuestra aplicación web que naturalmente estará basado en usuarios y roles. La implementacion mas común que veremos de Identity es ocupar una base SQL (SQL Server/Azure) con Entity Framework.

Haremos una implementación de este proveedor de identidad para que se integre con Cosmos DB.

Identity Core 2.0

Solo hagamos un breve repaso de los objetos que expone el proveedor (Microsoft.AspNet.Identity) que ocuparemos para nuestra implementación:

UserManager<TUser>: Esta clase principalmente tiene la responsabilidad de controlar todos los atributos del usuario contra el motor de persistencia, esta clase tiene una dependencia con IUserStore<TUser>. Que asume deberá ser inyectada en el constructor.

IUserStore<TUser>: Esta interfaz contiene todas las firmas con las operaciones básicas (para usuarios) que se deberán ejecutar sobre el proveedor de persistencia.

SignInManager<TUser>: Esta clase tiene la responsabilidad de controlar todo lo que tiene que ver con la autenticacion sobre cualquier protocolo, es la encargada de determinar si el usuario tiene los atributos para autenticarse de forma exitosa en la aplicación y también es la encargada de establecer la sesión sobre el protocolo que se determine (Cookies, Issued Tokens, JWT). Tiene dependencia con UserManager<TUser>.

RoleManager<TRole>: Cuando nuestra aplicación requiere roles, esta clase tiene la responsabilidad de controlar todos los atributos de los roles contra el motor de persistencia. Tiene dependencia con IRoleStore<TRole>.

La mayoría de estas clases e interfaces son genéricas pues permiten establecer una entidad POCO para definir las propiedades del usuario y rol.

Existen mas clases dentro del proveedor pero por ahora solo ocuparemos las mencionadas.

ASP .NET Identity Core – Cosmos DB

Integrando Cosmos DB

Lo primero que tenemos que hacer es implementar nuestra propia versión de IUserStore<TUser> y de IRoleStore<TRole>. Para esto tambien requerimos definir nuestra entidad que representa al usuario, que se veria algo asi:

En el código podemos ver que estamos heredando de IdentityUser<TKey> que nos permite tomar propiedades comunes para nuestra entidad como: Id, nombre de usuario, correo, email, contraseña, etc. No es obligatorio hacer esta herencia, podríamos omitirlo y nosotros establecer las propiedades que necesitemos. Unas propiedades si las necesitaremos de forma predefinida. Ya lo veremos

También tenemos una propiedad con el nombre de Tenan,  esta se ocupa en el caso donde queramos tener una partición en la colección de usuarios para la base de Cosmos DB. Por ahora esta en duro pues solo es para ejemplificar.

La implementacion de IUserStore<TUser> se podría referir a nuestro repositorio de usuarios dentro de nuestra aplicación, así es que lo que vamos a hacer es incluir la implementacion de esa interfaz en nuestra misma interfaz que define nuestro de repositorio de usuarios.

La definición del repositorio de usuario se podría ver así:

Estoy definiendo mi propia interfaz de IUserRepository que expone 4 métodos propios y adicional esta interfaz debe implementar IUserStore<User>. Adicional estoy agregando otras interfaces a implementar.

IUserPasswordStore<User>: Esta interfaz se requiere para definir como se obtendrá la propiedad que contiene la contraseña con hash. Es importante pues se ocupa a momento de autenticar por medio de contraseña al usuario.

IUserRoleStore<User>: Esta interfaz se requiere para controlar como se asocian los roles a los usuarios. Esto es porque en nuestra aplicación esta basada en usuarios y roles. Si no requerimos roles se puede omitir.

La clase de la implementacion de IUserRepository queda bastante extensa pero se veria algo asi:

Hasta este punto no hemos escrito aun nada de código que interactúe con Cosmos DB. En la clase UserRepository podemos ver que hereda de BaseRepositoryCosmos<User> que es donde se hace la interacción con Cosmos DB. La clase base  BaseRepositoryCosmos<User> encapsula la lógica necesaria para crear el cliente, crear la base y la colección basada en la entidad genérica que recibe; en este caso nuestra entidad User, internamente la clase base define el nombre de la entidad como el nombre de la colección para Cosmos DB.

Esta es la definición de BaseRepositoryCosmos<User>:

Para mas detalle de esta clase base para el repositorio lo explico aquí.

Como ya lo explicamos, esto nos permite interactuar con la base de Cosmos DB. Solo nos queda implementar cada unos de los métodos que requieren las interfaces que ocuparemos.

Aqui la implementacion de algunos metodos:

Los métodos FindByIdAsync FindByNameAsync como su nombre lo dice permiten buscar al usuario por id o por username, estos métodos los ocupa bastante el proveedor de Identity, pues hay varias operaciones donde tiene que ir a buscar a un usuario en especifico.

En el código de arriba podemos ver los métodos para crear y actualizar, ambos internamente ocupan un metodo de la clase base que es Upsert, ese método ejecuta la operación de UpsertDocumentAsync.

En este enlace de la solución se puede ver la implementación completa de cada método. Solución aquí.

Una vez que tenemos la implementación completa en nuestros repositorios hay que indicarle al proveedor de donde debe tomar lo que requiere.

Configurando proyecto Web (ASP .NET Core)

El proveedor de Identity se configura y se inicializa en nuestro proyecto web, aquí es donde establecemos todas las políticas y protocolos con los que el sitio web autoriza el acceso.

Previo debemos indicar a los objetos de UserManager<User> SignInManager<User> que deben de ocupar nuestro repositorio de usuario pues ahí esta la lógica para consumir nuestra colección de Cosmos DB. Para eso tenemos que hacer nuestra propia versión heredando de cada clase.

Se veria algo asi:

Aquí tenemos la clase ApplicationUserManager  que hereda de UserManager<User> y vemos que la definición del constructor requiere de muchas dependencias. La que nos interesa es la primera, ahí es donde le indicamos que debe de utilizar nuestro repositorio definido sobre la interfaz IUserRepository.

Las demás dependencias las dejamos igual, internamente el motor de inyección de dependencias se encargara de resolverlas.

Ahora vemos la clase ApplicationSignInManager que hereda de SignInManager<User> y realizamos lo mismo en el constructor. Esta clase tiene dependencia con UserManager<User> así es que le indicamos que la clase que debe ocupar es nuestra definición de ApplicationUserManager.

Con esto logramos indicar al proveedor que debe de ocupar nuestro repositorio que se conecta a Cosmos DB.

Ahora debemos de indicar al motor de inyección de dependencias de ASP .NET Core el registro y configuración de todas nuestras dependencias. Esto lo hacemos en el archivo Startup.cs como naturalmente se hace.

Primero indicamos que nuestro método sera por medio de Cookies.

Hacemos el registro de todas nuestras dependencias. También indicamos como resuelve la configuración para Cosmos DB con al clase CosmosConfiguration.

Finalmente le indicamos al proveedor Identity las dependencias que debe de ocupar.

Controlador y Login

Ahora ya podemos ocupar el proveedor de forma natural sobre nuestros controladores para determinar la autenticación y autorización. Aquí tenemos el controlador que ocupamos en la vista de login.

Podemos ver como en el constructor del controlador inyectamos la dependencias de UserManager<User>SignInManager<User>. En la acción DoLogin es donde autenticamos al usuario obteniendo sus credenciales (nombre de usuario y contraseña), internamente ocupamos el método PasswordSignInAsync que recibe el nombre de usuario y contraseña.

Internamente el método PasswordSignInAsync se encarga de hacer las validaciones necesarias para determinar si autentica al usuario ocupando nuestro repositorio. Por eso es importante hacer la implementación completa de IUserStore<User> pues de forma indistinta ocupa cada unos de los métodos.

En el caso de que queramos restringir un recurso basado en roles podemos ocupar el atributo Authorize:

Internamente el proveedor de Identity ocupa nuestro repositorio para determinar si el usuario tiene el rol de “Admin”. Para esto debemos hacer la implementación correcta de IUserRoleStore<User>.

De forma básica esto es lo que se tendría que hacer para integrar el proveedor con Cosmos DB. Aquí dejo una vista de la solución en visual studio con todo lo que contiene. Y el enlace a la solucion en github.

 

Migrando desde SQL Server hacia Cosmos DB (Migration Tool)

Existe una herramienta desarrollada por el equipo de Microsoft Azure para realizar migracion de datos hacia Azure Cosmos DB. Esta herramienta permite importar datos desde varias fuentes como: JSON files, CSV files, SQL, MongoDB, Azure Table storage, Amazon DynamoDB, e incluso Azure Cosmos DB SQL API.

Esta vez realizaremos la importación desde SQL Server y validaremos como establece las relaciones de las tablas o que opciones tenemos para controlar la forma en la que importamos los datos.

Primero vamos revisar nuestros objetos/tablas (esquema BD) en SQL Server, después ejecutaremos la herramienta y veremos como resuelve cada objeto, y como resuelve los campos con su tipo de dato.

Migration Tool

 

SQL Server (source)

Como fuente de datos vamos a usar la tabla DimProduct de la clasica BD de AdventureWorksDW. La tabla contiene:

DimProduct – AdeventureWorksDW

Como podemos ver en la imagen tenemos suficientes tipos de datos para probar. Tenemos incluso el tipo varbinary.

Nuestra llave primaria en esta caso esta sobre el campo ProductKey esto debemos tenerlo en cuenta al momento de la migración. La herramienta nos permite establecerlo.

Cosmos DB (target)

Para el destino debemos habilitar el servicio de Cosmos DB, lo podemos hacer hacia el emulador de Cosmos DB (lo puede descargar aqui) o podemos crear el servicio desde el portal de Azure.

Si tenemos el emulador se debería de abrir una pagina en localhost que se ve asi:

Azure Cosmos DB Emulator

Como se puede ver en la imagen no existe ninguna DB y ninguna colección. De esta parte solo debemos de validar que podamos ejecutar el emulador correctamente y recuperar los datos para la conexión.

En el caso del emulador los parámetros de conexión son:

Estos parámetros son los predefinidos por el emulador. Podemos cambiarlos ejecutando el emulador desde la consola.

Migration Tool

La herramienta de migración la podemos descargar de aquí.

Ejecutamos la herramienta en modo UI (dtui.exe) y se abrirá la siguiente ventana:

Migration Tool – Source

Y lo primero que debemos indicar es la fuente de datos. En la imagen podemos ver todas las fuentes de datos que podemos ocupar. En este caso vamos a ocupar SQL para conectarnos a la base de datos de AdventureWorksDW.

Al momento de seleccionar la fuente como SQL debemos de configurar la conexión hacia el servidor y base de datos con una cadena de conexión, y adicional debemos definir un query con el que obtiene los registros para migrar. Esto facilita bastante porque incluso podríamos hacer un join entre distintas tablas para formar una sola entidad/documento.

Migration Tool – Source SQL

Nos permite escribir directamente un query o seleccionar un archivo que lo contenga, este es mi query:

Cada una de las columnas que incluye la sentencia se creara como propiedad dentro de la entidad/documento dentro de Cosmos DB con el mismo nombre.

Le damos siguiente. Y ahora nos solicita configurar el destino, que en nuestro caso sera Cosmos DB. Debemos indicar los parámetros de conexión, y los datos de la base de datos y nombre de la colección. En caso de que no exista alguno lo creara.

Migration Tool – Target

Veamos cada uno de los parámetros que estamos estableciendo:

Export to: Aquí nos permite establecer la importación de forma secuencial definiendo el valor de partición.

Connection String: Definimos los parámetros de conexión hacia Cosmos DB, en nuestro caso estamos ocupando el emulador local. Y la cadena queda separada por “;”.

Aquí también estoy definiendo el nombre de la base de datos como: migrationdb

Collection: El nombre de la colección, si no existe la crea.

Partition Key: En este caso vamos manejar las particiones de forma ilimitada (unlimited auto-scaling partitioning) por lo tanto debemos definir nuestra llave de partición. Establecemos la llave de partición la linea del producto que se encuentra en la propiedad ProductLine. La sintaxis establece que se debe indicar con “/” esto es porque podríamos seleccionar una propiedad que se encuentra a mayor profundidad en nuestro objeto. Ej. “/prop1/deep1/deep2”

Collection Throughput: Para poder implementar el auto-particionado o tener nuestro indice en forma ilimitada lo mínimo que podemos establecer como RU (request unit) son 1,000 o mas.

Id Field: Aquí definimos la propiedad que funcionara como id único, en nuestro caso ocupamos la propiedad ProductKey.

Le damos siguiente. Podemos establecer un archivo de log.

Finalmente ejecutamos la importación.

Migration Tool – Results

Como podemos ver la imagen finalizo correctamente la importacion de los 606 registros. Ahora vamos a ver que hay en el explorador del emulador:

Azure Cosmos DB Emulator

Vemos que hizo la creación de la base de datos y colección. También podemos ver que hizo la inserción de los registros correctamente.

Ahora vamos a ver como resolvió el tipo varbinary:

Varbinary field

El campo LargePhoto es varbinary y vemos que lo resolvió como un arreglo de enteros (no es buena idea guardar binarios sobre un indice en Comos DB). Vemos que la codificación de caracteres también lo resolvió de forma correcta:

Encoding

Es de gran utilidad esta herramienta incluso cuando cambiamos la forma de particionar del indice y tenemos que migrar los datos.

Troubleshooting: Algunas veces manda un error la conexión hacia el emulador al momento de ejecutar el ultimo. Nada mas reinicia el emulador.

Repositorio con Azure Cosmos DB

Un repositorio debe tener la responsabilidad de comunicarse con el proveedor de persistencia, obtener los datos y mapearlo hacia las entidades de dominio. Esto es como lo describe Martin F. en: https://martinfowler.com/eaaCatalog/repository.html

Haremos una implementacion sobre una solucion en .Net (C#) consumiendo como proveedor de persistencia Azure Cosmos DB.

Interacción del Repositorio

Primero entendamos en que parte de nuestro diseño de solución encaja la implementación de un componente que tenga esta responsabilidad de Repositorio.

Interacción del Repositorio

Como se ve en la imagen el Repositorio se comunica con el provedor de persistencia que podria ser cualquier cosa: SQL Server, MySQL, Mongo DB, Cosmos DB, etc.

Sin depender el proveedor de persistencia el repositorio debe poder ejecutar tareas como: insercion, busquedas, eliminar, o ejecutar acciones especificas de negocio. La forma en la que se comunican otros componentes con el Repositorio es comúnmente por medio de las entidades de dominio u objetos de transporte/transferencia (DTO).

Implementación del Repositorio

Vamos a realizar un repositorio para persistir una lista de ToDo’s. Deberá permitir crear nuevos items, consultar y borrar.

La implementación del repositorio esta representada en este diagrama UML:

Repository Diagram

De acuerdo al diagrama que tenemos arriba tenemos:

BaseRepository<T>: Esta es una clase abstracta que define 4 métodos abstractos para las operaciones básicas.

BaseRepositoryCosmos<T>: Esta clase hereda de BaseRepository e implementa los métodos heredados. En esta clase colocamos toda la lógica para ocupar el API de CosmosDB. Esta clase también es abstracta, pues finalmente requiere la implementación del repositorio de negocio o que contiene la lógica de acuerdo a lo que se esta desarrollando.

TodoRepository: Esta clase hereda de BaseRepositoryCosmos<T> e internamente tiene acceso a los métodos con las operaciones básicas. Adicional tendría acceso al cliente de CosmosDB por si requiere hacer alguna otra acción o tarea.

TodoItem: Nuestra entidad de dominio (POCO).

Código

Ahora veamos el código de cada una de las partes ya mencionadas:

Este es le repositorio base que nos permite hacer cuantas implementaciones queramos, en esta ocasión es para CosmosDB.

Aqui se ve la implementacion de cada una de las acciones que de forma basica requiere un repositorio. Adicional a los metodos que se ven se require inicializar el nombre de la base de dtos o el id de la base de documentos donde se va a guardar cada entidad/documento. Para eso el SDK de CosmosDB expone unos metodos para hacelo.

Primero debemos crear un DocumentClient con parametros de configuracion que los obtenemos desde el portal de Azure:

En este caso estamos inicializando o instanciando DocumentClient desde el constructor de la clase BaseRepositoryCosmos, los parámetros de configuración los tomamos del config. Después debemos crear la “base de datos” y después debemos crear la colección (la colección podemos entender que es como un tabla). Para estas dos acciones tenemos dos metodos this.CreateDatabase() y this.CreateCollection().

El nombre de la base de datos se toma desde el constructor. Y el nombre de la colección se toma del nombre del tipo de dato genérico que espera el repositorio genérico.

Adicional, en la definición de la clase BaseRepositoryCosmos de tenemos un método privado con el nombre GetIdPropertyValue. Este método nos permite buscar una propiedad de una clase POCO con el nombre de Id, esto es porque naturalmente en la serializacion a JSON del objeto para persistirlo con CosmosDB utiliza un propiedad Id como identificador único. Esto es por si no queremos ocupar atributos como decoradores en nuestra classe POCO.

El método busca la una propiedad que tenga el atributo JsonPropertyAttribute con el name igual a “id”, si no lo encuentra, entonces busca una propiedad con el nombre Id. Una vez encontrada la propiedad trata de obtener su valor. Este método se ocupa en la acción de Delete.

Todas las clases anteriores no están optimizadas o incluyen mejoras con para la inicializacion de objectos con Lazy, ni tampoco para las repuestas asíncronas. Son ejemplos.

Finalmente el código de la implementacion del repositorio TodoRepository:

Aquí tenemos la implementacion del repositorio que nos permitirá guardar nuestra entidad TodoItem para cumplir con el objetivo de nuestra solución.

Esta es la clase TodoItem:

Como cliente para consumir el repositorio ocuparemos un proyecto de consola. Donde muestra las operaciones básicas: insertamos una entidad, después hacemos un update y finalmente obtenemos el contenido de la colección.

Para probar como se ven los documentos/entidades sobre la coleccion puedes bajar el Azure Cosmos DB Emulator en: https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator

Si quieres ver la solución completa aquí el repositorio: https://github.com/arhandres/cosmosdb-repository