Orquestaciones de Durable Functions

Durable Functions es una extensión de Azure Functions. Puede usar una función de orquestador para organizar la ejecución de otras funciones de Durable Functions dentro de una aplicación de función. Las funciones de orquestador tienen las siguientes características:

  • Las funciones de orquestador definen flujos de trabajo de función con código de procedimiento. No se necesitan esquemas ni diseñadores.
  • Las funciones de orquestador pueden llamar a otras funciones duraderas de forma sincrónica y asincrónica. La salida de las funciones llamadas puede guardarse de forma confiable en variables locales.
  • Las funciones de orquestador son duraderas y confiables. El progreso de la ejecución se controla automáticamente cuando la función está en espera o está procesándose. El estado local nunca se pierde si el proceso se recicla o se reinicia la máquina virtual.
  • Las funciones de orquestador pueden ser de ejecución prolongada. La duración total de una instancia de orquestación puede ser de segundos, días o meses, o no terminar nunca.

En este artículo se ofrece información general sobre las funciones de orquestador y cómo pueden ayudarle a resolver diversos desafíos del desarrollo de aplicaciones. Si aún no está familiarizado con los tipos de funciones disponibles en una aplicación de Durable Functions, lea primero el artículo Tipos de Durable Functions.

Identidad de orquestación

Cada instancia de una orquestación tiene un identificador (también conocido como identificador de instancia). De forma predeterminada, cada identificador de instancia es un GUID generado automáticamente. Sin embargo, los identificadores de instancia también pueden ser un valor de cadena generado por el usuario. Cada identificador de instancia de orquestación debe ser único dentro de una central de tareas.

A continuación se muestran algunas reglas acerca de los identificadores de instancia:

  • Los id. de instancia deben tener entre 1 y 100 caracteres.
  • No deben comenzar por @.
  • No deben contener caracteres /, \, ? ni #.
  • No deben contener caracteres de control.

Nota

En general, se recomienda usar identificadores de instancia generados automáticamente siempre que sea posible. Los identificadores de instancia generados por el usuario están diseñados para escenarios en los que hay una asignación de uno a uno entre una instancia de orquestación y alguna entidad externa específica de la aplicación, como un pedido de compra o un documento.

Además, la aplicación real de las reglas de restricción de caracteres puede variar en función del proveedor de almacenamiento que use la aplicación. Para garantizar un comportamiento y una compatibilidad adecuados, se recomienda encarecidamente seguir las reglas de id. de instancia enumeradas anteriormente.

El identificador de instancia de una orquestación es un parámetro necesario en la mayoría de las operaciones de administración de instancias. También son importantes para el diagnóstico, como la búsqueda de datos de seguimiento de orquestación en Application Insights con fines de solución de problemas o análisis. Por esta razón, se recomienda guardar los identificadores de instancia generados en una ubicación externa (por ejemplo, una base de datos o en registros de aplicación), donde se puede hacer referencia a ellos fácilmente más adelante.

Confiabilidad

Las funciones del orquestador mantienen de forma confiable su estado de ejecución mediante el patrón de diseño origen de eventos. En lugar de almacenar directamente el estado actual de una orquestación, Durable Task Framework utiliza un almacén solo de anexión para registrar la serie completa de acciones que la orquestación de función realiza. Un almacén de solo anexar tiene muchas ventajas en comparación con el "volcado" del estado de tiempo de ejecución completo. Entre las ventajas se incluyen un mayor rendimiento, escalabilidad y capacidad de respuesta. También obtiene coherencia ocasional para los datos transaccionales y registros e historiales de auditoría completa. Los registros de auditoría son compatibles con las acciones de compensación confiables.

Durable Functions usa el aprovisionamiento de eventos transparente. En segundo plano, el operador await (C#) o yield (JavaScript y Python) en una función del orquestador devuelve el control del subproceso del orquestador al distribuidor de Durable Task Framework. En el caso de Java, no hay ninguna palabra clave de lenguaje especial. En su lugar, llamar a .await() en una tarea devolverá el control al distribuidor a través de un personalizado Throwable. El distribuidor, a continuación, confirma las nuevas acciones que la función de orquestador programó (como llamar a una o más funciones secundarias o programar un temporizador durable) al almacenamiento. La acción de confirmación transparente actualiza el historial de ejecución de la instancia de orquestación al anexar todos los eventos nuevos al almacenamiento, de forma muy parecida a un registro de solo adición. De forma similar, la acción de confirmación crea mensajes en el almacenamiento para programar el trabajo real. En este momento, la función del orquestador se puede descargar de la memoria. De forma predeterminada, Durable Functions usa Azure Storage como su almacén de estado de entorno de ejecución, pero también se admiten otros proveedores de almacenamiento.

Cuando una función de orquestación recibe más trabajo para realizar (por ejemplo, se recibe un mensaje de respuesta o expira un temporizador durable), el orquestador se activa y vuelve a ejecutar toda la función desde el principio con el fin de recompliar el estado local. Durante esta reproducción, si el código intenta realizar una llamada a una función (o realizar cualquier otro trabajo asincrónico), Durable Task Framework consulta el historial de ejecución de la orquestación actual. Si descubre que la función de actividad ya se ha ejecutado y ofrece un resultado, reproduce el resultado de esa función y el código del orquestador sigue ejecutándose. La reproducción continúa hasta que finaliza el código de función o hasta que se programa un nuevo trabajo asincrónico.

Nota

Para que el patrón de reproducción funcione correctamente y de forma confiable, el código de la función de orquestador debe ser determinista. El código de orquestador no determinista puede dar lugar a errores de entorno de ejecución u otro comportamiento inesperado. Para más información sobre las restricciones de código para las funciones de orquestador, consulte la documentación sobre las restricciones de código de la función de orquestador.

Nota

Si una función de orquestador emite mensajes de registro, el comportamiento de reproducción puede provocar la emisión de mensajes de registro duplicados. Consulte el tema Registro para más información sobre por qué se produce este comportamiento y cómo solucionarlo.

Historial de orquestación

El comportamiento del origen de eventos de Durable Task Framework está estrechamente relacionado con el código de la función de orquestador que se escriba. Supongamos que tiene una función de orquestador de encadenamiento de actividad, como la siguiente función de orquestador:

Nota:

La versión 4 del modelo de programación de Node.js para Azure Functions está disponible de forma general. El nuevo modelo v4 está diseñado para que los desarrolladores de JavaScript y TypeScript tengan una experiencia más flexible e intuitiva. Obtenga más información sobre las diferencias entre v3 y v4 en la guía de migración.

En los siguientes fragmentos de código, JavaScript (PM4) indica el modelo de programación V4, la nueva experiencia.

[FunctionName("HelloCities")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}

Cada vez que se programa una función de actividad, el Durable Task Framework hace un punto de control del estado de ejecución de la función en algún backend de almacenamiento durable (Azure Table storage de manera predeterminada). Este estado es lo que se conoce como historial de orquestación.

Tabla del historial

Por lo general, Durable Task Framework hace lo siguiente en cada punto de control:

  1. Guarda el historial de ejecución en almacenamiento duradero.
  2. Pone en cola los mensajes para las funciones que el orquestador desea invocar.
  3. Pone en cola los mensajes para el orquestador en sí, por ejemplo, los de temporizador durable.

Una vez completado el punto de control, la función de orquestador se puede quitar de la memoria hasta que tenga más trabajo.

Nota

Azure Storage no proporciona garantías para la transacción de guardado de datos en Table Storage y las colas. Para controlar los errores, el proveedor de Durable Functions de Azure Storage usa patrones de posible coherencia. Estos patrones garantizan la ausencia de pérdida de datos en caso de bloqueo o pérdida de conectividad en medio de un punto de control. Los proveedores de almacenamiento alternativos, como los proveedores de almacenamiento MSSQL de Durable Functions, pueden proporcionar garantías de coherencia más sólidas.

Tras la finalización, el historial de la función mostrada anteriormente tiene un aspecto similar a la siguiente tabla en Azure Table Storage (abreviado con fines ilustrativos):

PartitionKey (InstanceId) EventType Timestamp Entrada Nombre Resultado Estado
eaee885b ExecutionStarted 2021-05-05T18:45:28.852Z null HelloCities
eaee885b OrchestratorStarted 2021-05-05T18:45:32.362Z
eaee885b TaskScheduled 2021-05-05T18:45:32.67Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:32.67Z
eaee885b TaskCompleted 2021-05-05T18:45:34.201Z """Hello Tokyo!"""
eaee885b OrchestratorStarted 2021-05-05T18:45:34.232Z
eaee885b TaskScheduled 2021-05-05T18:45:34.435Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.435Z
eaee885b TaskCompleted 2021-05-05T18:45:34.763Z """Hello Seattle!"""
eaee885b OrchestratorStarted 2021-05-05T18:45:34.857Z
eaee885b TaskScheduled 2021-05-05T18:45:34.857Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.857Z
eaee885b TaskCompleted 2021-05-05T18:45:34.919Z """Hello London!"""
eaee885b OrchestratorStarted 2021-05-05T18:45:35.032Z
eaee885b OrchestratorCompleted 2021-05-05T18:45:35.044Z
eaee885b ExecutionCompleted 2021-05-05T18:45:35.044Z "[""Hello Tokyo!"",""Hello Seattle!"",""Hello London!""]" Completed

Notas sobre los valores de las columnas:

  • PartitionKey: contiene el identificador de instancia de la orquestación.
  • EventType: representa el tipo de evento. Aquí puede encontrar descripciones detalladas de todos los tipos de eventos del historial.
  • Marca de tiempo: marca de tiempo UTC del evento del historial.
  • Name: nombre de la aplicación de función invocada.
  • Entrada: entrada de la función con formato JSON.
  • Result: resultado de la función; es decir, el valor devuelto.

Advertencia

Aunque es útil como herramienta de depuración, no confíe en esta tabla. Puede cambiar con el progreso de la extensión Durable Functions.

Cada vez que la función se reanuda después de esperar a que se complete una tarea, Durable Task Framework vuelve a ejecutar la función de orquestador desde cero. En cada repetición, consulte el historial de ejecución para determinar si la tarea asíncrona actual se ha completado. Si el historial de ejecución muestra que la tarea ya se ha completado, el marco reproduce la salida de esa tarea y pasa a la siguiente tarea. Este proceso continúa hasta que se ha reproducido todo el historial de ejecución. Una vez que se ha reproducido el historial de ejecución actual, las variables locales se restaurarán a sus valores anteriores.

Características y patrones

Las secciones siguientes describen las características y los patrones de las funciones de orquestador.

Suborquestaciones

Las funciones de orquestador pueden llamar a las funciones de actividad, pero pueden llamar también a otras funciones de orquestador. Por ejemplo, puede crear una orquestación mayor a partir de una biblioteca de funciones de orquestador. También puede ejecutar varias instancias de una función de orquestador en paralelo.

Para más información y ejemplos, vea el artículo Suborquestaciones.

Temporizadores durables

Las orquestaciones pueden programar temporizadores duraderos para implementar retrasos o configurar el control de tiempo de espera en acciones asincrónicas. Use temporizadores duraderos en las funciones del orquestador en lugar de las API "sleep" nativas del lenguaje.

Para más información y ejemplos, consulte el artículo Temporizadores en Durable Functions.

Eventos externos

Las funciones de orquestador pueden esperar por los eventos externos para actualizar una instancia de orquestación. Esta característica de Durable Functions suele ser útil para controlar las interacciones humanas u otras devoluciones de llamada externas.

Para más información y ejemplos, consulte el artículo Eventos externos.

Control de errores

Las funciones de orquestador pueden usar las características de control de errores del lenguaje de programación. Los patrones existentes como try/catch se admiten en el código de orquestación.

Las funciones de orquestador también pueden agregar directivas de reintento a las funciones de suborquestador o actividad a las que llaman. Si una actividad o una función de suborquestador produce un error con una excepción, la directiva de reintentos especificada puede retrasar automáticamente la ejecución y volver a intentarla un número especificado de veces.

Nota

Si hay una excepción no controlada en una función de orquestador, la instancia de orquestación se completará con un estado Failed. Una instancia de orquestación no se puede reintentar una vez que se ha producido un error.

Para más información y ejemplos, vea el artículo Control de errores.

Secciones críticas (Durable Functions 2.x, actualmente solo .NET)

Las instancias de orquestación tienen un único subproceso, por lo que no es necesario preocuparse por las condiciones de carrera dentro de una orquestación. Sin embargo, se pueden producir condiciones de carrera cuando las orquestaciones interactúan con sistemas externos. Para mitigar las condiciones de carrera al interactuar con sistemas externos, las funciones de orquestador pueden definir secciones críticas con un método LockAsync de .NET.

En el código de ejemplo siguiente se muestra una función de orquestador que define una sección crítica. Entra en la sección crítica mediante el método LockAsync. Este método requiere pasar una o varias referencias a una entidad duradera, que administra de forma duradera el estado de bloqueo. Solo una única instancia de esta orquestación puede ejecutar el código de la sección crítica a la vez.

[FunctionName("Synchronize")]
public static async Task Synchronize(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var lockId = new EntityId("LockEntity", "MyLockIdentifier");
    using (await context.LockAsync(lockId))
    {
        // critical section - only one orchestration can enter at a time
    }
}

LockAsync adquiere los bloqueos duraderos y devuelve un resultado IDisposable que finaliza la sección crítica cuando se desecha. Este resultado IDisposable se puede usar junto con un bloque using para obtener una representación sintáctica de la sección crítica. Cuando una función de orquestador entra en una sección crítica, solo una instancia puede ejecutar ese bloque de código. Todas las demás instancias que intenten entrar en la sección crítica se bloquearán hasta que la instancia anterior salga.

La característica de sección crítica también es útil para coordinar los cambios en las entidades duraderas. Para más información acerca de las secciones críticas, consulte el tema Coordinación de entidades duraderas.

Nota

Las secciones críticas están disponibles en Durable Functions 2.0. Actualmente, solo las orquestaciones en proceso de .NET implementan esta característica. Las entidades y las secciones críticas aún no están disponibles en Durable Functions para un trabajo aislado de dotnet.

Llamada a puntos de conexión HTTP (Durable Functions 2.x)

No se permite la E/S de las funciones de orquestador, según se describe en las restricciones de código de las funciones de orquestador. La solución alternativa habitual para esta limitación es ajustar cualquier código que necesite realizar operaciones de E/S en una función de actividad. Las orquestaciones que interactúan con sistemas externos usan con frecuencia funciones de actividad para realizar llamadas HTTP y devolver el resultado a la orquestación.

Para simplificar este patrón común, las funciones de orquestador pueden usar el método CallHttpAsync de .NET para invocar directamente a las API HTTP.

[FunctionName("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    Uri url = context.GetInput<Uri>();

    // Makes an HTTP GET request to the specified endpoint
    DurableHttpResponse response = 
        await context.CallHttpAsync(HttpMethod.Get, url);

    if ((int)response.StatusCode == 400)
    {
        // handling of error codes goes here
    }
}

Además de admitir patrones de solicitud y respuesta básicos, el método admite el control automático de los patrones de sondeo asincrónicos HTTP 202 y, además, admite la autenticación de servicios externos mediante identidades administradas.

Para más información y ejemplos detallados, consulte el artículo Características de HTTP.

Nota

La llamada a puntos de conexión HTTP directamente desde las funciones de orquestador está disponible en Durable Functions 2.0 y versiones posteriores.

Paso de varios parámetros

No es posible pasar varios parámetros a una función de actividad directamente. Se recomienda pasar una matriz de objetos o de objetos compuestos.

En .NET también puede usar objetos ValueTuple. El ejemplo siguiente utiliza las nuevas características de ValueTuple agregado con C# 7:

[FunctionName("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string major = "ComputerScience";
    int universityYear = context.GetInput<int>();

    object courseRecommendations = await context.CallActivityAsync<object>(
        "CourseRecommendations",
        (major, universityYear));
    return courseRecommendations;
}

[FunctionName("CourseRecommendations")]
public static async Task<object> Mapper([ActivityTrigger] IDurableActivityContext inputs)
{
    // parse input for student's major and year in university
    (string Major, int UniversityYear) studentInfo = inputs.GetInput<(string, int)>();

    // retrieve and return course recommendations by major and university year
    return new
    {
        major = studentInfo.Major,
        universityYear = studentInfo.UniversityYear,
        recommendedCourses = new []
        {
            "Introduction to .NET Programming",
            "Introduction to Linux",
            "Becoming an Entrepreneur"
        }
    };
}

Pasos siguientes