Restricciones de código de las funciones de orquestador
Durable Functions es una extensión de Azure Functions que le permite crear aplicaciones con estado. 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 estado y son confiables y potencialmente de larga duración.
Restricciones de código del orquestador
Las funciones de orquestador usan el suministro de eventos para garantizar la ejecución confiable y mantener el estado de la variable local. El comportamiento de reproducción del código de orquestador crea restricciones sobre el tipo de código que puede escribir en una función de orquestador. Por ejemplo, las funciones de orquestador deben ser deterministas: una función de orquestador se reproducirá varias veces y debe producir el mismo resultado cada vez.
Uso de API deterministas
En esta sección se proporcionan algunas pautas simples que le permiten garantizar que su código sea determinista.
Las funciones de orquestador pueden llamar a cualquier API en sus lenguajes de destino. Sin embargo, es importante que las funciones de orquestador llamen solo API deterministas. Una API determinista es aquella que siempre devuelve el mismo valor con la misma entrada, con independencia de cuándo o con qué frecuencia se le llama.
En las secciones siguientes se proporcionan instrucciones sobre las API y los patrones que debe evitar porque no son deterministas. Estas restricciones solo se aplican a las funciones de orquestador. Otros tipos de funciones no tienen estas restricciones.
Nota
A continuación se describen varios tipos de restricciones de código. Desafortunadamente, esta lista no es exhaustiva y algunos casos de uso podrían no cubrirse. Lo más importante a tener en cuenta a la hora de escribir el código del orquestador es si una API que se usa es determinista. Una vez que se sienta cómodo al pensar de esta manera, es fácil entender qué API es seguro usar y cuáles no sin necesidad de consultar esta lista documentada.
Fechas y horas
Las API que devuelven la fecha o la hora actual son no deterministas y nunca deben usarse en las funciones del orquestador. Esto se debe a que cada repetición de la función orquestadora producirá un valor diferente. En su lugar, se debe usar la API equivalente a las Durable Functions para obtener la fecha u hora actual, que permanece consistente a través de las repeticiones.
No use DateTime.Now
, DateTime.UtcNow
o las API equivalentes para obtener la hora actual. También se deben evitar clases como Stopwatch
. Para las funciones del orquestador en proceso de .NET, use la propiedad IDurableOrchestrationContext.CurrentUtcDateTime
para obtener la hora actual. Para las funciones orquestadoras aisladas de .NET, use la propiedad TaskOrchestrationContext.CurrentDateTimeUtc
para obtener la hora actual.
DateTime startTime = context.CurrentUtcDateTime;
// do some work
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);
GUID y UUID
Las API que devuelven un valor de GUID o UUID aleatorio no son deterministas porque el valor generado es diferente en cada reproducción. En función del lenguaje que use, puede haber una API integrada para generar GUIDs o UUIDs deterministas. De lo contrario, use una función de actividad para regresar un GUID o UUID generado aleatoriamente.
No use API como Guid.NewGuid()
para generar GUID aleatorios. En su lugar, use la API NewGuid()
del objeto de contexto para generar un GUID aleatorio que sea seguro para la repetición del orquestador.
Guid randomGuid = context.NewGuid();
Nota
Los GUID generados con las API de contexto de orquestación son UUID de tipo 5.
Números aleatorios
Use una función de actividad para devolver números aleatorios a una orquestación. Los valores devueltos de las funciones de actividad son siempre seguros para su reproducción porque se guardan en el historial de orquestación.
Como alternativa, se puede usar un generador de números aleatorios con un valor de semilla fijo directamente en una función de orquestador. Este enfoque es seguro siempre que se genere la misma secuencia de números para cada repetición de la orquestación.
Enlaces
Una función del orquestador no debe usar ningún tipo de enlace, ni siquiera los enlaces del cliente de orquestación y del cliente de entidad. Use siempre los enlaces de entrada y salida desde una función de cliente o actividad. Esto es importante porque las funciones del orquestador se pueden reproducir múltiples veces, causando una E/S no determinista y duplicada con los sistemas externos.
Variables estáticas
Evite el uso de variables estáticas en las funciones del orquestador porque sus valores pueden cambiar con el tiempo, lo cual provoca un comportamiento no determinista en tiempo de ejecución. En su lugar, use constantes, o limite el uso de variables estáticas a las funciones de actividad.
Nota
Incluso fuera de las funciones del orquestador, el uso de variables estáticas en Azure Functions puede ser problemático por varias razones, ya que no hay garantía de que el estado estático persista a través de múltiples ejecuciones de la función. Las variables estáticas deben evitarse excepto en casos de uso muy específicos, como el almacenamiento en memoria de mejor esfuerzo en funciones de actividad o entidad.
Variables de entorno
No use variables de entorno en las funciones de orquestador. Sus valores pueden cambiar con el tiempo, lo que resulta en un comportamiento de tiempo de ejecución no determinista. Si una función del orquestador necesita una configuración definida en una variable de entorno, debe pasar el valor de la configuración a la función del orquestador como entrada o como valor de retorno de una función de actividad.
Red y HTTP
Use las funciones de actividad para hacer llamadas de red salientes. Si necesita realizar una llamada HTTP desde la función de orquestador, también tiene la opción de usar las API HTTP duraderas.
API de bloqueo de subprocesos
Las API de bloqueo como "sleep" pueden causar problemas de rendimiento y escala para las funciones del orquestador y deben evitarse. En el plan de consumo de Azure Functions, incluso pueden provocar cargos innecesarios en tiempo de ejecución. Puede usar alternativas para bloquear las API cuando estén disponibles. Por ejemplo, use temporizadores duraderos para crear retrasos que sean seguros para la repetición y que no cuenten para el tiempo de ejecución de una función del orquestador.
API asincrónicas
El código del orquestador nunca debe iniciar ninguna operación asíncrona excepto las definidas por el objeto de contexto del activador de orquestación. Por ejemplo, nunca use Task.Run
, Task.Delay
y HttpClient.SendAsync
en .NET o setTimeout
y setInterval
en JavaScript. Una función del orquestador solo debe programar trabajos asíncronos que usen las API del SDK de Durable, como las funciones de programación de actividades. Cualquier otro tipo de invocaciones asíncronas debe realizarse dentro de las funciones de actividad.
Funciones asincrónicas de JavaScript
Declare siempre las funciones del orquestador de JavaScript como funciones generadoras sincrónicas. No debe declarar las funciones de orquestador de JavaScript como async
porque el runtime de Node.js no garantiza que esas funciones asincrónicas sean deterministas.
Corrutinas de Python
No debe declarar las funciones de orquestador de Python como corrutinas. En otras palabras, nunca declare funciones del orquestador de Python con la palabra clave async
porque la semántica de la corrutina no se alinea con el modelo de repetición de Durable Functions. Siempre se deben declarar las funciones del orquestador de Python como generadores, lo que significa que se debe esperar que la API context
use yield
en lugar de await
.
API de subproceso de .NET
Durable Task Framework ejecuta código de orquestador en un solo subproceso y no puede interactuar con otros. Ejecutar continuaciones asíncronas en un subproceso del grupo de trabajadores en la ejecución de una orquestación puede dar lugar a una ejecución no determinista o a bloqueos. Por esta razón, las funciones del orquestador casi nunca deberían usar las APIs de subproceso. Por ejemplo, nunca use ConfigureAwait(continueOnCapturedContext: false)
en una función del orquestador. Esto garantiza que las continuaciones de tareas se ejecuten en SynchronizationContext
original de la función del orquestador.
Nota
El Durable Task Framework intenta detectar el uso accidental de subprocesos no orquestadores en las funciones del orquestador. Si encuentra una infracción, este marco genera una excepción NonDeterministicOrchestrationException. Sin embargo, este comportamiento de detección no detectará todas las infracciones y no debe depender del mismo.
Control de versiones
Una orquestación duradera puede ejecutarse continuamente durante días, meses, años o incluso eternamente. Cualquier actualización de código realizada en aplicaciones de Durable Functions que afecte a las orquestaciones que no se hayan completado, puede interrumpir el comportamiento de reproducción de esas orquestaciones. Por lo tanto, es importante planificar cuidadosamente las actualizaciones que se harán en el código. Para obtener una descripción más detallada de cómo se debe hacer una versión del código, consulte el artículo Control de versiones.
Tareas durables
Nota
En esta sección se describen los detalles internos de implementación de Durable Task Framework. Puede usar Durable Functions sin necesidad de conocer esta información. Su único fin es ayudarle a entender el comportamiento de reproducción.
Las tareas que pueden esperar de forma segura en las funciones de orquestador se conocen en ocasiones como tareas durables. Durable Task Framework crea y gestiona estas tareas. Algunos ejemplos son las tareas devueltas por CallActivityAsync
, CreateTimer
y WaitForExternalEvent
en las funciones de orquestador de .NET.
Estas tareas duraderas se administran internamente mediante una lista de objetos TaskCompletionSource
en .NET. Durante la reproducción, estas tareas se crean como parte de la ejecución del código del orquestador. Estas terminan cuando el distribuidor enumera los eventos de historial correspondientes.
La ejecución de las tareas se lleva a cabo sincrónicamente con un único subproceso hasta que se reproduce todo el historial. Las tareas duraderas, que no se completan al final de la reproducción del historial, llevan las acciones correspondientes asociadas. Por ejemplo, un mensaje puede ponerse en cola para llamar a una función de actividad.
La descripción de esta sección sobre el comportamiento del runtime debería ayudarle a comprender por qué una función de orquestador no puede usar las opciones await
o yield
en una tarea no duradera. Esto es debido a dos razones: el subproceso del distribuidor no puede esperar a que finalice la tarea, y cualquier devolución de llamada de esa tarea podría crear un error en el estado de seguimiento de la función de orquestador. Existen algunas comprobaciones de tiempo de ejecución que le permitirán detectar estas infracciones.
Para obtener más información acerca de cómo Durable Task Framework ejecuta las funciones de orquestador, consulte el código fuente de Durable Task en GitHub. En concreto, consulte TaskOrchestrationExecutor.cs y TaskOrchestrationContext.cs.