Le Blog Amazon Web Services

De “Poll” à “Push”: Transformez une API avec Amazon API Gateway REST APIs et les WebSockets

Vous déployez une application web et souhaitez donner à un nombre important d’utilisateurs, un accès contrôlé aux données analytiques? Ou peut-être avez-vous un site de vente en ligne permettant la prise de commandes, ou une application permettant aux utilisateurs de se connecter à des sources de données tierces avec un temps d’accès potentiellement long. L’ensemble de ces cas d’usage nécessitent de pouvoir gérer un temps long de réception d’une réponse après l’envoi d’une requête.

Ce billet a pour but de montrer comment optimiser ses requêtes longues sans avoir à interroger de manière récurrente le point d’accès (ci-après « polling ») . Dans un premier temps, nous présentons les défis inhérents au modèle d’interrogation continuelle, et les approches alternatives pour résoudre ces problématiques de diffusion d’information. Nous montrerons ensuite comment construire et déployer une solution dans votre propre environnement AWS.

Voici un premier aperçu de l’application exemple que vous pourrez déployer dans votre environnement AWS :

Mise à jour

Afin de rendre cette solution plus scalable, ce billet a été mis à jour pour mettre en correspondance les ARNs d’exécution des AWS Step Functions avec les API Gateway WebSocket ConnectionIds des clients connectés. Cela permet de s’assurer de la scalabilité de la solution lors de l’envoi en retour (push back) des données aux clients connectés avec un grand nombre de connexions concurrentes. Vous pouvez déployer cet exemple sur votre compte AWS en utilisant l’application PollToPush dans AWS Serverless Application Repository.

Quelles sont les problématiques liées au « polling » ?

De nombreux clients ont à implémenter  des applications qui indiquent le status ou la progression d’opérations longues (comme des requêtes sur un entrepôt de données, ou l’exécution d’une commande d’achat). Ils ont pu alors développer une solution de “polling” similaire à celle-ci :

  1. Envoi d’une requête POST,
  2. Requête GET et réception d’une réponse vide,
  3. Une autre… GET et réception d’une réponse vide,
  4. Encore une autre… GET et réception d’une réponse vide,
  5. Finalement, GET et enfin réception des données demandées.

Problématiques liés aux méthodes de polling traditionnelles

  • Échanges et coûts inutiles pour avoir des résultats — Chaque requête effectuée par le client sur l’API ajoute un surcoût dû à l’utilisation de l’infrastructure sous-jacente pour calculer le résultat. Les réponses vides ne sont que du gaspillage !
  • Réduction de la durée de vie de la batterie — Le polling excessif est un des principaux facteurs de réduction de la durée de vie et de l’autonomie de la batterie. Se retrouver dans la “liste de la honte” des principaux utilisateurs de la batterie peut entraîner la suppression de votre application.
  • Délai dans la mise à disposition des données — Certaines approches de polling impliquent la mise en place d’un délai graduel entre chaque requête pour limiter les réponses vides, d’où un délai entre la disponibilité des données et leur arrivée.

Qu’en est-il du “long polling” ?

  • Réduction des performances applicatives à cause de requêtes bloquées — Les réponses synchrones de longue durée peuvent induire des temps d’attente inattendus pour l’utilisateur ou des blocages de l’UI, en particulier sur les appareils mobiles,
  • Pannes applicatives à cause des surconsommations de mémoire — Garder des requêtes à longue durée ouvertes sur vos serveurs peuvent les surcharger et provoquer des scénarios catastrophe, et causer des pannes dans l’application,
  • Expériences utilisateur inconsistantes à cause des valeurs par défaut des délais d’expiration HTTP — Ces délais varient selon le navigateur utilisé, et sont à la source d’une expérience inconsistante pour les utilisateurs finaux. Selon la taille et la complexité des requêtes, le traitement peut durer plus longtemps que ces données et prendre plusieurs minutes avant de renvoyer ses résultats.

Pour remédier à cela, créez une architecture événementielle et transformez vos APIs du “poll” au “push”.

Modèle “push” asynchrone

Afin de fournir une expérience utilisateur optimale, les développeurs d’application s’efforcent souvent de proposer des expériences progressives et réactives aux utilisateurs. Ceux-ci peuvent interagir avec les éléments applicatifs (par exemple, un bouton à cliquer) avec un délai minimal lors de l’envoi de requête et la réception de données. Mais les développeurs d’application souhaitent aussi que l’utilisateur puisse recevoir des données en temps opportun, sans pour autant sacrifier d’autres actions ni avoir recours à des traitements inutiles.

L’apparition ces dernières années des Microservices et du Cloud a permis aux développeurs d’application et de service de répondre à ces problématiques de manière asynchrone. Cela permet aux ressources virtuellement illimitées du cloud de chorégraphier le traitement des données. Cela permet aussi aux clients de bénéficier d’expériences utilisateur progressives et réactives.

Il s’agit d’une alternative nouvelle au modèle de conception synchrone, qui repose souvent sur les clients pour agir en temps que pilote des requêtes utilisateur. Le diagramme ci-dessous compare les flux de communication de ces deux modèles :

L’orchestration asynchrone peut être facilement chorégraphiée en utilisant les définitions de flux de travail, la surveillance, et la console de suivi des machines d’état AWS Step Functions. Vous pouvez décomposer vos services en fonctions avec AWS Lambda et suivre leurs exécution à l’aide d’un diagramme d’état tel que celui-ci :

Grâce à cette approche, le client envoie une requête qui initie un traitement distribué en aval. Les étapes suivantes sont invoquées en fonction du flux de travail de la machine d’état et chaque exécution est surveillée.

Parfait, mais comment le client a-t-il accès aux résultats de ces traitements ?

Une problématique, plusieurs approches

Plusieurs solutions sont possibles pour donner accès aux résultats des traitements. Afin de fournir la solution optimale, le responsable du service peut avoir à poser plusieurs questions.

Existe-t-il une relation de confiance avec le client (comme avec un autre microservice) ?

Si la réponse est positive, une solution consiste à utiliser Amazon SNS. De cette manière, les clients peuvent s’abonner à des rubriques et recevoir les données par email, SMS, ou même point de terminaison HTTP/HTTPS en tant qu’abonné (autrement dit, des “webhooks”).
Grâce aux “webhooks”, le client crée un point de terminaison sur lequel le service peut appeler une fonction callback utilisant très peu de ressources côté clients. Le client attend une requête entrante pour faciliter le traitement en aval.

  1. Envoi de la requête,
  2. Abonnement du client à la rubrique,
  3. Mise à disposition du point de terminaison,
  4. SNS envoie la requête POST au point de terminaison.

Si aucune relation de confiance n’existe, alors les clients ne peuvent pas fournir un point de terminaison HTTP après avoir envoyé la requête initiale. Dans ce cas, il est nécessaire d’ouvrir le webhook. Considérez un framework alternatif.

Êtes vous restreint à utiliser uniquement un protocole REST ?

Certaines applications peuvent n’avoir accès qu’à des requêtes HTTP. C’est par exemple le cas de l’utilisation de technologies ou de navigateurs d’un certain âge, ou d’exigences en matière de sécurité bloquant d’autres protocoles.

Dans ces cas d’usages, les clients peuvent utiliser une méthode GET comme meilleure pratique, mais il est toujours recommandé d’éviter le polling. Des questions de conception peuvent alors se poser: est-ce que la préparation des données se fait à un instant prédéterminé ou avec un intervalle de temps défini? L’utilisateur peut-il tolérer un délai entre la préparation des données et leur livraison?

Si les réponses à ces deux questions sont positives, envisagez comme solution l’envoi d’appel GET à votre API REST une seule fois. Par exemple, si la préparation des données dure généralement moins de 10 minutes, effectuez l’appel GET 10 minutes après la soumission de la requête. Cela parait simple, non? Bien plus simple que le polling.

Dois-je utiliser GraphQL, une websocket, ou un autre framework ?

Chaque framework vient avec ses compromis.
Si vous souhaitez un schéma de requête plus flexible, GraphQL, qui suit une approche “UI pilotée par les données”, est une solution envisageable. Si votre interface utilisateur est pilotée par les données, GraphQL peut être la meilleure solution.

  • AWS AppSync est un service GraphQL entièrement géré qui supporte la mise en place de ces concepts. Il fournit des fonctionnalités telles que l’intégration avec les services AWS, la synchronisation des données hors ligne, et la résolution des conflits. Au sein de GraphQL, le concept d’abonnement (“Subscriptions”) permet aux clients de s’abonner à des évènements liés à des modifications de données.
  • Amazon API Gateway simplifie le déploiement d’APIs sécurisées à l’échelle pour les développeurs. Avec l’annonce récente des API Gateway WebSocket APIs, les clients web et mobiles et les services associés peuvent communiquer au travers d’une connexion WebSocket. Cela permet aussi aux clients d’être plus réactifs aux mises à jour des données, et de n’effectuer le traitement qu’une seule fois, lorsqu’une mise à jour est reçue via une connexion WebSocket.

L’approche usuelle côté applicatif est de créer un composant UI qui est mis à jour lorsque le traitement est terminé. Cela s’avère bénéfique par rapport à un rafraichissement complet de la page web et améliore l’expérience utilisateur du consommateur.

Puisque de nombreuses sociétés ont choisi d’utiliser le framework REST pour créer des contrats de services étroitement liés aux APIs, il est possible d’utiliser une interface RESTful pour valider et recevoir la requête. Elle fournit alors un point de terminaison utilisé pour donner le statut de la requête. Cela fournit aussi une flexibilité supplémentaire pour la livraison des résultats à des clients variés, en parallèle de l’API WebSocket.

Solution “Poll-to-push” avec API Gateway

Imaginez un cas d’usage où vous souhaitez une mise à jour de l’interface utilisateur dès que les données sont prêtes. Au lieu de la méthode de polling usuelle décrite ci-dessus, utilisez une API de type API Gateway WebSocket. Elle va pousser les nouvelles données au client lors de leur création, afin qu’elles soient affichées sur l’interface cliente.

Vous pouvez également envisager de déployer un serveur WebSocket sur Amazon EC2. Cette approche nécessite que votre serveur fonctionne en continu pour accepter et maintenir les nouvelles connexions. En outre, vous gérez manuellement la mise à l’échelle de l’instance en cas de forte demande.

En utilisant une API avec API Gateway WebSocket en amont de Lambda, le fonctionnement en continu d’une machine n’est pas nécessaire, ce qui évite de gréver votre budget. API Gateway s’occupe des connexions et d’appeler Lambda à chaque nouvel évènement. La mise à l’échelle est gérée par le service. Les URL de callback API Gateway peuvent être utilisées pour mettre à jour le client connecté au service. Les SDKs AWS simplifient la communication avec le service. Voici un exemple d’utilisation avec Boto3 et post_to_connection :

import boto3 #Use a layer or deployment package to #include the latest boto3 version....
apiManagement = boto3.client('apigatewaymanagementapi', region_name={{api_region}},
                      endpoint_url={{api_url}})
...
response = apiManagement.post_to_connection(Data={{message}},ConnectionId={{connectionId}})
...

Exemple de solution

Pour construire cette solution optimisée, créez une application web simple permettant à un utilisateur d’effectuer une requête sur un volume de données important. Les résultats sont envoyés sous forme de fichier plat via une WebSocket. Dans ce cas d’usage, la requête s’effectue via Amazon Athena sur un datalake S3 contenant les données de sentiments AWS Twitter (Twitter: @awscloud ou #awsreinvent). Cependant, cette solution est applicable à n’importe quel entrepôt de données, ou à une requête nécessitant une longue durée de traitement.

Pour le client, créez l’application web à l’aide d’un framework Javascript (tel que JQuery). Utilisez API Gateway pour accepter une API REST pour les données et ouvrir une connexion WebSocket sur le client qui facilite l’envoi du résultat :

  1. Le client envoie une requête REST à API Gateway. Cela déclenche une fonction Lambda qui démarre l’exécution d’une machine d’état Step Functions. Cette fonction renvoie l’ID d’exécution (Execution ARN) au client,
  2. Avec le résultat de la requête REST, le client se connecte à l’API WebSocket et envoie l’ID d’exécution Step Functions à la connexion WebSocket,
  3. L’API WebSocket déclenche une fonction Lambda qui enregistre dans DynamoDB la correspondance entre Execution ARN et ConnectionId,
  4. Une fois la fonction RunAthenaQuery exécutée avec succès, Lambda récupère le ConnectionId associé à l’Execution ARN courant dans DynamoDB,
  5. A l’aide du ConnectionId associé au client de l’API WebSocket API Gateway, Lambda informe le client connecté via la WebSocket API que le traitement est terminé. La fonction utilise pour cela l’appel API REST post_to_connection,
  6. Le client reçoit les résultats S3 via sa connection WebSocket à l’aide de la fonction Lambda IssueCallback, grâce à l’URL de callback de l’API WebSocket API Gateway.

Dans cet exemple d’application, la réponse est une URL S3 présignée contenant les résultats de la requête Athena. Le client peut donc utiliser ce lien S3 pour télécharger les données.

Pourquoi ne pas ouvrir la Websocket pour la requête ? Bien que cela puisse fonctionner, ce n’est pas recommandé sur ce cas d’usage.

Séparez les interfaces nécessaires en trois étapes:

  1. Envoi de la requête,
  2. Récupération du statut de la requête (pour la détection d’erreurs),
  3. Envoi des résultats.

En ce qui concerne l’interface d’envoi de la requête, les APIs RESTful disposent de contrôles rigoureux pour s’assurer que la requête de données de l’utilisateur est valide et reçoit bien les garde-fous correspondant au but de la requête. Cela permet d’éviter les demandes non désirées et d’avoir un impact imprévu sur les environnements d’analyse, en particulier lorsqu’ils sont exposés à un grand nombre d’utilisateurs.

Dans cet exemple, les requêtes de données sont restreintes à des pays spécifiques au niveau du client Javascript. Utiliser la méthode RESTful POST pour les requêtes API vous permet de valider les données à l’aide des paramètres de requête, comme ci-dessous :

http://<apidomain>.amazonaws.com/Demo/CreateStateMachineAndToken?Country=France

Les modèles et templates de mapping API Gateway peuvent aussi servir à valider ou transformer le contenu des requêtes au niveau de la couche API avant qu’elle n’atteigne le service. Utilisez les modèles pour vous assurer que la structure et le contenu des requêtes clientes correspondent à ce qui est attendu. Utilisez les templates de mapping pour transformer le contenu envoyé par les clients dans un format reconnu par le service.

Ce framework de validation REST est aussi utilisable pour détecter les informations de l’entête concernant la compatibilité Websocket du navigateur. Si l’utilisation de WebSocket offre de nombreux avantages, tous les navigateurs ne le supportent pas, en particulier les plus anciens. Ainsi, une requête API REST peut envoyer ces informations sur le navigateur et déterminer si une WebSocket peut être ouverte.

Comme une interface REST est déjà créé pour soumettre la requête, vous pouvez aisément ajouter une requête GET au cas où le client devrait effectuer une requête pour connaître le statut de la machine d’état Step Functions. Cela peut être le cas si une vérification de la santé du service prend plus de temps que prévu. Vous pouvez aussi ajouter une méthode GET supplémentaire comme moyen d’accès alternatifs pour les clients compatibles uniquement en REST.

Si des requêtes et récupération de données à latence faible sont une caractéristique importante de votre API et qu’il n’y a pas de risque de compatibilité du navigateur, utiliser une API WebSocket avec un modèle de sélection du modèle JSON pour protéger votre service avec un schéma.

Dans l’esprit de choisir l’outil adéquat pour une tâche donnée, utilisez une API REST pour la couche de requêtes et une API WebSocket pour attendre et accéder aux résultats.

Pour explorer plus loin cette solution, vous pouvez déployer cette solution au sein de votre propre environnement AWS, suivez les instructions du dépôt PollToPush dans l’AWS Serverless Application Repository.

Conclusion

Lorsqu’une application cliente utilise le polling sur des tâches de longue durée, cela peut induire un gaspillage et une utilisation préjudiciable et coûteuse des ressources. Ce billet présente différentes manières de refactoriser la méthode de polling. Utilisez API Gateway pour héberger une interface RESTful, les Step Functions pour orchestrer le flux de travail, Lambda pour effectuer les traitements côté service, et une API WebSocket API Gateway pour pousser les résultats à vos clients.

Post initiatialement publié par Adam Westrich – AWS Principal Solutions Architect & Ronan Prenty – Cloud Support Engineer. Traduit en Français par Olivier Chappe, Architecte Solutions AWS Game Tech, LinkedIn.

Source