¿Qué son los Output Bindings en Azure Functions?

Qué son los Output Bindings en Azure Functions

Por mi experiencia profesional, he podido observar que muchos Developers no aplican o desconocen el concepto de Binding o enlace. Vamos a conocerlos un poco mejor en este artículo.

Azure Functions

Pocos desarrolladores a estas alturas, por no decir ninguno, no habrán oído hablar del concepto «Serverless» o de las arquitecturas sin servidor. En Microsoft Azure para poder implementarlas disponemos del servicio de Azure Functions.

Si ya tienes algunas horas de vuelo en este tema, seguro que conoces el concepto de Trigger. Estos Triggers o desencadenadores provocan la ejecución de una Azure Function cuando sucede algo, como por ejemplo, cuando se escribe un mensaje en un Service Bus.

Pero, como decía al principio, tras muchos años de experiencia he observado que algunos desarrolladores no aplican o no conocen el concepto de Binding o enlace.

Quizá sí se conoce el concepto de enlace de entrada, ya que el Trigger, en sí mismo, es un tipo especial de enlace de entrada que permite leer del origen de datos que ha disparado la ejecución de la función, pero sin duda, el tipo de enlace que menos se emplea es el de salida.

Por ejemplo, si en una Azure Function queremos escribir un mensaje en una cola o insertar datos en un Table Storage, la mayoría de desarrolladores hacen uso del SDK de Azure Storage correspondiente y generan una solución «a mano», obviando la existencia de esta característica que nos simplifica el trabajo, con algún matiz a tener en cuenta.

¿Qué son los Bindings o enlaces?

Los enlaces o Bindings conectan recursos o datos a una función de Azure. Podemos tener varios enlaces para acceder a diferentes tipos de datos, lo que nos permite conectarnos al origen o al destino del dato sin tener que escribir la lógica específica para ello. Hay dos tipos de enlaces:

  • Entrada: Permiten leer datos. Se conecta al origen de datos.
  • Salida: Permiten escribir datos. Se conecta al destino de los datos.

Hay bastantes más enlaces de salida que de entrada (todos los tipos de funciones menos las de tipo temporizador tienen enlaces de salida). Estos enlaces nos van a permitir establecer un flujo de ejecución de funciones haciendo uso de una característica nativa del servicio, sin necesidad de escribir más código extra.

Pero vamos a comenzar y ver qué podemos hacer con estos enlaces.

Show me the code

En este ejemplo vamos a enlazar la ejecución de varias Azure Functions haciendo uso de los enlaces de salida. En primer lugar, esta demo se ha realizado en .NET 6 y Visual Studio 2022, que nos gusta estar a la última. Por tanto, con la versión 4.x de Azure Functions. Podemos crear el proyecto de tipo Azure Functions con el Wizard de Visual Studio, en VsCode o desde un terminal haciendo uso de los comandos de Azure Functions Core Tools.

En este caso hemos escrito el ejemplo en C#, pero podríamos hacer uso de cualquier otro lenguaje de programación como JavaScript o Python.

En C#, para hacer uso de las extensiones del SDK de Azure Storage empleadas, hay que instalar el paquete Nuget Microsoft.Azure.WebJobs.Extensions.Storage, en este caso hemos usado la versión 4.0.5 para poder hacer uso de los atributos de los enlaces de salida de Table Storage, ya que la versión 5.0.0 de la librería no hace uso de los mismos. Puedes consultar más información aquí.

Como decíamos, el objetivo de este ejemplo es conectar varias Azure Function mediante enlaces de salida. Para ello, vamos a tener tres functions:

  • Un HttpTrigger que recibe unos datos en el cuerpo de la petición y los escribe en un mensaje en un Azure Queue Storage.
  • Un QueueTrigger que va a copiar el mensaje en un fichero JSON y lo almacena en un Blob Storage.
  • Un BlobTrigger que lee el contenido del fichero e inserta un registro en un Azure Table Storage.
Cómo conectar varias Azure Functions

Azure Function HTTP Trigger

Creamos un endpoint HTTP muy sencillo al que, mediante un POST, se le envían los datos de un grupo de música. Como vemos, el método Run tiene este atributo:

[return: Queue("%BandQueueName%")]

Esta línea indica, mediante un enlace de salida de Azure Queue Storage, que el tipo de dato que devuelve la Azure Function, un array de bytes, se va a escribir en la cola de mensajes que indiquemos en el parámetro. En caso de que no existiese la cola, se crearía automáticamente en el Storage correspondiente, en este ejemplo, en el emulador.

public static class BandApi
{
    [FunctionName("BandApi")]
    [return: Queue("%BandQueueName%")]
    public static async Task<byte[]> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "band")] HttpRequest req)
    {
        using MemoryStream ms = new();
        await req.Body.CopyToAsync(ms);
        return ms.ToArray();
    }
}

Azure Function Queue Trigger

Esta Azure Function recibe el mensaje y esta vez, en lugar de usar un atributo en el método, se usa como parámetro de salida de la función, indicando que se va a escribir en un blob de un contenedor determinado, generando un fichero JSON cuyo nombre será un GUID. Como anteriormente, si no existiese el contenedor dónde queremos almacenar el fichero se crearía automáticamente.

public static class BandMessageHandler
{
    [FunctionName("BandMessageHandler")]
    public static async Task Run(
        [QueueTrigger("%BandQueueName%")] byte[] message,
        [Blob("%BandsContainerName%/{rand-guid}.json", FileAccess.Write)] CloudBlockBlob outputBlob)
    {
        using var ms = new MemoryStream(message);
        await outputBlob.UploadFromStreamAsync(ms);
    }
}

Azure Function Blob Trigger

Para terminar, se recibe el contenido del fichero y se inserta en un Table Storage. Haciendo uso de nuevo del atributo en el método, de esta manera:

[return: Table("%BandTable%")]

En este caso, si la tabla no existiese, obtendríamos un error, por lo que habría que crearla previamente. La función quedaría así:

public static class StoreBand
{
    [FunctionName("StoreBand")]
    [return: Table("%BandTable%")]
    public static async Task<BandEntity> Run(
        [BlobTrigger("%BandsContainerName%/{name}")] Stream myBlob,
        string name)
    {
        var band = await JsonSerializer.DeserializeAsync<Band>(myBlob, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        });
 
        return new BandEntity
        {
            PartitionKey = band.Genre,
            RowKey = Guid.NewGuid().ToString(),
            Name = band.Name,
            FoundationYear = band.FoundationYear,
            Albums = band.Albums
        };
    }
}

A continuación os dejamos el video para ver la demo del ejemplo:

¿Qué conclusiones podemos sacar?

Los enlaces de salida nos permiten escribir en el destino de datos de manera nativa, sin la necesidad de un trabajo extra por parte del desarrollador para realizar esta tarea. A su vez, nos va a permitir separar responsabilidades en nuestras funciones, pudiendo generar un flujo de ejecución lógico en función de nuestras necesidades.

Podeis acceder al repo de Github con el código aquí.

Happy Coding!

Carlos Moreno - Encamina

Artículo escrito por Carlos Moreno, Software & Cloud Architect en ENCAMINA.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

Relacionados

Tendencias

Más leídos

Se habla de..

0
Would love your thoughts, please comment.x
()
x