Application sans serveur : les 6 recettes pour bien les développer

Application sans serveur : les 6 recettes pour bien les développer Les offres de functions as a service impliquent une logique de développement bien particulière. Le maître-mot : le découpage du logiciel en micro-services.

Compte tenu de ses particularités, le développement d'applications sous forme de fonctions implique un mode de développement bien particulier. Au sein d'un service cloud de FaaS (pour Function as a Service), les fonctions se présentent sous la forme de micro-containers embarquant le code applicatif. Des containers exécutés en général suite à un événement. Lors de l'appel d'une page web, une série de fonctions pourra par exemple enclencher chacune un service : authentification, interrogation de la base de données, push notification, etc. Elles pourront aussi s'exécuter au regard d'un moteur de règles défini par le programmeur, à une date précise ou suite à la mise à jour de tel ou tel système par exemple.

Le temps d'exécution des fonctions événementielles est restreint. C'est l'une de leur principale limite. Cette durée n'excède pas 10 minutes sur le cloud Azure de Microsoft et 15 minutes sur celui d'Amazon. Idem pour les ressources machines. Chez AWS, les fonctions ne dépassent pas 10 Go de Ram et 6 vCPU, et chez Microsoft 14 Go de Ram et 5 vCPU.

"Compte tenu de toutes ces spécificités, un projet de FaaS n'est pas un développement comme les autres. Il implique la mise en œuvre d'une architecture à fois événementielle, modulaire, découplée et asynchrone. Ce qui nécessite de penser l'application dès le départ, avant de se lancer dans la phase de programmation", insiste Thomas Ruiz, CTO au sein de l'entreprise de services du numérique Neoxia.

1. Découper l'application en micro-services

En amont, les fonctions impliquent de découper l'application en micro-services, chacun correspondant à une fonctionnalité. "Il existe de nombreux moyens de s'abstraire de la limite des fonctions en termes de temps d'exécution et de ressources machines", précise Mario Arnautou, head of engineering au sein de l'entreprise de services du numérique Neoxia. "Cela passe déjà par la gestion orientée événements, voire par la mise en place d'un traitement par lots en vue de paralléliser les traitements. Le cas échéant, un service externe pourra être sollicité pour certaines tâches qui ne pourront être supportées car impliquant un temps d'exécution trop long ou des capacités de calcul trop importantes. Ce service pourra par exemple donner lieu à une mise en mémoire cache ou à une procédure de stockage clé/objet."

Prenons l'exemple de l'envoi de notifications en mode push. En amont, une fonction orchestratrice sera centrée sur le déclenchement du processus. Celle-ci sollicitera tous les utilisateurs à notifier en base de données. Pour chacun d'eux, elle générera la création d'un record qu'elle poussera dans une queue. Cette dernière permettra de gérer la reprise en cas d'échec. En aval, une seconde fonction appellera le service de notification en invoquant, par exemple, l'API de l'outil de push de Firebase. Résultat : l'architecture en question permettra de répartir la charge, tout en étant robuste et résiliente, le tout à un coût relativement faible.

2. Appliquer les bonnes pratiques du software engineering

"Pour pérenniser le projet et faire en sorte qu'il passe à l'échelle, il ne faudra pas négliger les bonnes pratiques générales du software engineering", insiste Mario Arnautou. "Certains développeurs peuvent penser que les fonctions se résument à du scripting. Ce n'est pas le cas. Il est recommandé d'utiliser un superset de typage, de réaliser des tests unitaires, etc." Et le head of engineering de Neoxia de conseiller "de se concentrer sur la qualité et la robustesse du code, d'éviter les duplications, et de favoriser la réutilisation." Enfin, il prévient : "Ce sont des bonnes pratiques assez classiques qu'on peut avoir tendance à oublier compte tenu d'un environnement d'exécution qui est extrêmement simple à utiliser."

3. Estimer la durée d'exécution d'une fonction

Il est important d'évaluer le temps de processing des fonctions. Objectif : ajuster la durée de traitement au regard des limitations affichées. "Au final, le coût correspondra à l'allocation de ressources de calcul multipliée par le temps que va prendre la tâche à s'exécuter", commente Alain Rouen, CTO de la SS2L Smile. Pour être efficace, cette évaluation devra être réalisée au fur et à mesure de la construction de l'architecture et de sa complexification. Pour effectuer les mesures au plus juste, il est recommandé de recourir directement à l'outillage de monitoring proposé par le cloud retenu.

"Mieux vaut mettre un garde-fou. Si le temps d'exécution ne peut aller au-delà de 15 minutes, il est conseillé de se laisser une marge en misant sur 12 minutes par exemple, notamment si le niveau de souplesse en termes de parallélisation atteint ses limites", souligne Mario Arnautou chez Neoxia. "Une fois les 12 minutes atteintes, on pourra stocker le maximum d'informations sur l'état de la charge. L'objectif étant d'avoir tous les éléments en main pour lancer une fonction de reprise après échec."

4. Recourir à la gestion événementielle

Les fonctions sont historiquement conçues pour répondre à des événements. "Dans les applications FaaS complexes, la gestion événementielle est une bonne pratique. Elle permet de réaliser la captation et l'enclenchement des traitements le plus vite possible, avec à la clé un stockage en base de données ou dans la file d'attente d'un middleware. In fine, l'objectif est de se donner les moyens d'exécuter une série de fonctions en chaîne en vue de gérer un process métier par exemple", rappelle Mario Arnautou. "C'est la logique des Step Functions chez AWS. Cette méthode consiste à découper un processus en étape, en limitant la durée d'exécution de chacune, avec à la clé un logique d'orchestration."

5. Exposer des fonctionnalités de front office

Parmi les points forts d'AWS, son service de FaaS, Lambda, propose une déclinaison en mode edge. Un service dont les développeurs peuvent se saisir pour exécuter des fonctions en frontière de réseau. En ligne de mire : optimiser les fonctionnalités ou les contenus poussés, par le biais d'API, au cœur des interfaces graphiques JavaScript modernes ou des objets connectés, au plus proche de l'utilisateur final. Dans ce contexte, les fonctions prennent la forme d'appels d'API GraphQL qui définissent le modèle de données.

"Dans certains schémas d'architecture, des fonctions vont générer du code HTML, notamment dans une logique de progressive web app. C'est le cas par exemple avec le framework NextJS", détaille Alain Rouen. Concrètement, le squelette de l'application sera généré à une fréquence basse quand d'autres éléments seront poussés via API. A mi-chemin entre les deux, une fonction pourra aussi être déclenchée suite à la mise à jour d'une donnée dans un CMS en vue de pousser cette donnée de manière statique sur un frontal web. "On inverse ainsi la logique de la page internet puisque celle-ci n'est pas mise à jour au moment de son chargement mais lors du déclenchement de la fonction ", résume Alain Rouen.

6. Le choix du langage de programmation

Selon le baromètre 2023 de Datadog sur le serverless, Python et NodJS sont de loin les langages les plus utilisés pour développer des fonctions. "NodJS est un langage mono-thread qui est taillé pour un démarrage rapide et des exécutions courtes. Il convient par conséquent parfaitement aux fonctions. En matière de FaaS, il est très utilisé dans le cadre d'applications purement digitales", constate Alain Rouen. Et Mario Arnautou d'ajouter : "Chez Neoxia, nous aurons tendance à partir sur JavaScript pour le développement d'API ou de plateforme."

Quant à Python, il est plébiscité par les développeurs serverless, encore et toujours, pour sa simplicité d'accès et sa richesse en matière de traitement data et IA. Reste qu'en matière d'intelligence artificielle, le recours au machine learning dans les fonctions sera limité par le faible poids de ces dernières. Elles ne supporteront que des modèles de machine learning prédictifs simples et peu consommateurs de mémoire, de type classification ou régression par exemple.