Azure Notifier, un sistema de notificación basado en Logic Apps y Azure Functions

Hasta hace unas semanas, un compañero interno de Microsoft había construido una aplicación que permitía recibir por correo electrónico las novedades relacionadas con Azure de las principales fuentes de información: el blog de Azure, las actualizaciones de servicio y el contenido de Channel 9. La aplicación era un proyecto personal y tras irse de la compañía el servicio desapareció.

Debido al ritmo de evolución de los servicios de Azure, una utilidad como esta era muy práctica para tener una visión diaria de qué había nuevo, qué había cambiado y qué nuevo contenido multimedia estaba disponible para ser consumido. Es por ello que estos días de vacaciones me propuse ver si se podía montar algo rápido con los servicios PaaS de Azure y así es como me he construido mi propio servicio notificador.

En Azure tenemos un gran número de servicios PaaS que pueden ser utilizados para construir un sistema como este. La elección de uno o de otro dependerá únicamente del nivel de personalización al que queramos llegar o las integraciones que necesitemos realizar. En mi caso, la idea era hacer algo lo más sencillo posible que cumpliera con lo que necesitaba. Es por ello que escogí Logic Apps y Azure Functions.

Logic Apps es una herramienta de diseño para modelar y automatizar procesos en flujos de trabajo. Disponer de diferentes conectores que permiten integrar de forma rápida y transparente diferentes servicios entre sí sin necesidad de escribir código. Por otro lado, Azure Functions permite disponer de una arquitectura serverless que permite la ejecución de nuestro código modelado como una función y sólo pagar por el tiempo en el que dicho código esté en ejecución. Esto nos evita todo el trabajo de gestión, mantenimiento y actualización que tendríamos si trabajáramos con una máquina virtual.

Obteniendo la información

Los tres servicios de los que queremos recoger información disponen de un sistema de sindicación de contenido basado en RSS. Esto simplifica bastante el proceso ya que en Logic Apps existe un conector específico para obtener información de un feed RSS.

Para crear nuestra aplicación tendremos que seleccionar New > Logic App . La información que necesitamos proporcionar es bastante reducida: el nombre de nuestra aplicación lógica junto con el grupo de recursos y región donde queremos que sea desplegada.

Crear aplicación lógica
Crear aplicación lógica

Una vez creada iremos al diseñador gráfico para añadir los elementos de nuestro workflow. Hay un gran número de plantillas ya preparadas para ser utilizadas; sin embargo, en nuestro caso empezaremos un una plantilla en blanco.

Creación Logic App en blanco
Creación Logic App en blanco

El editor nos mostrará un campo para buscar el conector que queremos emplear. Si buscamos por RSS veremos que aparece uno que se llama “RSS – When a feed ítem is published”. Este sería el ideal para utilizar pero dado que no controlamos cuándo se publican o no mensajes y nos gustaría ver si funciona correctamente la aplicación haremos un sistema algo más manual. Buscamos el conector “Recurrence” que nos permite definir cada cuánto tiempo queremos que se ejecute la actividad. En mi caso, una vez al día es suficiente.

Añadiendo el conector de recurrencia
Añadiendo el conector de recurrencia

Tras ello, añadimos un nuevo paso del flujo de trabajo que se una acción . Si buscamos por RSS de nuevo veremos que existe una nueva opción llamadas “RSS – List all RSS feed ítems”. Si lo añadimos nos pedirá una URL, en nuestro caso vamos a probar con el del blog de Azure

Añadiendo el feed RSS a sindicar
Añadiendo el feed RSS a sindicar

Dado que vamos a hacer esto con varios blogs, nos va a interesar disponer de un sitio donde ir almacenando los nuevos contenidos para ser procesados todos de golpe y generar de esta manera el correo electrónico final. Para ello, utilizaremos la conexión existente en Logic Apps con Azure Storage Queues. Cada vez que encontremos un nuevo artículo lo enviaremos a la cola para que sea procesado a posteriori. Al igual que antes, creamos una nueva acción y buscamos “Azure Queues – Put a menssage on a queue”.

Añadiendo acción de enviar mensaje a la cola
Añadiendo acción de enviar mensaje a la cola

Tendremos que seleccionar el nombre de la cola a la que queremos enviar el mensaje y construir el mensaje JSON que pasaremos. En mi caso, el mensaje tiene la siguiente estructura. Como veis, he añadido un campo personalizado indicando que estos artículos vienen del blog de Azure. De esta manera luego puedo identificar cada uno de los mensajes que hay en la cola.

En cuanto empecéis a construir el mensaje la aplicación se dará cuenta de que estáis iterando sobre los elementos de una array por lo que añadirá una estructura “For each” sin necesidad de hacer nada extra.

Añadiendo mensaje a la cola desde Logic Apps
Añadiendo mensaje a la cola desde Logic Apps

Tras esto, tendríamos nuestra primera fuente de información indexada. En mi caso, para obtener la información de los otros dos sitios he realizado el mismo proceso con cada una de ellas.

Probando la Logic App

Si queremos ver que todo funciona como se espera, podemos ejecutar de forma manual nuestra aplicación con el botón que se encuentra en la parte superior del diseñador que se llama “Run”. Esto encolará la ejecución de nuestra aplicación y en unos segundos nos mostrará el resultado de la ejecución y el tiempo gastado en cada uno de los pasos definidos.

Resultado ejecucción manual de la Logic App
Resultado ejecucción manual de la Logic App

Si vamos a ver nuestra cola de Azure Storage, veremos que nuevos elementos han aparecido en ella. En mi caso he optado por el Microsoft Azure Storage Explorer, una utilidad gratuita muy cómoda para trabajar con el almacenamiento.

Mensajes introducidos en la cola automáticamente
Mensajes introducidos en la cola automáticamente

Procesando los mensajes con Azure Functions

Todos los días de forma automática los mensajes se irán colocando en la cola a la espera de ser procesados. Será la responsabilidad de Azure Functions el lanzar nuestro código de forma periódica una vez al día para que coja esos mensajes, los procese y prepare nuestro correo electrónico. Para ello, creamos una nueva función de Azure desde el portal.

Crear una Azure Function
Crear una Azure Function

A diferencia de las Logic Apps aquí podemos definir el modelo de hosting que queremos para nuestra función, el elegir uno u otro impactará en cómo se facturarán los gastos de computación. Si escogéis “Consumption Plan” pagaréis por cada ejecución y los recursos se reservarán de forma dinámica. Si escogéis “App Service Plan” se asignará una capacidad específica con unos costes predecibles y una escalabilidad predefinida asociada al App Service Plan.

A la hora de crear nuestra primera función tenemos un asistente que nos facilita el proceso. En nuestro caso necesitamos una aplicación basada en un “Timer” y desarrollada con C#.

Asistente para crear nueva función
Asistente para crear nueva función

Una vez creada solo toca empezar a escribir nuestro código para procesar los mensajes. Azure Functions soporta paquetes NuGet por lo que añadiremos el SDK de Azure para trabajar con nuestra cuenta de almacenamiento. Para ello, necesitáis el fichero Project.json que contenga lo siguiente. En cuanto salvéis los cambios el entorno empezará a descargarse el paquete y estará accesible desde la función.

{
    "frameworks": {
        "net46": {
            "dependencies":{
                "WindowsAzure.Storage": "7.2.1"
            }
        }
    }
}

En mi caso, el código es bastante sencillo. En primer lugar obtenemos una referencia a nuestra cola de mensajes que se llama “rssinput”. Se está haciendo uso de las claves primarias de la cuenta de almacenamiento para acceder a ella, esto implica que si se pierden estos credenciales cualquiera podría acceder a nuestra cuenta de almacenamiento. En mi caso no hay nada más en dicha cuenta de almacenamiento y es para hacer pruebas por lo que no es crítico. Si vas a usar esta solución en producción es recomendable que revises el acceso basado en SAS para dar permisos concretos de lectura sobre esta cola y evitar problemas extras de seguridad.

Tras ello, hago un peek para saber si hay mensajes o no que procesar e invoco a dos métodos que generan el mensaje de correo a enviar. Finalmente, se invoca de forma asíncrona a una URL que corresponde a otra Logic App que veremos a continuación.

public async static void Run(TimerInfo myTimer, TraceWriter log)
{
 
        string accountName = "XXXXX";
        string accountKey = "XXXXXX";
 
        StorageCredentials storageAccountCredentials = new StorageCredentials(accountName, accountKey);
 
        CloudStorageAccount storageAccount = new CloudStorageAccount(storageAccountCredentials, true);
 
        CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
        CloudQueue rssQueue = queueClient.GetQueueReference("rssinput");
 
        CloudQueueMessage emptyQueueCheckMessage = rssQueue.PeekMessage();
 
        String emailMessage = String.Empty;
 
        if (emptyQueueCheckMessage != null)
        {
            emailMessage = processRssContent(rssQueue);
        } else
        {
            emailMessage = processRssEmpty();
        }
 
        log.Info("Message: " + emailMessage);
 
        HttpClient web = new HttpClient();
        HttpContent content = new StringContent(emailMessage.ToString());
        var response = await web.PostAsync("https://prod-06.northeurope.logic.azure.com:443/workflows/XXXXXXXX", content);
 
        rssQueue.Clear();
 
        log.Info("Request Status Code: " + response.StatusCode.ToString());
}

Una vez enviado el mensaje se eliminan los mensajes de la cola. Si hubiera habido algún error estos mensajes se perderían. Podríamos implementar un mecanismo de control de errores para reintentarlo hasta que tenga éxito o hasta un número máximo de intentos. En esta prueba, no es algo crítico por lo que se asume que Azure funcionará sin problemas :)

Enviando correos electrónicos

Una vez generado el mensaje lo podríamos enviar directamente desde la función; sin embargo, esto implica escribir todo lo necesario para conectarnos al servidor de correo, formatear el mensaje, enviarlo, etc. Para simplificarlo emplearemos una Logic App más que envíe automáticamente los correos electrónicos a través de mi cuenta de correo en Office 365.

Para ello, creamos una nueva Logic App como hemos anteriormente. Escogemos el conector del tipo “Request” y la acción del tipo “Office 365 – Outlook – Send an email”.

Enviando un correo desde Logic Apps
Enviando un correo desde Logic Apps

En el primer bloque se generará una URL que podremos invocar y pasarle información. Esta URL es la que hemos añadido a nuestra función de Azure. En este caso concreto se está enviando un texto plano por lo que no vamos a definir un esquema; sin embargo, si pasáramos un JSON podríamos especificar el esquema para que el editor conociera los datos y nos permitiera utilizarlos de forma dinámica en los siguientes campos.

En el segundo bloque únicamente definimos a quién queremos enviar el correo, el título y el contenido. En este caso será directamente el texto recibido desde la función de Azure.

Probando nuestro notificador

Si ejecutáis de forma manual las aplicaciones lógicas y tras ello lanzáis vuestra función podréis ver cómo nuestra notificador funciona y recibimos un correo electrónico en nuestra bandeja de entrada.

En el caso de que haya contenido nuevo, en mi caso será algo así:

Correo recibido con novedades
Correo recibido con novedades

Si hoy no hay actualizaciones, el mensaje será el siguiente:

Correo recibido sin novedades
Correo recibido sin novedades

Para acabar

Como comentaba al principio la idea era hacer algo lo más sencillo posible que cumpliera con lo que necesitaba. Lo que os he mostrado justamente lo hace por lo que se podría quedar así sin problemas. Sin embargo, es posible añadirle mejoras como emplear el conector “RSS – When a feed ítem is published” al saber que el resto de los elementos funciona. Esto ayudaría a no tener que detectar mensajes repetidos o antiguos. También el correo se podría enviar como HTML en lugar de texto plano. Se podría usar DocumentDB en lugar de Queues y tener así un histórico de la información enviada, se podría convertir la Function en una Logic App personalizada…

Pero eso será en otra ocasión ;)

Leave a Reply

Your email address will not be published. Required fields are marked *