Grails, désynchronisez vos traitements
Pour ceux qui ne connaissent pas Grails, il s’agit de la réponse Java aux outils tel que Ruby on Rails ou Django. Framework MVC jonglant sur le principe du conventions over configuration, il fait parti de ces outils qualifiés de RAD. Et il est efficace pour peu que l’on comprenne que l’on fait du Groovy.
Malgré cette efficacité de développement, Grails reste un framework web avec des contraintes et ses points bloquants. Un de ces points est la durée de la requête en fonction de ce qui est demandé. Prenons le cas d’un envoi de courriel, par exemple une confirmation d’inscription ou de commande. L’envoi d’un courriel nécessite une connexion à un serveur de messagerie, habituellement sur une machine dédiée. Cette opération peut être bien plus longue que les autres pour beaucoup de raisons toutes liées au temps d’accès au service et à la réalisation de celui-ci.
Et en bout de chaîne, l’utilisateur attends et a un comportement dépendant de l’interface, allant donc de l’exaspération (qui n’est pas grave pour notre système) à une excitation compulsive sur le clavier et l’envoi de nouvelles requêtes (et donc du stress sur notre système).
En quoi Grails peut-il nous aider sur cette question ?
Mise en place de l’exemple
Reprenons l’implémentation d’un appel à un serveur de courrier électronique. Un développeur débutant verra la prise en charge par Grails des courriers électroniques et s’empressera d’installer le plug-in par un :
grails install-plugin mail
Puis, il suffit d’ajouter la configuration (par exemple pour gmail) :
grails { mail { host = "smtp.gmail.com" port = 465 username = "youracount@gmail.com" password = "yourpassword" props = ["mail.smtp.auth":"true", "mail.smtp.socketFactory.port":"465", "mail.smtp.socketFactory.class":"javax.net.ssl.SSLSocketFactory", "mail.smtp.socketFactory.fallback":"false"] } }
Enfin, il ne reste plus qu’à faire appel au service, par exemple au pllus simple :
mailService.sendMail { to "recipient@somewhere.com" from "me@g2here.com" subject "Message title" body 'Message content.' }
Et là, on en maîtrise plus rien. L’enchaînement d’écrans va très bien passer sur le poste du développeur, mais pourra être soumis à tellement de risques en production, que le visiteur risque d’être surpris par un caractère long de la requête. Comment s’en prévenir ? On ne peux pas réellement en fait.
Quelle conséquence cela peut il avoir ? Tout dépend de l’utilisateur. Inquiet, il peut réaliser plusieurs actions stressantes sur notre système comme recharger la page ou clicker à plusieurs reprises sur le bouton d’envoi et donc générer de nouvelles requêtes. J’ai déjà vu comme stratégie de prévention la désactivation par Javascript des différents boutons. Cela peut éviter l’envoi multiple mais n’empêche pas l’utilisateur de s’impatienter.
Aussi, il faut désynchroniser l’envoi du courriel du traitement de la requête. Dans une approche SOA classique, un message pourrait être envoyé à une application dédiée à l’envoi de courriels. Mais on va dire qu’on n’a pas ça, on n’a que ce serveur. Alors que faire ?
Et bien en Grails, on peut s’approcher de ce type de traitements grâce aux Jobs.
Mise en place du Job
Les Jobs correspondent à des tâches exécutées à l’initiative d’un ordonnanceur, comparable à Cron. Grails utilise Quartz en tant qu’ordonnanceur, et chaque Job définit sa fréquence d’exécution ainsi que son contenu. L’objectif va donc être de modifier le traitement pour persister le contenu du courriel. Nous allons donc d’abord ajouter un objet Courriel :
grails create-domain-class Courriel
et le compléter :
Class Courriel { String from String to String subject String body static constraints = { from(email:true, blank:false) to(email:true, blank:false) subject(blank:false) body(blank:false) } }
On va ensuite modifier le traitent où, au lieu de faire un mailService.sendMail
comme décrit ci-avant, on va plutôt faire :
New Courriel(to:"recipient@somewhere.com", from:"me@g2here.com", subject:"Message title", body:'Message content.').save()
Voila, l’application n’a plus de problème de ralentissement dût au problème de connexion au serveur de courrier électronique. Passons à la création du job.
Depuis la version 0.5.5, la gestion des jobs passe par un plug-in, il faut donc l’installer par :
grails install-plugin quartz
Il est donc possible de créer ensuite un job par :
grails create-job sendMail
On va ensuite créer le job dont voici le code qui sera discuté après :
class sendMail { static triggers = { cron name: 'myTrigger', cronExpression: "0 0/5 * * * ?" } def concurrent = false def group = "MyGroup" def mailService def execute(){ while(def courriels = Courriel.list(max:20)) { courriels.each() { mailService.sendMail { to it.to from it.from subject it.subject body it.body } it.delete() } } } }
Cette classe va définir avec la closure triggers quand elle s’exécute. Ici, nous avons défini à la ligne 3 sous le mode Cron que le Job s’exécuterait toutes les 5 minutes. Toutes les 5 minutes, sendMail est exécuté identifiée par le gourpe MyGroup. mailService est injecté par autowiring, et la méthode execute est exécutée. Cette méthode requête la base de données, collecte tous les courriels en attente, et pour chacun, fait appel au serveur de courrier électronique puis efface le courrier de la base.
Évidemment, on peut dire que le traitement de l’ensemble des courriers peut durer plus de 5 minutes, comment est alors gérée la concurrence entre le job non fini et celui qui va être démarré (et le risque d’engorger encoure plus les requêtes au serveur de courrier électronique) ? Simplement par la ligne 6 qui interdit l’exécution de deux jobs concurents. Simple et suffisant, cela permet aussi d’exécuter une requête sur l’ensemble des données de la base.
En conclusion
Bien entendu, ce code est présenté à but d’illustration. Il a été écrit sans être testé (pas encore 🙂 ) et est très simpliste. Rien ne permet de gérer ici des problèmes plus importants au niveau de la communication entre le serveur applicatif et le serveur de courrier électronique. Cet exemple a pour but de montrer qu’avec Grails et le plug-in quartz, il est possible d’introduire des traitements asynchrones qui vont garantir une bonne qualité de service de l’application. Car on peut aussi ajouter que si l’exemple présenté ici est simpliste, il est également couvert par un un plug-in dédié.
À propos de... Darko Stankovski
iT guy, photographe et papa 3.0, je vous fais partager mon expérience et découvertes dans ces domaines. Vous pouvez me suivre sur les liens ci-dessous.
Merci pour ce super article sur « Désynchronisez vos traitements avec Grails. », enfin un article clair et en Français.