Le 10 mars 2009, Zenika a présenté Apache Wicket au Paris JUG. Carl Azoury et Nicolas André ont construit en une heure une application de gestion simple, avec des fonctionnalités avancées en Ajax. L’objet de l’article c’est de vous montrer, vidéo à l’appui, comment construire la même application avec Grails. Le gros plus : je vais réellement persister mes Contacts en base, sans efforts supplémentaires, là où la version Wicket s’arrête à la partie Web, sans la partie persistance de l’application.
Le cahier des charges proposé par Zenika est très clair, vous pouvez retrouver à cette adresse la liste détaillée des fonctions à coder.
Le résultat final
Voici la liste du cahier des charges. Pour chaque élément j’ai mis en italique les points inutiles pour Grails et en rouge les fonctions que je n’ai pas développé.
* La navigation entre pages
* L’organisation d’une structure commune des pages (Type Tiles / Sitemesh)
* La désactivation du lien correspondant à la page courante
* L’édition d’un contact
* La création d’un contact
* Lister les contacts
* L’ajout rapide d’un contact dans une liste
* Le refresh de la date courante via appels Ajax
* L’édition « in place » d’un libellé sans passer par un écran d’édition
* L’ajout de la validation (Nom et prénom obligatoires, contrôle du format date, contrôle du type email)
* Utilisation d’un date picker
* Synchronisation du format du DatePicker avec le format utilisé par le convertisseur
* Ajout des mêmes contrôles de validation côté client, en Javascript
* Gestion de la problématique du refresh afin d’éviter la double soumission
* Non duplication du code du formulaire… le même composant doit être utilisé pour la page d’édition de contacts et de liste des contacts
* Le drag & drop depuis la liste vers le formulaire d’édition (en fait même sur ma version Wicket cela ne marchait pas)
* L’affichage de message d’erreurs
* La réutilisation des mêmes messages d’erreurs en validation serveur et javascript
* Le tri de la liste des contacts par nom et par prénom
Le résultat final
Avant tout, voici une vidéo du résultat final.
Les fonctions non réalisées
Je n’ai pas codé la fonction qui désactive dans le menu du haut la page courante. Cela serait possible en codant un taglib dans Grails, mais ce n’est pas disponible par défaut.
Pour des raisons de problèmes Javascript, dans cette version j’ai créé 2 pages distinctes pour implémenter d’une part la saisie rapide et d’autre part l’édition inPlace. Une page utilise Scriptaculous, une autre page utilise Prototype. Il faut voir pourquoi lorsque j’ai les deux sur la même page cela ne fonctionne pas.
Enfin concernant la non-duplication du code, par défaut la pratique de Grails propose de créer une page gsp par action, ce que j’ai fait pour simplifier le code. Il est possible sinon de rappeler une vue. Sur ce point je n’ai pas bataillé.
Ce que fait cette application en plus
Tout d’abord, c’est une vraie application avec une base de données, Hibernate, Spring, bref tout ce que vous avez l’habitude d’utiliser. En version développeur, j’utilise une base en mémoire HSQLDB qui est effacé à chaque redémarrage. En mode production nous pouvons utiliser n’importe quelle base supportée par Hibernate.
L’application est constituée d’une entité Contact associé à un contrôleur en charge de tout ce qui est opération CRUD (Create, Research, Update et Delete).
Au total, voici le nombre de fichiers et de lignes de code de l’application :
+----------------------+-------+-------+
| Name | Files | LOC |
+----------------------+-------+-------+
| Controllers | 2 | 121 |
| Domain Classes | 1 | 12 |
| Tag Libraries | 1 | 26 |
+----------------------+-------+-------+
| Totals | 4 | 159 |
+----------------------+-------+-------+
Grails n’a besoin que de 4 classes : une classe Contact pour le domaine, un controlleur et une Taglib Grails pour me simplifier la vie. Si je dresse un tableau de comparaison des 2 versions :
Code | Pages | Fichiers de conf | JS | Lignes de code | |
---|---|---|---|---|---|
Wicket | 7 fichiers java | 5 pages HTML | 1 seul ds web.xml | Aucuns | 237 lignes |
Grails | 4 fichiers groovy | 13 fichiers GSP | 6 fichiers | Aucuns | 158 lignes |
Grails est moins verbeux que la version Java. J’ai utilisé l’utilitaire CLOC en renommant les fichiers groovy en java, afin de regarder la quantité de code. Attention cependant : Grails génère des fichiers groovy de travail dans un répertoire caché et le nombre total de lignes de code est de 787 lignes. Mais si l’on regarde le nombre de lignes que l’on manipule, c’est bien 158 lignes. N’oubliez pas que l’application persiste réellement dans une base de données les Contacts.
Comment créer son application en 8 minutes ?
Assez parlé, il est temps peut-être de voir comment créer cette application. Tout compris, j’ai passé presque 2h30 pour réaliser l’ensemble de l’application. J’ai perdu beaucoup de temps sur la partie Ajax, ne connaissant pas encore les plugins de Grails par coeur. Par contre, et c’est ce que je veux vous montrer, j’ai écris le coeur de l’application en 8 minutes.
Pré-requis:
– Téléchargez Grails 1.2-RC2 et décompressez l’archive dans un répertoire C:\grails par exemple
– Ajoutez une variable d’environnement sous Windows appelé GRAILS_HOME qui pointe vers C:\grails.
– Ajouter ensuite %GRAILS_HOME%\bin dans votre variable PATH sous windows afin que Grails se trouve dans le path. Ouvrez un terminal et vérifiez que tout fonctionne avec « grails -version »
– Sous Unix, éditez votre fichier .bash_profile et ajouter la variable GRAILS_HOME. Ajoutez aussi GRAILS_HOME/bin à votre PATH. Validez l’installation avec « grails -version »
Ok c’était facile. On continue ?
Dans un répertoire, nous allons créer une application zencontact :
grails create-app zencontactdemo
...
...
Ensuite déplacez vous dans le répertoire zencontact. Nous allons commencer par créer notre entité « Contact » avec la commande create-domain-class :
cd zencontactdemo
grails create-domain-class org.letouilleur.demo.Contact
...
Maintenant, nous avons besoin d’un Controller et de pages par défaut pour la partie CRUD. La commande generate-all va créer un Controller pour afficher, éditer, supprimer et modifier les entités de type Contact. Il va aussi créer un ensemble de pages GSP pour chacune de ces actions. Le modèle est donc la class Contact, le contrôleur sera le fichier ContactController et enfin les vues seront l’ensemble des pages GSP (edit.gsp, index.gsp, create.gsp…) correspondant à chacune des actions du contrôleur Grails.
grails generate-all org.letouilleur.demo.Contact
Je ne sais pas pour vous… mais comme cela fait déjà 6 minutes il faudrait peut-être tester non ? Alors nous allons lancer un serveur Jetty préconfiguré et déjà intégré à notre application. C’est le mode « développement » qui permet de travailler très rapidement sans devoir installer un serveur. La base de données est vraiment là, c’est une base en mémoire HSQLDB. Par défaut, en mode développement Grails efface et recrée la base et les tables à chaque redémarrage. Vous pouvez changer ce comportement bien sûr… En fait vous pouvez tout faire.
Bref nous allons démarrer notre serveur et ensuite regarder la tête de notre application pour l’instant :
grails run-app
...
Environment set to development
[groovyc] Compiling 1 source file to /Users/nicolas/.grails/1.2-M4/projects/zencontactdemo/target/classes
Running Grails application..
Server running. Browse to http://localhost:8080/zencontactdemo
Si vous ouvrez maintenant votre navigateur favori vous devriez voir ceci :
Par contre si vous cliquez sur notre premier contrôler, le moins que l’on puisse dire c’est que pour l’instant il ne se passe rien non ?
Etape 2 : le domaine
Pour l’instant il ne se passe rien car Grails ne sait pas trop quoi faire. Pour l’aider, arrêtez le serveur pour l’instant et éditez le fichier ./grails-app/domain/org/letouilleur/demo/Contact.groovy avec un éditeur de texte autre qu’Eclipse :
En suivant le cahier des charges, nous allons déclarer les attributs nom, prenom comme obligatoire. Remarquez aussi que le champ email est typé comme étant une adresse email. Groovy est expressig et Grails s’en sert simplement pour construire tout ce dont il a besoin. La validation, l’affichage des messages d’erreurs, les contraintes en base : la validation est déclarée ici une fois pour toute. Comme Beans Validator, Grails pense que le meilleur endroit pour déclarer les contraintes d’un modèle sont dans le modèle lui-même. Pas dans le GUI, pas dans la base. Cependant, la base de données qui est créé automatiquement par Grails reprendra ces contraintes afin de typer correctement les colonnes.
class Contact { String nom String prenom Date dateNaissance String email static constraints={ prenom(blank:false) nom(blank:false) dateNaissance(max:new Date(),nullable:true) email(email:true,nullable:true,blank:true,unique:true) } }
Après avoir changé autant notre domaine, nous allons simplement recréer toutes les vues et le contrôleur, avant de relancer le serveur. En ligne de commande, relancez la génération de cet ensemble et répondez « a » pour ALL lorsque Grails vous demande si vous voulez écraser vos anciens fichiers :
grails generate-all org.letouilleur.demo.Contact
...
File [...]/zencontactdemo/grails-app/views/contact/create.gsp already exists. Overwrite? [y,n,a] a
Puis relancez le serveur (grails run-app) et revenons à notre page « contact ». Vous pouvez maintenant créer un contact avec les champs que nous avons simplement déclaré dans notre POGO (Plain Old Groovy Object). Si vous cliquez sur « New Contact » vous pouvez maintenant vraiment créer des contacts et les stocker en base. Pas mal non au bout de 8 minutes ?
Etape 3 : mettre en place le template
A cette étape nous avons une class ContactController.groovy qui nous donne toutes les fonctions du cahier des charges. Il suffit maintenant de travailler sur la vue, d’adapter un peu certaines parties, mais vous verrez que c’est plutôt simple. Tout d’abord, recopiez les images et la feuille CSS de Zenika dans le répertoire web-app de votre application. Nous allons débuter par modifier la page d’accueil. Il faut s’occuper du menu de navigation et de l’affichage de l’heure en temps réel.
Dans le répertoire « grails-app/views/layouts/ » nous allons d’abord nous attaquer au template. Grails utilise SiteMesh, et nous devons montrer qu’il est possible de définir le look and feel à un seul endroit. Pour cela je vais d’abord éditer le fichier main.gsp. Notez à la ligne 6 que ce tag permettra aux pages qui utiliseront ce template d’ajouter dans le HEADER du contenu. A la ligne 7 j’importe la librairie prototype. Un souci avec scriptaculous m’a obligé à déclarer un deuxième template « main2.gsp » pour certaines pages. A la ligne 12 j’ai déclaré les différentes URLs de mon application. Ici, en principe en Grails la bonne pratique veut que l’on crée une taglibs. Je l’ai fait un peu plus bas pour vous expliquer le principe. Pour nous, s’agissant d’un bout de code qui n’est pas dupliqué autre part, je peux très bien déclarer mes URLs en dur (principe DRY).
La ligne 21 permet de déclarer la zone où Grails ajoutera le contenu des pages filles, que nous allons modifier dans 2 minutes.
<html> <head> <title><g:layoutTitle default="Zencontact"/></title> <link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}"/> <link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon"/> <g:layoutHead/> <g:javascript library="prototype"/> </head> <body> <div id="header"> <g:link controller="contact" action="index">Accueil</g:link> | <g:link controller="contact" action="listWithIcons">Liste des contacts</g:link> | <g:link controller="contact" action="list">Liste v2</g:link> | <g:link controller="contact" action="create">Ajouter un contact</g:link> </div> <div id="content"> <div id="contacts"> <div class="contact"> <g:layoutBody/> </div> </div> </div> <div id="footer"> <p id="legal"><a href="http://www.touilleurexpress.fr/">Le Touilleur Express</a> Copyright© 2009-2010</p> </div> </body> </html>
Créer un deuxième fichier appelé main2.gsp dans le même répertoire, et changez simplement la ligne 7 en spécifiant <g:javascript library= »scriptaculous »/>. Voilà vous avez créé 2 templates pour le site
Etape 4 : mettre en place la page d’accueil
La page d’accueil est simple. Elle affiche la navigation, et fait appel à une taglib pour afficher l’heure en temps réel. Cela va me permettre de vous expliquer comment créer une taglib et ensuite comment ajouter un contrôleur supplémentaire. Vous allez même faire de l’Ajax dès maintenant !
Voyons le code de cette page tout d’abord :
<html> <head> <title>Zencontact avec Grails 1.2-M4</title> <meta name="layout" content="main2"/> </head> <body> <table> <tbody><tr> <td width="300px"><img src="images/zenika_logo.png"></td> <td width="300px"><img src="images/pjug_logo.png"></td> </tr> </tbody></table> <br><br><br> <!-- Appel d'un tag custom cree dans taglibs pr afficher la date --> <div><g:showDateAndTime/></div> <P>Voici la version Grails 1.2 de la démonstration Wicket de Zenika réalisée début mars 2009 au Paris Java User Group. Nous retrouvons les fonctions du cahier des charges avec surtout un Controller complet capable de réellement créer, modifier et effacer un objet Contact.</P> </body> </html>
Explications :
– la ligne 4 spécifie que nous utiliserons le template « main2 » pour cette page. En effet, notre taglib pour afficher l’heure utilise Scriptaculous.
– la ligne 16 devrait vous intriguer… C’est une taglib, et pour l’instant elle n’existe pas
Pour créer la taglib vous devez exécutez la commande suivante, relancez le serveur à la fin de la commande et laissez-le tourner.
grails create-tag-lib Tools ... WARNING: You have not specified a package. It is good practise to place classes in packages (eg. mycompany.Book). Do you want to continue? (y, n) y ... grails run-app ...
Editez ensuite le fichier grails-app/taglib/ToolsTagLib.groovy et copiez-y ce bout de code:
class ToolsTagLib { def showDateAndTime = {attrs -> out << "<script type='text/javascript'>" out << "new Ajax.PeriodicalUpdater('timerDiv', '/zencontactdemo/timer/getTime' , " out << "{method: 'get',frequency: 1,decay: 1});" out << "</script>" out << "<div id='timerDiv'></div>" } }
Une taglib est automatiquement importée et devient disponible dès lors que vous la déclarez dans votre page. La ligne 16 de notre fichier index.gsp de tout à l’heure est presque prête à fonctionner. Si vous regardez le code groovy de ce tag, vous verrez que j’utilise Scriptaculous et sa fonction Ajax.PeriodicalUpdater afin d’appeler l’url /zencontactdemo/timer/getTime. Cependant pour l’instant, il n’y a pas de contrôleur associé. Nous allons donc créer un contrôleur et y ajouter la méthode getTime.
grails create-controller org.letouilleur.demo.Timer ...
Editez le fichier grails-app/controllers/org/letouilleur/demo/TimerController.groovy, voici comment celui-ci se présente par défaut :
package org.letouilleur.demo class TimerController { def index = { } }
En Grails, un Controller c’est une URL, et une méthode, c’est une sous-url de ce Controller. Ajoutez maintenant un fichier index.gsp dans le répertoire grails-app/views/timer. Vous l’aurez compris : chaque Controller a son répertoire, et une action correspond à un fichier de vue. Dans le fichier index.gsp, mettez « Coucou » et ensuite lancez le serveur si celui-ci est éteint. Vous verrez que l’url http://localhost:8080/zencontactdemo/timer fonctionne et affiche « Coucou ».
Par ailleurs, si vous éditez le fichier index.gsp et que vous rechargez la page, votre modification est prise en compte sans relancer le serveur comme avec Tapestry.
Mais pas comme Wicket.
Je vais faire mal là où cela fait mal. Avec Wicket il faut relancer le serveur dès lors que vous touchez à la partie Java. Avec Grails, vous êtes tranquille tant que vous n’ajoutez pas de nouveaux Controller ou que vous ne modifiez pas une classe de la partie Domain. A l’usage, on relance moins Grails que Wicket. J’ai travaillé avec les deux, croyez-moi.
Pour terminer notre première page, il nous faut une méthode qui retourne la date et l’heure actuelle.
class TimerController { def index = { } def getTime = { def theTime = new java.text.SimpleDateFormat("HH:mm:ss dd MMMM yyyy").format(new Date()) [currentTime: theTime] } }
Ajoutez aussi un fichier getTime.gsp dans le répertoire /grails-app/views/timer avec ce contenu:
Il est ${currentTime}
Pour tester le tout, ouvrez l’url http://localhost:8080/zencontactdemo/timer/getTime et vous devriez voir votre message s’afficher.
Cette partie nous permet de voir comment Grails passe des paramètres du Modèle vers la Vue. La ligne 7 de la méthode getTime construit une Map avec pour clé « currentTime » et pour valeur le contenu de ma chaîne « theTime ». Si je vous dis que derrière tout cela, c’est du Spring MVC, vous vous rendez compte de la simplicité de ce bout de code ? Notre vue utilise la syntaxe ${currentTime$} pour afficher la valeur de l’objet passé en paramètre. C’est simple non ?
Il est temps maintenant de charger notre page de démarrage et de vérifier que l’heure change toutes les secondes.
Voilà pour la première page !
Je vous ai préparé un fichier zip avec l’ensemble du code de cette étape afin de vous aider si vous n’avez pas pu suivre toutes les explications. Cliquez-ici pour télécharger le fichier grails_touilleur_etape1.tar.gz
Conclusion etape 1
Je m’arrête là pour ce premier article. Si vous testez de votre côté vous verrez que la liste des contacts et l’ajout d’un nouveau contact fonctionne. A propos de validation, je ne sais pas si vous avez remarqué, mais l’email est vérifié. Si vous ne spécifiez pas de prénom ou de nom lors de la création, Grails vous affichera une erreur. Bref il ne reste plus qu’à implémenter les fonctions avancées de la liste de résultat, mais vous verrez que ce n’est pas beaucoup d’effort.
Grails est un framework Web qui va de la couche présentation à la partie service et métier. Il vous suprendra par la rapidité à laquelle vous produisez un résultat parfait. Si lors de la lecture vous avez pensé parfois que je donnais trop de détails, c’est aussi pour vous montrer un vrai projet et pas un simple HelloWorld. C’est aussi pour cette raison que je trouve l’idée de Zenika intéressante : puisque l’on dit que c’est simple, autant montrer un exemple qui marche en une heure ! Wicket est certainement plus intégré et plus fort pour une application orientée vue. Et c’est un très bon framework. Je pense que Grails vous séduira plus par la partie Hibernate/CRUD que par la partie vue, qui reste simple. Du côté Ajax et Javascrpit j’ai un peu galéré, et là où Wicket propose des composants prêts à l’emploi, j’ai dû bidouiller pour couvrir le cahier des charges. J’exagère un peu car d’un autre côté, certaines fonctions sont très simples avec Grails. Vous souhaitez la liste de tous les contacts triés par nom ? En Grails c’est Contact.listAllByNom() que vous placez dans une list, et que vous itérez du côté de la vue…
Nous verrons la prochaine fois que j’ai fait appel à des plugins pour compléter mon application. La vidéo vous montre le résultat terminé en moins de 5 minutes. La suite de cet article sera en ligne dans une semaine.
A bientôt !
0 no like
Encore un billet qui va populariser Grails
Dans 5 ans, on verra fleurir des applications Grails en production chez nos clients préférés et là j’ai comme l’impression que l’on va revivre l’enfer des premières mise en production des applications J2EE (pleine d’EJB 1.x entity). La raison ? Sous couvert d’agilité, de framework haute productivité et de langage dynamique,et afin de maintenir leur marge, les prestataires de services vont rogner sur les salaires et les formations des développeurs. Nos imminents confrères développeurs feront donc comme à l’habitude de leur mieux en picorant un peu de savoir sur Google mais à l’arrivée l’application sera plus un mix de Java & de Groovy qu’une application ‘Grails’
(J’espère bien avoir tort !)
Merci le Touilleur!
Un bon article comme ca va me permettre de regarder plus facilement ces fameux Groovy / Grails.
Ca à l’air pas mal (même si j’aime beaucoup beaucoup Wicket)
Super article, je vais suivre le tuto ça fera un bon exercice pour démarrer avec Grails!
Super article !
C’est étrange, il me semblait que prototype était une dépendance de Scriptaculous… (http://www.clever-age.com/veille/publications/developpement-specifique/fiches-produits/prototype-et-script.aculo.us.html)
En important scriptaculous, tu dois donc aussi logiquement avoir accès aux fonctions de prototype sans avoir à l’importer de façon explicite. Il y a peut être un conflit de version entre le prototype importé explicitement et celui importé en tant que dépendance de scriptaculous?
C’est marrant, je suis un programmeur Java et je me suis remis au PHP avec le framework symfony ces derniers jours et il y a un nombre de similitudes étranges entre les commandes Grails et les commandes Symfony. Je te rejoins totalement quand tu dis que le développement Java classique à du soucis à se faire…
Excellent article, très clair et convaincant. Tellement convaincant que Nicogiard serait prêt à remettre en question son wicket chéri? 😉
Cet article est convainquant, je tente ma chance en espérant gagner une minute ou deux avec STS 🙂
Très bonne idée de reprendre une démo/tuto « spécifiée » et de la refaire avec une autre techno, ça a l’avantage de faciliter la comparaison des frameworks!
@Benoit
J’espère aussi que tu te trompes, mais ce genre de situations arrivera certainement. Plus le cout d’entrée sur une techno est bas, plus le risque d’y placer des développeurs trop rapidement formés est grand…
C’est ce qui fait par exemple la force et la faiblesse du monde PHP. Et pour le coup, un framework tel que symfony est une aubaine pour cet univers : il demande de s’investir pour l’utiliser, mais est très structurant et force les bonnes pratiques.
Dans le genre « série d’articles autour de la réalisation d’une application » Grails, vous pouvez aussi lire les articles de Cyril Brouillard : http://blog.excilys.com/tag/series-grails-albums/ (série en cours de publication).
Excellent article sur Grails et très intéressant à lire !
Le gain en productivité est indéniable, bien que l’on soit très orienté CRUD ce qui est le cas de ZenContact et ce que tu soulignes fort justement dans ta conclusion.
Comme tu le dis, l’approche vue de Wicket est différente, on part de pages xHTML faite par un designer, et on développe l’application, qui est elle en Java, en bindant les composants avec les parties dynamiques du site.
En tout cas, un critère important de choix aussi entre les deux technologies en entreprise sera le suivant : « jsp / groovy » ou « xHTML / Java » (Bien entendu il existe d’autres critères et d’autres technologies comme Flex et GWT) et pourquoi bientôt Spring Roo qui permet d’avoir du Grails mais en Java.
Pour les aspects CRUD, il est possible de mentionner Celerio qui permet de générer toute l’application à partir du schéma de la base de données, je vais leur demander de participer au concours avec leur outil qui hormis les aspects Drag&Drop et Label éditable doit permettre de générer une grande majorité de l’application.
Cependant, je n’ai pas vu mention du label éditable quand on clique dessus, mais le composant doit aussi exister en Groovy/Grails. Certes l’approche composants facilite ce genre de choses en Wicket. Mais cela me permet de poser la question sur la façon dont on intègre une librairie Javascript. Par exemple pour ajouter l’intégration de YAV, une librairie JS, il faut créer un Behavior (une classe Java) qu’on ajoute ensuite au formulaire et qui du coup peut être réutilisé sur tous les projets.
Un grand merci à nouveau pour ce comparatif, c’est le but de cette application en tout cas, et j’espère qu’il y aura d’autres tentatives avec d’autres technos !
@Carl Pour le label éditable (edit in place), il me semble que c’est implémenté dans l’article 3 :-)( http://www.touilleur-express.fr/2009/12/30/grails-etape-3-boostrap-xml-json-ajax-et-creation-dun-gtag/ )