Azure Durable Functions

Si has leído los artículos de los últimos días, ya sabes casi todo lo que Azure Functions te puede ofrecer. Sin embargo, te habrás dado cuenta de que hay escenarios que son mucho más complejos que una simple función y no es plan de usar serverless como si fuera un código espagueti inmenso, cuando nos deberíamos pasar los días pensando en refactorizar y reutilizar lo más posible. Lo más lógico es que en muchas ocasiones debamos de realizar diferentes llamadas a diferentes funciones. A veces en secuencial, a veces en paralelo, y es aquí donde entra Azure Durable Functions.
Esta funcionalidad se trata de una extensión para Azure Functions, que te permite la orquestación de llamadas a funciones serverless. En este post te explicaré el ejemplo más sencillo para que comprendas su utilidad.

Lo primero que debes saber acerca de esta extensión es que te facilita el uso de diferentes patrones. En este post vamos a ver el patrón chaining, donde se ejecutan una función detrás de otra, es decir en secuencial, en un orden específico.

Patrón Function chaining

Hay tres funciones como mínimo que entran en juego:

  • HTTP starter:  es la encargada de llamar al orquestador que se le pase como parámetro.
  • Orchestrator: en esta función es dónde se define el patrón a utilizar para llamar a las diferentes actividades que componen el flujo.
  • Activity: es la función que contiene la lógica de negocio que quieres lanzar. Lo normal es que tengas diferentes actividades que sean las que necesitas orquestrar, aunque también puede ser la misma con diferentes parámetros.

Veremos cada una de ellas según montamos el ejemplo.

HTTP Starter

Desde el portal de Azure, existen plantillas que nos facilitan la introducción a las durable functions. Desde tu servicio, en el apartado Functions, haz clic en el botón + para agregar una nueva función y selecciona la plantilla Durable  Functions HTTP Starter.

Crear función del tipo Durable Functions HTTP starter

Se te avisará de que la extensión Microsoft.Azure.WebJobs.Extensions.DurableTask no está instalada, por lo que debes hacer clic en el botón Install antes de proceder.

Una vez que haya finalizado la instalación te pedirá el nombre de la función, que llamaremos HTTPStarter.

const df = require("durable-functions");
module.exports = async function (context, req) {
    const client = df.getClient(context);
    const instanceId = await client.startNew(req.params.functionName, undefined, req.body);
    context.log(`Orchestrator to call: ${req.params.functionName}`);
    context.log(`Started orchestration with ID = '${instanceId}'.`);
    return client.createCheckStatusResponse(context.bindingData.req, instanceId);
};

Como decía al inicio, esta función tiene como cometido llamar al orquestador, que se encargará de invocar a las actividades que quieres ejecutar. He añadido una línea para que veas por pantalla el valor de req.params.functionName. Esta será la función que deberás llamar para iniciar todo el proceso. Después te diré como 🙂

Orquestador

El siguiente componente que debes crear es el orquestador, que es donde se invocará a las funciones actividades que realmente tienen el código que quieres ejecutar. También existe una plantilla para este componente llamada Durable Functions orchestrator.

Durable Functions orchestrator

En esta plantilla lo que vas a ver es cómo se llama a las diferentes actividades de forma secuencial, utilizando el patrón chaining, que se apoya en las funciones generadoras de ECMAScript 6. Llámala HelloInSequence.

/*
 * This function is not intended to be invoked directly. Instead it will be
 * triggered by an HTTP starter function.
 * 
 * Before running this sample, please:
 * - create a Durable activity function (default name is "Hello")
 * - create a Durable HTTP starter function
 * - run 'npm install durable-functions' from the wwwroot folder of your 
 *    function app in Kudu
 */
const df = require("durable-functions");
module.exports = df.orchestrator(function* (context) {
    const outputs = [];
    // Replace "Hello" with the name of your Durable Activity Function.
    outputs.push(yield context.df.callActivity("Hello", "Tokyo"));
    outputs.push(yield context.df.callActivity("Hello", "Seattle"));
    outputs.push(yield context.df.callActivity("Hello", "London"));
    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
});

Lo bueno de esta plantilla es que viene con comentarios para entender qué es lo que hace y qué es lo que te hace falta para llamar a una actividad que todavía no existe, llamada Hello.

Actividad

El final de este recorrido son las actividades. Son las funciones que contienen tu código y necesitas que sean orquestradas por el orquestrador. En este ejemplo sólo existe una llamada a la actividad Hello, por lo que crea una nueva función utilizando la plantilla Durable Functions activity y asegúrate que la misma se llama Hello.

Durable Functions activity

Esta plantilla no tiene más que una función súper simple que concatena ‘Hello ‘ con la que cadena que se ha pasado a la función como parámetro, en este caso Tokyo, Seattle y London.

/*
 * This function is not intended to be invoked directly. Instead it will be
 * triggered by an orchestrator function.
 * 
 * Before running this sample, please:
 * - create a Durable orchestration function
 * - create a Durable HTTP starter function
 * - run 'npm install durable-functions' from the wwwroot folder of your
 *   function app in Kudu
 */
module.exports = function (context) {
    context.done(null, `Hello ${context.bindings.name}!`);
};

Instalar el módulo durable-functions

Por último, para que funcione todo este tinglado es necesario instalar el módulo durable-functions. Accede al portal Kudu desde tu Azure Function:

Azure Functions – Advanced tools (Kudu)

A través de la opción CMD del menú, en Debug Console, sube un archivo llamado package.json con el siguiente contenido:

{
    "dependencies": {
        "durable-functions": "^1.1.2"
    }
}

Ejecuta npm install para instalarlo y que esté disponible dentro del servicio.

npm install durable-functions

Probar Azure Durable Functions

Para comprobar que todo funciona como esperamos lo que tenemos que hacer es invocar a la función HTTPStarter. Cabría esperar que la llamada a la misma fuera https://TU_SERVICIO.azurewebsites.net/api/HTTPStarter?code=AZURE_FUNCTION_KEY pero se está haciendo uso de una route template que modifica la llamada a https://TU_SERVICIO.azurewebsites.net/api/orchestrators/{functionName}?code=AZURE_FUNCTION_KEY

Azure Durable Functions – HTTPStarter – Integrate – Route template

Los métodos HTTP disponibles son GET y POST por lo que podemos realizar la llamada con cualquiera de ellos. Para que no tengas que montar la URL puedes ir directamente a la función HTTPStarter y hacer clic en </> Get function URL, pero quería que entendieras por qué esta URL era diferente a la de cualquier función.

Azure Durable Functions – HTTPStarter – Get function URL

Abre postman y pega la URL de tu función HTTPStarter. Modifica {functionName} por HelloInSequence y haz clic en Send.

Postman – probando llamadas a Azure Durable Functions

Esta llamada devuelve cuatro URLs relacionadas con la instancia que se acaba de generar con tu llamada:

  • statusQueryGetUri: devolverá el estado de la instancia, si está pendiente o ha finalizado, además del resultado de la misma.
  • sendEventPostUri:  genera el evento de la instancia.
  • terminatePostUri: esta llamada finaliza la instancia.
  • rewindPostUri: también es posible reiniciarla.

Si accedemos a la primera de ellas podremos ver el resultado, que en este caso es llamar a la función Hello tres veces con diferentes parámetros: Tokyo, Seattle y London.

Postman – statusQueryGetUri

Ejecutar Azure Durable Functions en local

A día de hoy es importante que tengas un par de puntos en cuenta cuando quieres crear y ejecutar este tipo de funciones en local:

Lo primero de ello es que debes asegurarte de que la versión del paquete Microsoft.Azure.WebJobs.Extensions.DurableTask que aperece en el archivo extensions.csproj es al menos la 1.7.0.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
	<WarningsAsErrors></WarningsAsErrors>
	<DefaultItemExcludes>**</DefaultItemExcludes>
  </PropertyGroup>
  <ItemGroup>
     <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="1.7.0" />
     <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.0" />
     <PackageReference Include="Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator" Version="1.0.*" />
  </ItemGroup>
</Project>

Por otro lado, debes añadir una variable del sistema (no en variables del usuario, ya que no funcionará) llamada WEBSITE_HOSTNAME con el valor localhost:7071.

Variable WEBSITE_HOSTNAME con el valor localhost:7071

Con todo esto, ya estás list@ para dar un paso más, orquestando tus funciones serverless.

¡Saludos!