Depuis .NET 5.0, le SDK Azure Functions offre deux approches pour créer et exécuter des applications de fonctions Azure : in-process et isolated process.
In-process
Le mode In-process existe depuis la première version du runtime Azure Functions (Functions 1.0). Ce mode permet de créer une application de fonctions en utilisant un projet de type bibliothèque de classes. De ce fait, notre fonction est étroitement intégrée avec son environnement d’exécution dans Azure. L’avantage de cette approche est le fait que les fonctions pourront partager des API et des types de liaison. Mais, ce couplage fort fait en sorte que :
- les fonctions doivent s’exécuter sur la même version .NET que le runtime Azure Functions;
- Pas de prise en charge native de l’injection de dépendances. Il est toutefois possible de mettre cela en place, mais vous devez passer par un modèle d’injection de dépendances personnalisé qui demande un certain effort de mise en place;
- Aucune prise en charge des intergiciels;
- Pas de contrôle au niveau du processus (démarrage de l’application, configuration d’intergiciel, etc.);
- Moins de flexibilité : vous devez utiliser les mêmes versions d’assemblys que le processus hôte.
Il faut noter que .NET est le seul SDK d’Azure Functions qui utilise le mode In-process. Les autres SDK dont Java, Python ou encore JavaScript reposent sur le mode isolated process (out of process)
Isolated process
Le mode hors processus a été introduit avec le runtime Azure Functions 3.x. Cette version du runtime supporte .NET 5.0 et .NET 3.1. Mais le mode hors processus est uniquement disponible à partir de .NET 5.0.
Ce mode permet désormais de créer et exécuter des fonctions C# dans le processus isolé, comme cela est le cas pour JavaScript, PowerShell, etc.
Avec ce mode, nous avons un contrôle total du processus : vous contrôlez le démarrage de l’application, ainsi que les configurations utilisées et l’intergiciel démarré.
La mise en place de l’injection de dépendances est très simplifiée. Étant donné que vous contrôlez totalement le processus, vous pouvez utiliser les comportements .NET actuels pour injecter des dépendances et incorporer l’intergiciel dans votre application de fonctions.
Par ailleurs, ce mode offre moins de conflits : les fonctions s’exécutant dans un processus distinct, les assemblys utilisées dans votre application ne sont pas en conflit avec une version différente des mêmes assemblys utilisées par le processus hôte.
A la sortie de .NET 5 avec le mode Isolated process, Microsoft a programmé la fin du mode In-process qui allait encore être disponible pour la dernière fois avec .NET 6.
Le mode Isolated process était encore dépourvu des fonctionnalités essentielles comme le support des fonctions durables. C’est désormais le cas avec .NET 7.0 qui est sorti en fin d’année dernière et qui n’offre pas de prise en charge du mode in-process.
Dans ce billet de blog, nous verrons comment créer une fonction durable en utilisant .NET 7.0
Introduction aux fonctions durables
Durable Functions est une extension d’Azure Functions. Alors qu’Azure Functions s’exécute dans un environnement sans état, Durable Functions peut conserver les informations d’état entre les appels de fonction. Cette approche vous permet de simplifier les exécutions avec état complexes dans un environnement serverless.
Durable Functions s’adapte en fonction des besoins et constitue un moyen économique d’implémenter des workflows complexes dans le cloud.
Les fonctions durables vous permettent d’écrire du code basé sur des évènements. Une fonction durable peut attendre de façon asynchrone un ou plusieurs évènements externes, puis effectuer une série de tâches en réponse à ces évènements.
Il est possible de chainer plusieurs fonctions entre elles. Vous pouvez implémenter des modèles courants, comme le modèle fan-out/fan-in qui utilise une fonction pour appeler d’autres fonctions en parallèle et ensuite cumuler les résultats.
Vous pouvez orchestrer et coordonner plusieurs fonctions, et spécifier leur ordre d’exécution.
L’état des fonctions est géré automatiquement. Vous n’avez pas besoin d’écrire votre propre code pour enregistrer les informations d’état d’une fonction durable.
Modèles d’application de fonctions durables
Chainage de fonctions
Dans le modèle chainage de fonctions, le workflow exécute une séquence de fonctions dans un ordre spécifié. La sortie d’une fonction est appliquée à l’entrée de la fonction suivante dans la séquence. La sortie de la fonction finale est utilisée pour générer un résultat.
Le modèle Fan out/fan in
Le modèle Fan out/fan in exécute plusieurs fonctions en parallèle et attend ensuite que toutes les fonctions aient fini de s’exécuter. Les résultats des exécutions parallèles peuvent être agrégés ou utilisés pour calculer un résultat final.
Le modèle interaction humaine
Le modèle Interaction humaine combine des processus automatisés et des actions à faire manuellement. Inclure un processus manuel dans un processus automatisé est délicat, car les gens ne sont généralement pas autant disponibles et réactifs que des ordinateurs. Une interaction humaine peut être intégrée à l’aide d’une logique de délais d’attente et de compensation qui s’exécute si personne n’interagit correctement dans un délai de réponse spécifiée. Un processus d’approbation est un exemple de processus impliquant une interaction humaine.
Le modèle surveillance
Le modèle surveillance implémente un processus périodique dans un workflow, éventuellement pour détecter un changement d’état. Par exemple, vous pouvez utiliser ce modèle pour continuer l’interrogation jusqu’à ce que certaines conditions soient remplies.
Le modèle API HTTP Async
Le modèle API HTTP Async apporte une solution au problème de coordination de l’état des opérations de longue durée avec des clients externes. Un appel HTTP peut déclencher l’action de longue durée. Ensuite, il peut rediriger le client vers un point de terminaison d’état. Le client peut interroger ce point de terminaison pour savoir quand l’opération est terminée.
Créer une fonction durable avec .NET 7.0
La prise en charge du développement des applications de fonctions durables dans .NET 7.0 souffre encore de plusieurs manquements :
- Disponible encore au stade de préversion;
- Aucun modèle dans .NET 7;
- Manque de plusieurs fonctionnalités.
Pour commencer, nous allons créer une application de fonctions avec Visual Studio 2022. Nous allons prendre le modèle vide comme il n’existe aucun modèle pour les fonctions durables :
Une fois le projet créer, nous devons ajouter la prise en charge des fonctions durables en installant les packages Microsoft.Azure.Functions.Worker.Extensions.DurableTask et Microsoft.DurableTask.Generators :
Nous allons également installer l’extension pour le déclencheur HTTP via le package Microsoft.Azure.Functions.Worker.Extensions.Http.
Il faudra aussi mettre à jour le Worker pour utiliser au minimum la version 1.10. Pour cela, vous devez mettre à jour le package Microsoft.Azure.Functions.Worker.
Une application de fonctions durables est composée d’au minimum trois fonctions :
- Une fonction déclencheur : il s’agit de la fonction qui démarre une instance de l’orchestration et retourne une réponse d’état de vérification.
- Une fonction d’orchestration : c’est la fonction qui gère l’orchestration. L’orchestration démarre suite à son exécution par la fonction déclencheur et exécute la ou les fonctions d’activités en fonctions du modèle utilisé (chaine de fonctions, Fan out/fan in, etc.).
- Une ou plusieurs fonctions d’activités : cette fonction contient la logique métier qui sera orchestrée.
Création de la fonction d’activités
Nous allons commencer par la fonction d’activités, car c’est celle qui n’a aucune dépendance avec les autres fonctions. Cette fonction va implémenter la logique métier de notre application. Pour cet exemple, nous voulons mettre en place une simple fonction qui retourne « Hello »
Nous allons donc créer une classe SayHello.cs avec le code suivant :
using Microsoft.DurableTask; using Microsoft.Extensions.Logging; namespace DurableFunctions { [DurableTask(nameof(SayHello))] public class SayHello : TaskActivity<string, string> { readonly ILogger logger; public SayHello(ILoggerFactory loggerFactory) { logger = loggerFactory.CreateLogger<SayHello>(); } public override Task<string> RunAsync(TaskActivityContext context, string cityName) { logger.LogInformation("Saying hello to {name}", cityName); return Task.FromResult($"Hello, {cityName}!"); } } }
La fonction d’orchestration
Nous allons utiliser le modèle chainage de fonctions. La fonction d’orchestration appelle successivement à plusieurs reprises la fonction d’activités.
Pour implémenter la fonction d’orchestration, nous allons créer une nouvelle classe HelloOrchestration avec le code suivant :
using Microsoft.DurableTask; namespace DurableFunctions { [DurableTask(nameof(HelloOrchestration))] public class HelloOrchestration : TaskOrchestrator<string?, string> { public async override Task<string> RunAsync(TaskOrchestrationContext context, string? input) { string result = ""; result += await context.CallSayHelloAsync("Tokyo") + " "; result += await context.CallSayHelloAsync("London") + " "; result += await context.CallSayHelloAsync("Seattle"); return result; } } }
La package Microsoft.DurableTask.Generators ajoute des fonctionnalités permettant de disposer des méthodes d’extension dynamiques (CallSayHelloAsync) qui sont dérivées des noms des classes/fonctions d’activités et pouvant être utilisées pour exécuter celles-ci.
Fonction déclencheur
Nous allons maintenant créer notre fonction déclencheur qui sera exécutée suite à une requête HTTP. Pour cela, nous allons ajouter au projet une nouvelle classe HelloStarter avec le code suivant :
using Microsoft.DurableTask; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; namespace DurableFunctions { public class HelloStarter { [Function(nameof(StartHelloCities))] public static async Task<HttpResponseData> StartHelloCities( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, [DurableClient] DurableClientContext durableContext, FunctionContext executionContext) { ILogger logger = executionContext.GetLogger(nameof(StartHelloCities)); string instanceId = await durableContext.Client.ScheduleNewHelloOrchestrationInstanceAsync(); logger.LogInformation("Created new orchestration with instance ID = {instanceId}", instanceId); return durableContext.CreateCheckStatusResponse(req, instanceId); } } }
La méthode d’extension ScheduleNewHelloOrchestrationInstanceAsync() est également générée dynamiquement par le générateur de source (Microsoft.DurableTask.Generators). Son nom est dérivé du nom de la classe d’orchestration. Cette méthode permettra d’appeler la fonction d’orchestration.
Il ne nous reste plus qu’à exécuter et tester notre application de fonctions.
Conclusion
Nous venons de mettre en place une fonction durable isolée en utilisant .NET 7.0. L’extension pour Durable Functions est encore au stade de préversion, donc ne devrait pas être utilisée dans un environnement de production. Microsoft envisage de sortir la version stable avec .NET 8.0 LTS qui sera disponible en fin de cette année.
Le code source de cet exemple est disponible sur mon GitHub à l’adresse suivante : https://github.com/hinault/durable-functions-isolated