Contraintes de code des fonctions d’orchestrateur

Durable Functions est une extension d’Azure Functions qui vous permet de créer des applications avec état. Vous pouvez utiliser une fonction d’orchestrateur pour orchestrer l’exécution d’autres fonctions durables dans une application de fonction. Les fonctions d’orchestrateur présentent un état, sont fiables et peuvent être de longue durée.

Contraintes du code d’orchestrateur

Les fonctions d’orchestrateur utilisent l’approvisionnement en événements pour garantir une exécution fiable et maintenir l’état des variables locales. Le comportement de réexécution du code d’orchestrateur crée des contraintes sur le type de code qui peut être écrit dans une fonction d’orchestrateur. Par exemple, les fonctions d’orchestrateur doivent être déterministes : une fonction d’orchestrateur sera réexécutée à plusieurs reprises et doit générer le même résultat à chaque fois.

Utilisation des API déterministes

Cette section fournit des instructions simples vous permettant de vous assurer que votre code est déterministe.

Les fonctions d’orchestrateur peuvent appeler n’importe quelle API dans son langage cible. Toutefois, il est important qu’elles appellent uniquement des API déterministes. Une API déterministe est une API qui retourne toujours la même valeur pour la même entrée, quel que soit le moment ou la fréquence où elle est appelée.

Les sections suivantes fournissent des instructions sur les API et les modèles que vous devez éviter, car ils ne sont pas déterministes. Ces restrictions s’appliquent uniquement aux fonctions d’orchestrateur. Les autres types de fonction n’ont pas de telles restrictions.

Notes

Plusieurs types de contraintes de code sont décrits ci-dessous. Cette liste n’est malheureusement pas exhaustive et certains cas d’usage peuvent ne pas être couverts. La chose la plus importante à prendre en compte lors de l’écriture de code d’orchestrateur est de savoir si une API que vous utilisez est déterministe. Une fois que vous êtes à l’aise avec cette réflexion, vous saurez facilement quelles API sont sécurisées et celles qui ne le sont pas sans devoir vous référer à cette liste documentée.

Dates et heures

Les API qui retournent la date ou l’heure actuelle ne sont pas déterministes et ne doivent jamais être utilisées dans les fonctions d’orchestrateur. En effet, chaque relecture de fonction d’orchestrateur produit une valeur différente. Vous devez plutôt utiliser l’API Durable Functions équivalente pour obtenir la date ou l’heure actuelle, qui reste cohérente entre les relectures.

N’utilisez pas DateTime.Now, DateTime.UtcNow ou des API équivalentes pour obtenir l’heure actuelle. Les classes telles que Stopwatch doivent également être évitées. Pour les fonctions d’orchestrateur in-process .NET, utilisez la propriété IDurableOrchestrationContext.CurrentUtcDateTime pour obtenir l’heure actuelle. Pour les fonctions d’orchestrateur isolées .NET, utilisez la propriété TaskOrchestrationContext.CurrentDateTimeUtc pour obtenir l’heure actuelle.

DateTime startTime = context.CurrentUtcDateTime;
// do some work
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);

GUID et UUID

Les API qui retournent un GUID ou un UUID aléatoire ne sont pas déterministes, car la valeur générée est différente à chaque réexécution. Selon le langage que vous utilisez, une API intégrée pour générer des GUID déterministes ou des UUID peut être disponible. Sinon, utilisez une fonction d’activité pour retourner un GUID ou un UUID généré de manière aléatoire.

N’utilisez pas d’API telles que Guid.NewGuid() pour générer des GUID aléatoires. Utilisez plutôt l’API NewGuid() de l’objet de contexte pour générer un GUID aléatoire sécurisé pour la relecture d’orchestrateur.

Guid randomGuid = context.NewGuid();

Notes

Les GUID générés avec des API de contexte d’orchestration sont des UUID de type 5.

Nombres aléatoires

Utilisez une fonction d’activité pour retourner des nombres aléatoires à une fonction d’orchestrateur. Les valeurs de retour des fonctions d’activité sont toujours sécurisées pour la relecture, car elles sont enregistrées dans l’historique d’orchestration.

Vous pouvez également utiliser un générateur de nombres aléatoires avec une valeur initiale fixe directement dans une fonction d’orchestrateur. Cette approche est sécurisée tant que la même séquence de nombres est générée pour chaque relecture d’orchestration.

Liaisons

Une fonction d’orchestrateur ne doit utiliser aucune liaison, même les liaisons du client d’orchestration et du client d’entité. Utilisez toujours des liaisons d’entrée et de sortie à partir d’une fonction cliente ou d’activité. Cela est important, car les fonctions d’orchestrateur peuvent être relues plusieurs fois, provoquant des E/S non déterministes et dupliquées avec des systèmes externes.

Variables statiques

Évitez d’utiliser des variables statiques dans les fonctions d’orchestrateur, car leurs valeurs peuvent changer au fil du temps, générant un comportement d’exécution non déterministe. Utilisez plutôt des constantes ou limitez l’utilisation des variables statiques aux fonctions d’activité.

Notes

Même en dehors des fonctions d’orchestrateur, l’utilisation de variables statiques dans Azure Functions peut être problématique pour diverses raisons, car il n’existe aucune garantie que l’état statique persiste entre plusieurs exécutions de fonction. Les variables statiques doivent être évitées, sauf dans des cas d’usage très spécifiques, tels que la mise en cache en mémoire optimale dans les fonctions d’activité ou d’entité.

Variables d'environnement

N’utilisez pas de variables d’environnement dans les fonctions d’orchestrateur. Leurs valeurs peuvent changer au fil du temps, générant un comportement d’exécution non déterministe. Si une fonction d’orchestrateur a besoin d’une configuration définie dans une variable d’environnement, vous devez passer la valeur de configuration à la fonction d’orchestrateur comme entrée ou comme valeur de retour d’une fonction d’activité.

Réseau et HTTP

Utilisez les fonctions d’activité pour effectuer des appels réseau sortants. Si vous devez effectuer un appel HTTP à partir de votre fonction d’orchestrateur, vous pouvez également utiliser les API HTTP durables.

API de blocage de threads

Les API de blocage comme « sleep » peuvent entraîner des problèmes de performances et de mise à l’échelle pour les fonctions d’orchestrateur et doivent être évitées. Dans le plan de consommation Azure Functions, elles peuvent même entraîner des frais d’exécution inutiles. Dans la mesure du possible, utilisez des alternatives aux API de blocage. Par exemple, utilisez des retardateurs durables pour créer des retards sécurisés pour la relecture et ne comptent pas dans le temps d’exécution d’une fonction d’orchestrateur.

API asynchrones

Le code d’orchestrateur ne doit jamais démarrer d’opération asynchrone, sauf celles définies par l’objet de contexte du déclencheur d’orchestration. Par exemple, n’utilisez jamais Task.Run, Task.Delay et HttpClient.SendAsync dans .NET, ou setTimeout et setInterval dans JavaScript. Une fonction d’orchestrateur ne doit planifier que le travail asynchrone à l’aide des API du Kit de développement logiciel (SDK) Durable, comme la planification des fonctions d’activité. Tout autre type d’appel asynchrone doit être effectué dans les fonctions d’activité.

Fonctions JavaScript asynchrones

Déclarez toujours les fonctions d’orchestrateur JavaScript comme fonctions de générateur synchrones. Vous ne devez pas déclarer des fonctions d’orchestrateur JavaScript comme async, car le runtime node.js ne garantit pas que les fonctions asynchrones sont déterministes.

Coroutines Python

Vous ne devez pas déclarer des fonctions d’orchestrateur Python en tant que coroutines. En d’autres termes, ne déclarez jamais des fonctions d’orchestrateur Python avec le mot clé async, car la sémantique de coroutine ne s’aligne pas sur le modèle de réexécution Durable Functions. Vous devez toujours déclarer des fonctions d’orchestrateur Python en tant que générateurs, ce qui signifie que vous devez vous attendre à ce que l’API context utilise yield au lieu de await.

API de thread .NET

Durable Task Framework exécute le code d’orchestrateur sur un thread unique et ne peut pas interagir avec d’autres threads. L’exécution de continuations asynchrones sur un thread de pool Worker sur l’exécution d’une orchestration peut entraîner une exécution non déterministe ou des blocages. Pour cette raison, les fonctions d’orchestrateur ne doivent quasiment jamais utiliser des API de thread. Par exemple, n’utilisez jamais ConfigureAwait(continueOnCapturedContext: false) dans une fonction d’orchestrateur. Cela garantit que les continuations de tâches s’exécutent sur le SynchronizationContext d’origine de la fonction d’orchestrateur.

Notes

Durable Task Framework tente de détecter l’utilisation accidentelle de threads non orchestrateurs dans les fonctions d’orchestrateur. Si une violation est détectée, le framework lève une exception NonDeterministicOrchestrationException. Certaines violations peuvent toutefois échapper à ce comportement de détection. Vous ne devez donc pas en dépendre.

Gestion de version

Une orchestration durable peut s’exécuter en continu pendant des jours, des mois, des années voire même indéfiniment. Toutes les mises à jour de code apportées aux applications Durable Functions qui affectent des orchestrations non terminées peuvent perturber le comportement de réexécution de l’orchestration. Il est donc important de planifier soigneusement les mises à jour du code. Pour obtenir une description plus détaillée de la façon de gérer les versions de votre code, consultez l’article sur le contrôle de versions.

Tâches durables

Notes

Cette section décrit en détail l’implémentation interne de Durable Task Framework. Vous pouvez utiliser des fonctions durables sans disposer de ces informations. Elles sont uniquement destinées à vous aider à mieux comprendre le comportement de réexécution.

Les tâches qui peuvent attendre en toute sécurité dans les fonctions d’orchestrateur sont parfois appelées des tâches durables. Durable Task Framework crée et gère ces tâches. Les tâches retournées par CallActivityAsync, WaitForExternalEvent et CreateTimer dans les fonctions d’orchestrateur .NET en sont des exemples.

Ces tâches durables sont gérées en interne par une liste d’objets TaskCompletionSource en .NET. Lors de la réexécution, ces tâches sont créées dans le cadre de l’exécution du code d’orchestrateur. Elles s’achèvent à mesure que le répartiteur énumère les événements d’historique correspondants.

Les tâches s’exécutent de manière synchrone à l’aide d’un seul thread tant que tout l’historique n’a pas été réexécuté. Les tâches durables non terminées à la fin de la réexécution de l’historique font l’objet d’actions appropriées. Par exemple, un message peut être empilé pour appeler une fonction d’activité.

La description du comportement d’exécution faite dans cette section aide à comprendre pourquoi une fonction d’orchestrateur ne peut pas utiliser await ou yield dans une tâche non durable. Il y a deux raisons à cela. Tout d’abord, le thread de répartiteur ne peut pas attendre la fin de la tâche. D’autre part, tout rappel par cette tâche risquerait d’altérer l’état de suivi de la fonction d’orchestrateur. Certaines vérifications de l’exécution sont mises en œuvre pour détecter de telles violations.

Pour plus d’informations sur la façon dont Durable Task Framework exécute les fonctions d’orchestrateur, consultez le code source des tâches durables sur GitHub. Consultez en particulier TaskOrchestrationExecutor.cs et TaskOrchestrationContext.cs.

Étapes suivantes