Comenzando a desarrollar microservicios con Service Fabric

Antes de comenzar a desarrollar microservicios debemos tener instalado el SDK de Service Fabric, podemos encontrar los pre requisitos en el siguiente link.

Para comenzar vamos a iniciar con un servicio stateless, para lo cual vamos a iniciar Visual Studio en modo Administración dado que es requerido para el host del servicio.

Posteriormente vamos a iniciar un nuevo proyecto del tipo Service Fabric Application.

image

Posteriormente vamos a seleccionar la plantilla de proyecto Stateless Service

image

Dentro de la solución vamos a encontrar dos proyectos.

image

El primer proyecto Applcation no contiene código de la implementación, el mismo hace referencia al proyecto del Servicio y contiene distintos archivos de configuración del proyecto.

  • Perfiles de publicación: se usa para administrar las herramientas preferidas para los distintos entornos.
  • Scripts: incluye un script de PowerShell para implementar o actualizar aplicaciones. Visual Studio usa este script en segundo plano y se puede invocar directamente desde la línea de comandos.
  • Definición de aplicación: incluye el manifiesto de la aplicación en ApplicationPackageRoot y los archivos de parámetros de la aplicación asociados a ApplicationParameters que definen la aplicación y permiten configurarla específicamente para un entorno determinado.

Dentro del proyecto del servicio encontraremos 3 archivos CS.

Program

El servicio se ejecuta en un proceso de host de servicio (un archivo ejecutable que ejecuta el código del servicio). El punto de entrada es una aplicación de consola en donde se hace el registro del nombre y el tipo del servicio.

image 

ServiceEventSource

Esta clase nos facilita el registro de eventos de la aplicación para el ETW Event Tracking Windows. Dentro de esta clase encontraremos una serie de métodos que nos permiten publicar información sobre el estado de la aplicación en cada paso.

image

Encontraremos algunos métodos que poseen el atributo NonEvent, el cual indica que no genera un evento. Sin embargo, estos métodos utilizan otros que generan eventos registrándose finalmente el evento.

finalmente encontraremos el código que implementa el servicio propiamente dicho. Stateless1, debido al nombre que le di al servicio.

image

En Service Fabric, un servicio puede ejecutar cualquier lógica de negocios. La API de servicios proporciona dos puntos de entrada para el código:

  • El método “RunAsync” el cual es un punto de entrada donde se puede realizar la ejecución de cualquier lógica de negocio que queramos. En general se utiliza para actividades del tipo long running. Por ejemplo en este punto de entrada podríamos realizar la lectura de items dentro de una cola que deban ser procesados por nuestro servicio.
  • Un punto de entrada de comunicación el cual se puede conectar con la API web de ASP.NET, por ejemplo. Aquí es donde podemos recibir los request hacia nuestro servicio.

Cuando iniciamos un proyecto utilizando la plantilla que seleccionamos para este ejemplo, el código que encontraremos dentro del método RunAsync será el siguiente:

image

En este ejemplo solo se posee un contador, el cual es incrementado en cada paso y se visualiza el resultado de cada iteración mediante el EventSource.

Si ejecutamos el código hasta este momento podremos visualizar lo siguiente:

image

En el visor de eventos de diagnóstico podremos visualizar los distintos eventos que fueron impresos en las iteraciones. Si entramos al detalle de alguno de ellos podremos visualizar lo siguiente:

image

En mi caso particular el nodo que está respondiendo al procesamiento es el _Node_0. Ahora bien, si abrimos la consola de cluster podremos visualizar el estado de cada uno de los nodos de procesamiento y cuales están asignados a nuestro servicio.

image

El detalle de mis nodos indica cuál contiene la implementación del servicio que acabamos de realizar.

image

image

Ahora bien, si queremos que el servicio responda a solicitudes podemos partir de la implementación que tenemos hasta el momento y agregar el paquete Nuget de OWIN Self Host.

Dado que el código de aplicación de Web API se hospeda en su propio proceso, ¿cómo se conecta a un servidor web?. OWIN es simplemente un contrato entre las aplicaciones web .NET y servidores web. Tradicionalmente, cuando se usa ASP.NET (hasta MVC 5), la aplicación web se acoplaba con IIS a través de System.Web. Sin embargo, Web API implementa OWIN, lo que le permite escribir una aplicación web que se separa del servidor web que la hospeda. Por este motivo, puede usar un servidor web OWIN selfhost que puede iniciar en su propio proceso. Esto encaja perfectamente con el modelo de hospedaje de Service Fabric.

image

Una vez agregadas las dependencias podemos continuar con la estructura de proyecto de Web Api.

image

Posteriormente crearemos un Controller, en mi caso SampleController.

image

Luego debemos agregar una clase de inicio en la raíz del proyecto para registrar el enrutamiento, los formateadores y cualquier otro programa de configuración. Aquí también es donde el Web API se conecta al host.

image

Para realizar el Host de Web API utilizaremos Katana como host de OWIN.

La API de Reliable Services ofrece un punto de entrada de comunicación en el que puede conectar pilas de comunicación para permitir a los usuarios y a los clientes conectarse al servicio.

image

El servidor web (y cualquier otra estructura de comunicación que utilice en el futuro, como WebSockets) debe utilizar la interfaz ICommunicationListener para integrarse correctamente en el sistema.

Por tal motivo en primero lugar debemos crear una clase denominada OwinCommunicationListener que implemente ICommunicationListener:

image

La interfaz de ICommunicationListener contiene tres métodos para administrar un agente de escucha de comunicación para el servicio:

  • OpenAsync. Empezar a escuchar las solicitudes.
  • CloseAsync. Dejar de escuchar las solicitudes, finalizar las solicitudes en curso y cerrar correctamente.
  • Abort. Cancelar todo y detener inmediatamente.

Primero agregaremos los miembros el agente requiere para funcionar. Estos se inicializarán a través del constructor y se usarán más adelante cuando configure la dirección URL.

image

image

image

Antes de obtener un puerto para el servidor web, es importante comprender que Service Fabric proporciona una capa de aplicación que actúa como un búfer entre la aplicación y el sistema operativo en el que se ejecuta. Como tal, Service Fabric proporciona una manera de configurar extremos para los servicios. Service Fabric garantiza que los puntos de conexión están disponibles para que el servicio los utilice. De este modo, no tiene que configurarlos por su cuenta en el entorno de sistema operativo. Puede hospedar fácilmente su aplicación de Service Fabric en diferentes entornos sin tener que realizar cambios en la aplicación. (Por ejemplo, puede hospedar la misma aplicación en Azure o en su propio centro de datos).

Para realizar este paso abriremos el archivo ServiceManifest.xml

image

Este paso es importante porque el proceso de host de servicio se ejecuta con credenciales restringidas (servicio de red en Windows). Esto significa que el servicio no tendrá acceso para configurar un punto de conexión HTTP por sí mismo. Mediante la configuración del punto de conexión, Service Fabric sabe configurar la lista de control de acceso (ACL) adecuada para la dirección URL que el servicio escuchará. Service Fabric también proporciona un lugar estándar para configurar puntos de conexión.

Ahora si, podemos continuar con la implementación de nuestra clase, en primera medida implementaremos el método OpenAsync

image

El primer paso es obtener los elementos para construir la URL donde se hospedará nuestro servicio, para la cuál debemos acceder a los parámetros anteriormente configurados.

image

La dirección URL será diferente dependiendo de si se utiliza el agente de escucha en un servicio sin o con estado. Para los servicios con estado, el agente de escucha debe crear una dirección única para cada réplica de servicio con estado en la que el agente realiza la escucha. Para los servicios sin estado, la dirección puede ser mucho más sencilla.

image

La implementación de OpenAsync es una de las razones más importantes por las que el servidor web se implementa como una interfaz ICommunicationListener en lugar de simplemente abrirse directamente desde RunAsync() en el servicio. El valor devuelto de OpenAsync es la dirección que está escuchando el servidor web. Cuando se devuelve esta dirección al sistema, registra la dirección con el servicio. Service Fabric proporciona una API que permite a los clientes y otros servicios pedir esta dirección por nombre de servicio. Esto es importante porque la dirección del servicio no es estática. Los servicios se mueven en el clúster para fines de disponibilidad y equilibrio de recursos. Este es el mecanismo que permite a los clientes resolver la dirección de escucha de un servicio.

OpenAsync inicia el servidor web y devuelve la dirección en que está escuchando. Tenga en cuenta que realiza escuchas en ‘http://+’, pero antes de que OpenAsync devuelva la dirección, el “+” se sustituye por la dirección IP o FQDN del nodo en el que está actualmente. La dirección que este método devuelve es la que se registra con el sistema. También es lo que los clientes y otros servicios ven cuando solicitan la dirección de un servicio. Para que los clientes se conecten correctamente a ella, necesitan un IP o FQDN real en la dirección.

image

Si hemos utilizado la plantilla de Stateless debemos realizar algunos cambios en nuestra clase de Event Source

image

image

Posteriormente continuamos implementando los métodos de OwinCommunicationListener

image

image

Ahora, en nuestra clase de servicio, debemos implementar el método CreateServiceInstanceListeners y eliminamos el método RunAsync.

image

image

Para descargarse el código del ejemplo puede ir al link de descarga.

Introducción a Service Fabric

Service Fabric es una plataforma de sistemas distribuidos que facilita el despligue, la implementación y la administración de microservicios escalables y confiables. La principal motivación que existe en utilizar microservicios es la posibilidad de centrarce en la implementación de la lógica de los mismos, en vez de gastar tiempo en resolver problemas típicos de la infraestructura necesaria para este tipo de escenarios.

Service Fabric permite compilar y administrar aplicaciones escalables y confiables compuestas por microservicios que se ejecutan con una densidad muy alta en un grupo compartido de máquinas denominado Clúster de Service Fabric.

Dentro de las principales características de los microservicios encontraremos:

  • La capacidad de escalar funcionalidades independientes dentro de un sistema.
  • Los equipos de desarrollo pueden ser más ágiles e independientes en la implementación de cambios.
  • Desarrollar aplicaciones altamente escalables y de recuperación automática.
  • Desarrollar aplicaciones compuestas por microservicios con el modelo de programación de Service Fabric u hospedar otro tipo de aplicación ASP.NET Core 1, Node.js, etc.
  • Desarrollar microservicios con o sin estado, altamente confiables.
  • Simplificar el diseño de sistemas, mediante el uso de microservicios con estado en vez de utilizar caché o colas.
  • Permitir ejecutar el código sin ningún tipo de alteración, ya se en Azure o en ambientes locales con Windows o Linux.

Cluster de Service Fabric

Se pueden crear clusters de Service Fabric en muchos entornos diferentes, tanto en Azure como en un ambiente on-premise ya sea Windows o Linux. El entorno de desarrollo es idéntico al entorno de producción sin que intervenga ningún tipo de emulador.

service-fabric-overview 

Comparación de enfoques de desarrollo

monolithic-vs-micro

  1. Las aplicaciones monolíticas contienen funciones específicas y normalmente se dividen en capas funcionales, como Web, negocios y datos.
  2. Para escalar aplicaciones monolíticas es necesario duplicarlas en varios servidores, máquinas virtuales o contenedores.
  3. Las aplicaciones de microservicios separan las funciones en servicios más pequeños independientes.
  4. Este enfoque se escala horizontalmente mediante la implementación de cada servicio de manera independiente, con la creación de instancias de estos servicios en servidores, máquinas virtuales y contenedores.

Manejo de estado entre tipo de aplicaciones

statemonolithic-vs-micro

En el modelo monolítico en general se almacena el estado en una única base de datos.

En el modelo de microservicios se encuentran varios de ellos interconectados, alguno con estado y otros sin. Por lo general, el manejo de estado y el medio de persistencia solo tiene como ámbito el microservicio que lo consume. En este escenario hay una mayor diversidad de tecnologías.

Replica geográfica de SQL Database Azure

Uno de los principales factores para los sistemas de misión crítica y de alta disponibilidad para asegurar la continuidad operativa es la disponibilidad de la base de datos.

Si bien, en forma interna, Azure realiza distintas instancias de cada base de datos que instanciamos de SQL Database, dichas copias existen para asegurar la operación por parte de Azure.

Con la replica geográfica permite configurar hasta cuatro bases de datos secundarias legibles en las mismas ubicaciones de centros de datos o en otras (regiones). Las bases de datos secundarias están disponibles en caso de una interrupción del centro de datos o de imposibilidad para conectarse a la base de datos principal.

Si, por cualquier motivo, se produce un error en la base de datos principal o, simplemente, debe desconectarse, puede conmutar a cualquiera de las bases secundarias. Cuando se activa la conmutación, las demás bases de datos secundarias se vinculan automáticamente a la nueva base de datos principal.

La Replicación geográfica implementa un mecanismo para proporcionar redundancia de base de datos en la misma región de Microsoft Azure o en distintas regiones. Esta funcionalidad replica de forma asincrónica las transacciones confirmadas desde una base de datos en hasta cuatro copias de la base de datos en servidores diferentes.

Cuando se configura la replicación geográfica, se crea una base de datos secundaria en el servidor especificado y la base de datos original se convierte en la principal. La base de datos principal replica de forma asincrónica las transacciones confirmadas en cada una de las bases de datos secundarias. Aunque, en un momento dado, la base de datos secundaria puede estar algo retrasada respecto a la principal, se garantiza que los datos secundarios guarden siempre coherencia transaccional con los confirmados en la base de datos principal.

Una de las principales ventajas de la replicación geográfica es que ofrece una solución de recuperación ante desastres en el nivel de base de datos con un tiempo de recuperación muy reducido.

La redundancia entre regiones permite que las aplicaciones se recuperen de la pérdida permanente de todo un centro de datos, o de partes de él, causada por desastres naturales, errores humanos catastróficos o actos malintencionados. En la siguiente ilustración, se muestra un ejemplo de replicación geográfica configurada en una base de datos Premium con una principal en la región centro-norte de EE. UU. y una secundaria en la región centro-sur de EE. UU.

geo-replication-relationship

Una muy buena ventaja de la utilización de bases de datos secundarios es la posibilidad de reducir la carga de la aplicación para los procesos que solo requieren lectura y que muchas veces alteran la performance, como la generación de reportes. Si la replica se la crea con este fin se la puede crear en la misma región que la principal, pero en dicho caso no sería útil como DRP.

Debido a la elevada latencia de las redes de área extensa, la copia usa un mecanismo de replicación asincrónica. Esto hace inevitable cierta pérdida de datos si se produce un error. Sin embargo, es posible que algunas aplicaciones requieran que no se pierdan datos. Para proteger estas actualizaciones críticas, podemos llamar al stored procedure sp_wait_for_database_copy_sync inmediatamente después de confirmar la transacción. La llamada a sp_wait_for_database_copy_sync bloquea el subproceso de llamada hasta que se replica la última transacción confirmada en la base de datos secundaria. El procedimiento esperará hasta que la base de datos secundaria confirme todas las transacciones en cola.

Procesamiento del Event hub utilizando Stream Analytics

En un post anterior mostré como procesar registros del event hub desde una aplicación en C#. Ahora bien, muchas veces solo necesitamos que los eventos dentro del hub sean almacenados en algún medio que nos permita realizar consultas, reportes, o alimenten a servicios que serán consumidos por otros sistemas. Si este es nuestro caso, entonces no es necesario realizar una aplicación que haga este procesamiento, en su lugar podemos utilizar las funciones de Stream Analytics.

Para el siguiente ejemplo vamos a asumir la siguiente arquitectura

4

En esta arquitectura el componente de Stream Analytics es el encargado de procesar los registros dentro del Event Hub y distruibuir la información hacia SQL Database y Document DB. Posteriormente existirán otros componentes dentro de la solución, como Web Apps, Worker Roles, etc. que consuman la información almacenada dentro de los repositorios.

Para este ejemplo vamos a asumir la existencia de un Event Hub a utilizar y una base de datos de SQL Database donde almacenaremos los registros.

La base de datos deberá tener una tabla con el siguiente formato. Por supuesto el formato de esta tabla de es ejemplo y los tipos de datos seleccionados no son los ideales, sin embargo el valor para un ejemplo es exactamente el mismo.

image

Primero debemos crear una instancia de Stream Analytics.

image

En nuestro caso le hemos dato el nombre de IoTDemoHuerto.

image

Una vez que se encuentra creado, vamos a configurar la entrada de datos con el Event hub que ya hemos creado anteriormente en Posts anteriores.

image

image

image

image

image

image

Una vez que hemos configurado la entrada, podemos visualizar los datos que llegan desde el Event Hub.

image

Seleccionamos la fecha de consulta

image

Descarga del archivo generado

image

En este caso es un archivo JSON:

image

Una vez que hemos configurado la fuente de datos de entrada, configuramos la salida hacia la base de datos de SQL Database.

image

image

image

image

Una vez ya especificada la entrada y la salida, procedemos a realizar la consulta de conversión de datos.

image

La consulta nos queda de esta forma:

image

Dentro de la consulta debemos visualizar las siguientes sentencias

SELECT En esta sección se especifican los valores que deben adquirir cada uno de los campos de la fuente de salida en función de los valores de entrada. Existen distintas funciones que se pueden utilizar.

INTO Aquí se especifica la fuente de datos en donde se debe mapear los campos especificados en la sección anterior.

FROM Aquí se configura la fuente de entrada, la cual también determina los campos que se pueden utilizar dentro del mapeo realizado en la sección SELECT. La palabra reservada TIMESTAMP BY va a especificar cual es la fecha de ese registro de datos desde el punto de vista de la aplicación que generó dicho evento, y no desde el punto de vista del momento en que es procesado por el componente de Stream Analytics. En este ejemplo podemos visualizar que dentro del from se indica que en el campo tt de la fuente de entrada se encuentra el time stamp del sistema origen, sin embargo, en la salida dentro del campo tt se ingresa el time stamp de procesamiento.

GROUP BY Esta sección nos sirve para agrupar los eventos de la fuente de entrada y que a la consulta ingrese un registro por cada grupo. Adicional a esto podemos visualizar la función TUMBLINGWINDOW en donde se encuentra configurada cada 10 segundos. Esta función nos permite generar una ventana de tiempo, la cual es fundamental para procesar los eventos de la entrada. En este ejemplo, se entiende que 10 segundos es muy poco tiempo para que los datos de entrada cambien sustancialmente y en ese sentido se realiza un promedio de los valores obtenidos durante esa ventana de tiempo. Incluso si estuviera configurado cada 60 segundos sería un tiempo bajo.

Enviado datos desde RPi hacia Azure Event Hub

Una de las principales decisiones que debemos tomar cuando desarrollamos sobre plataformas de IoT, es como poder envíar datos desde nuestra plataforma hacia un servicio que me permita procesar y disponer de la información de manera correcta.

Generalmente muchas de estas soluciones se basan en la lectura de determinados sensores y la toma de acción a partir de los valores que los mismos adquieren. Para poder capturar y disponer fuera del dispositivo de dichos valores, debemos contar con un servicio que sea escalable y con alta disponibilidad.

Para poder recibir los eventos de las lecturas de los sensores, podemos disponer del Event Hub de Azure. Este componente de plataforma como servicio fue diseñado específicamente con este fin.

Lo primero que debemos realizar es configurarlo en Azure.

image

Una vez especificado el nombre que deseamos dar y la URI del servicio, seleccionamos crear.

image

Ya que tenemos el servicio creado, dentro del dispositivo RPi podamos utilizar Node.JS para generar un proceso que envíe los datos de nuestros sensores al Event Hub.

Dentro de nuestra solución debemos agregar el paquete npm “eventhubs-js”.

image

image

Posteriormente podemos utilizar la librería desde nuestro código para poder invocar al servicio y enviar los datos al Event Hub.

image

Aquí les comparto el código de la solución Node.JS.

Patrones de Azure Service Bus y diferencias

Dentro del Service Bus de Azure hay varios patrones de mensajería que podemos utilizar. Cada una de estas modalidades resuelven distintos problemas y necesidades de conexión.

Descarga de código de ejemplo.

image

Colas

Productores: Múltiples productores pueden generar mensajes, aunque en la mayoría de los casos es uno solo.

Consumidores: Múltiples consumidores pueden leer mensajes de la cola, pero el mensaje obtenido por un consumidor no será entregado a otro, siendo único cada mensaje existente en la cola. Si un mensaje es devuelto a la cola por un consumidor, por algún caso de error, el mismo podrá ser procesado por otro consumidor o por él mismo.

SB - Queue

Si dos consumidores realizan una lectura al mismo tiempo el mensaje que recibirá el primer consumidor es distinto al mensaje que recibirá el segundo.

Dentro de la configuración para colas encontraremos las siguientes opciones:

image

Dentro de estas opciones encontraremos dos puntos bastante importantes, uno de ellos es la duración de un item en la cola, la cual determina el tiempo que puede estar no disponible un consumidor, sin tener riesgos de que perdamos el mensaje. Otra de las opciones importantes es la posibilidad de enviar los items en error a una cola de errores, sin la necesidad de que creemos esta cola e implementemos dicha lógica.

Código del productor

image

Código del consumidor

image

Tópicos

Productores: Múltiples productores pueden generar mensajes, tanto de un tipo distinto cada uno como de tópicos similares.

Consumidores: Pueden existir múltiples suscriptores para cada tópico. Lo interesante de este caso es que los mensajes pueden ser ruteados en función del contenido del mensaje.

SB - topics

Código del productor

image

image

image

Código del consumidor

image

Centro de eventos

Productores: En este escenario se pueden tener múltiples productores, generalmente diseñado para que desde algún tipo de dispositivo se envíen eventos de sensores o estado de algún dispositivo.

Consumidores: Los consumidores pueden ser tantos en paralelo como particiones hayamos configurado. Cada una de las particiones tiene un índice individual para indicar el número de mensaje que cada consumidor ha procesado hasta el momento.

SB - EH

Código del productor

image

Código del consumidor

image

image

image

image