Traitements Asynchrones avec Symfony : retour sur le SymfonyLive 2019
3 avril 2019
Nous nous sommes rendus au SymfonyLive 2019 qui s’est déroulé à Paris les 29 et 30 mars derniers. A cette occasion, plusieurs conférences ont souvent mis en application un nouveau composant de Symfony permettant de faire des traitements asynchrones.
Depuis sa version 4.1, Symfony intègre le composant Messenger. Ce composant facilite grandement les traitements asynchrones qui permettent, par exemple, d’assurer des gros volumes de traitements tout en garantissant un temps de réponse instantané à l’utilisateur.
Le cas d’usage
Imaginons qu’un utilisateur historique d’une boutique en ligne souhaite recevoir une archive contenant toutes ses factures (environ 1000). La plupart des factures doivent être créées en PDF, ajoutées à l’archive et cette dernière doit être envoyée par mail en fin de traitement.
Si nous le faisions de manière synchrone, la requête se matérialiserait comme ceci :
Le problème dans ce cas-là, c’est qu’il y’a de fortes chances pour que notre requête ressemble surtout à cela avant d’avoir terminé l’archive :
En effet, les serveurs HTTP ne sont pas conçus pour attendre de manière indéfinie que le traitement d’une requête se termine. Outre l’engorgement réseau et le blocage de la machine que cela engendre, nous devons attendre la fin du traitement pour répondre à l’utilisateur, ce qui serait complètement inacceptable (plusieurs secondes voire minutes). Imaginons en plus qu’une centaine d’utilisateurs font la même demande en même temps et nous pouvons dire aurevoir à notre serveur 🙁
Quelles sont les solutions dans ce cas ?
Les Threads ?
PHP ne bénéficie pas sur support des Threads comme peuvent le faire nativement Java ou ASP. En plus, les Threads permettent bien de “sous-traiter” des tâches mais impliquent souvent une gestion assez fine derrière pour éviter de surcharger la machine… Ce n’est donc pas l’idéal dans notre cas non plus.
API Node.js ?
Pourquoi pas ! En plus on peut facilement scaler la chose en multipliant les serveurs pour répondre à de plus en plus de demandes ! “Oui mais”… Deux problèmes majeurs se posent alors : notre code est dupliqué à deux endroits (pour la récupération du client et de ses factures), et notre portefeuille est rarement aussi “scalable” que notre hébergeur…
On fait comment alors ?
Tous les héros ne portent pas forcément de cape : c’est là que le composant Symfony Messenger vient à notre secours ! Ce composant apporte une couche d’abstraction très confortable au développeur autour du concept apporté par les “Messages Brokers”. L’idée est d’envoyer un “Message” dans une file d’attente, qui sera consommé par un Worker (dit Handler).
Dans notre cas, c’est l’outil idéal et ce pour plusieurs raisons:
- Évite la surcharge de la machine: un Worker travaille unitairement, donc tant qu’il n’a pas fini sa tâche, les autres messages restent en file d’attente.
- Scalable à souhait : beaucoup de Message Brokers sont disponibles sur le marché – RabbitMQ, ActiveMQ ou AWS SQS pour ne citer que les plus connus, avec beaucoup de choix d’architecture possible, failover, etc.
- Le Message et le Worker sont codés au même endroit : au sein de notre application Symfony ! Tout est donc accessible ET testable: services, repositories, entités…
Il ne nous reste plus qu’à adapter le message pour notre utilisateur, et nous pouvons coder. Voici à quoi va ressembler le scénario final:
- Utilisateur HTTP GET /invoices/archive
- Serveur envoie un message en file d’attente: “Générer archive pour client #42”
- L’utilisateur voit une page lui indiquant que son archive va lui être envoyée par mail prochainement.
Et voilà ! Notre Worker prendra le relais dès qu’il recevra le message, constituera l’archive et l’enverra par mail une fois terminée. Voyons maintenant comment implémenter tout ça dans notre application…
Le Message
Notre Worker n’aura besoin que de l’ID de l’utilisateur afin de générer l’archive. Il est généralement conseillé de créer des messages les plus légers possibles car ils seront sérialisés pour le transport dans la file d’attente.
Le Worker
Nous allons maintenant créer le Worker qui fera le plus gros du travail.
Le Dispatch
Maintenant que notre Message et son Handler sont prêts, nous allons pouvoir faire fonctionner tout ça très simplement directement dans notre Controller:
Tada! Vous pouvez déjà tester votre code, sans rien faire de plus. Oui oui, ça fonctionne déjà 🙂
Les plus attentifs d’entre vous auront remarqué que certes, ça fonctionne, mais nous sommes encore en mode synchrone… Pratique pour le développement, mais toujours problématique comme au début de notre exemple. Il nous faut donc maintenant ajouter un Transport afin que notre file d’attente soit externalisée de notre processus Web.
Le Transport
Plusieurs Transports existent par défaut. Il est bien sûr possible de créer son propre Transport si nécessaire. Celui qui nous sera utile ici sera le transport générique “AMQP” qui fonctionne avec la plupart des Message Brokers que nous avons cités :
Il nous reste à lancer le démon qui permet de traiter les messages en file d’attente, et voilà : notre application Symfony est maintenant prête à faire de l’asynchrone !
En production, ce démon devra être lancé par un superviseur pour qu’il soit relancé/monitoré en cas de crash (supervisor par exemple). Une fois lancé, il tourne avec le code déployé à ce moment-là. Il faudra donc penser à le redémarrer après chaque déploiement de l’application.
Le Routing
Le composant Messenger donne également la possibilité de router chaque Message vers des Broker différents. Ce besoin peut arriver si par exemple nous souhaitons envoyer les emails depuis un autre serveur. Nous enverrons alors depuis notre CreateArchiveHandler un nouveau Message qui se chargera d’envoyer le message et transitera dans un Transport différent.
J’espère que cette (petite) introduction à Symfony Messenger vous donnera envie de dé-synchroniser les tâches gourmandes en ressources de vos applications. Sachez que beaucoup de configurations sont possibles, tant pour Messenger que pour vos Message Brokers, afin de rendre votre architecture hautement disponible et scalable.
POUR ALLER PLUS LOIN
Découvrez notre expertise Symfony
DécouvrirLead Developer