Après avoir codé la version Grails de l’application Zencontact de Zenika, voyons aujourd’hui la version développée avec le framework Play! grâce à Guillaume Bort, créateur de Play!
Mais avant, quelques mots sur Play!, un framework qui gagne à être connu.
Présentation du framework Play!
Play! est un framework Web Java open source (license Apache 2.0) développé par Guillaume Bort et une équipe de contributeurs. Play! a été écrit par des développeurs Webs, pour des développeurs Webs. Attention, je ne parle pas de développeur Web au sens css/html/php mais bien de vous, développeur Java, qui à un moment donné de votre vie avait dû coder cette @#$* de page en JSP ou en JSF.
Quelques caractéristiques de Play! :
– En mode développement, il suffit d’éditer un fichier Java, de le sauvegardez et rechargez votre navigateur pour tester. Il n’y a pas de compilations de votre code Java ! Il n’est pas nécessaire de relancer le serveur. Cela fonctionne pour les pages comme pour les classes Java, ce qui est très pratique et très productif. Votre IDE assure même une première vérification, comme avec IDEA IntelliJ, ce qui permet de travailler plus facilement qu’avec Grails à mon avis.
– Play est un framework sans état où la vue présentée au navigateur client n’est pas la représentation d’un état sur le serveur, contrairement à d’autres frameworks webs, ce qui permet de monter en charge sans risques. Cela respecte au passage l’architecture web, qui est sans état par nature.
– Les pages webs utilisent un système de templates simples basés sur Groovy comme Expression Language. Il est possible de faire de l’héritage de pages, des inclusions et de définir des tags comme avec Grails.
– Lorsqu’une erreur survient, Play s’efforce de présenter la source de l’erreur en vous montrant le code plutôt qu’une StackTrace assez indigeste.
– Play est intégré avec Hibernate, OpenID, Memcached.
– Un système de modules similaire aux plugins de Grails est en cours de construction.
– Play n’est pas un framework de prototypage, c’est un outil mis en production avec de solides références
– Play contrairement à Grails (que j’adore aussi) est complètement basé sur Java, ce qui permet de garder ses réflexes de « java-istes », son IDE favori (IDEA IntelliJ) et donc de travailler sans changer ses habitudes
– Play démarre très rapidement en mode développement, bien plus vite que Grails
– Enfin c’est fait par des Français, alors Cocorico, on va tester cela rapidement
Démonstration de l’application finale
Fonctions implémentées
Pour reprendre la liste des fonctions initiales, voici le périmètre couvert par la version Play!
* 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
* Gestion de la problématique du refresh afin d’éviter la double soumission
* L’affichage de message d’erreurs
Les fonctions non implémentées:
– le tri des colonnes n’est pas implémenté (order et orderBy). Play est tout à fait capable de le faire, le module CRUD par exemple a une méthode list avec un order et un orderBy. Cependant ce code est assez verbeux.
3 autres points non couvert par la version Play :
* Ajout des mêmes contrôles de validation côté client, en Javascript
* 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
* La réutilisation des mêmes messages d’erreurs en validation serveur et javascript
L’application utilise une base de données en mémoire, tout comme la version Grails. Une entité Contact est géré par un unique contrôleur. Au premier coup d’oeil, j’apprecie de retrouver ce mode de convention au lieu de la configuration, comme pour Grails.
Je ne pourrai pas comparer le nombre de lignes de codes, étant donné que la version Play! n’utilise pas le module CRUD. Mais la class Application ne fait que 30 lignes de code effectif pour remplir le cahier des charges exact de Zenika, le tout avec de la persistance. La class CRUD.java du module CRUD de Play! est assez verbeuse, mais très pratique.
L’entité de départ
« No fluff, just stuff » décrit bien la simplicité de la classe Contact. Ecrite en Java, elle étend une classe de base de Play! tout en restant très simple :
// (manque les imports @Entity public class Contact extends Model { @Required public String firstname; @Required public String name; @Required public Date birthdate; @Required @Email public String email; }
L’annotation @play.data.validation.Required de Play permet de rendre obligatoire ce paramètre dans la vue, tout comme le tag @Email.
Pour comparer, voici la version Grails de Contact:
package org.letouilleur.demo class Contact { String nom String prenom Date dateNaissance String email static constraints={ prenom(blank:false) nom(blank:false) dateNaissance(nullable:true) email(email:true,nullable:true,blank:true,unique:true) } }
Le Controller Application
Le Controller principal retourne la date et l’heure courante pour la page d’accueil. La méthode list() retourne une liste des Contacts, sans prendre en compte cependant deux critères de la vue (order et orderBy). La méthod form() est similaire à la méthode show() de la version Grails,
public class Application extends Controller { public static void index() { Date now = new Date(); render(now); } public static void list() { Listcontacts = Contact.find("order by name, firstname").fetch(); render(contacts); } public static void form(Long id) { if (id == null) { render(); } Contact contact = Contact.findById(id); render(contact); } public static void save(@Valid Contact contact) { if (validation.hasErrors()) { if (request.isAjax()) error("Invalid value"); render("@form", contact); } contact.save(); list(); } }
Notez tout d’abord que dans la version Play, les méthodes du Controller ne retourne pas de Model, mais délèguent à un render l’opération de restitution du modèle. J’apprécie la légèreté et la simplicité du code, qui est facilement lisible. Certains d’entre vous doivent être entrain de se demander pourquoi le Controller étend une classe de base, et même penser que c’est une limitation… ce que je ne partage pas. Cela ne me dérange pas ici de restreindre cette classe Java à un rôle, en utilisant l’héritage pour la typer. Il y a plusieurs exemples intéressants dans Play! où l’approche est moins académique, et franchement, moi j’aime bien.
Tenez, prenons la classe Contact. Avez-vous noté que les attributs sont publiques ? Qu’il n’y a pas d’encapsulation ? A priori, pour récupérer des champs simples, pourquoi s’embêter à les rendre private, pour ajouter un couple getter/setter simple ? A noter que si vous souhaitez écrire une méthode getName, cela reste possible.
public class Contact extends Model { @Required public String name; /** Met en majuscule le nom de famille */ public String getName() { return (name!=null ? name.toUpperCase() : null); } }
L’application est composée de 2 fichiers Java et de 3 pages HTML, ce qui est vraiment très léger. Bonne nouvelle : je n’ai pas croisé de fichiers XML pour faire marcher le tout. Là où j’ai eu quelques soucis pour comprendre, c’est la partie vue. Je vous donne un avis personnel : j’ai plus de mal avec la syntaxe de Play qu’avec celle de Grails pour les pages. Mais bon, c’est une histoire d’habitude je pense. Développons un peu cet argument.
Voici la version Play de la page d’accueil. Comme dans Grails qui utilise Sitemesh, il est très facile de définir un template de page générique :
<!DOCTYPE html> <html> <head> <title>Zencontact, by zenexity ★ #{get 'title' /}</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <link rel="stylesheet" type="text/css" media="screen" href="@{'public/stylesheets/style.css'}" /> <link rel="stylesheet" type="text/css" media="screen" href="@{'public/stylesheets/south-street/jquery-ui-1.7.2.custom.css'}" /> <script src="@{'public/javascripts/jquery-1.4.min.js'}" type="text/javascript" charset="utf-8"></script> <script src="@{'public/javascripts/jquery-ui-1.7.2.custom.min.js'}" type="text/javascript" charset="utf-8"></script> <script src="@{'public/javascripts/jquery.editinplace.packed.js'}" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="zencontact"> <header> <img src="@{'public/images/logo.png'}" alt="logo" id="logo" /> <h1>Zencontact <span>by zenexity</span></h1> </header> <nav> <a id="home" href="@{index()}" class="${request.action =~ /index/ ? 'selected' : ''}">Home</a> <a id="list" href="@{list()}" class="${request.action =~ /list/ ? 'selected' : ''}">List</a> <a id="new" href="@{form()}" class="${request.action =~ /form|save/ ? 'selected' : ''}">New</a> </nav> <section> #{doLayout /} </section> <footer> <a href="http://www.w3.org/TR/html5/">html5</a> - <a href="http://www.w3.org/TR/css3-roadmap/">css3</a> - <a href="http://www.playframework.org/">playframework</a> </footer> </div> </body> </html>
La ligne 24 sera remplacée par le contenu des pages filles. La page d’accueil avec le compteur de temps étend la page main.html, définit le titre puis utilise jQuery pour récupérer l’heure. Ma version Grails était moins verbeuse car j’ai utilisé un GTag, ce qui a l’avantage d’alléger le code de cette page, mais ce qui n’est pas très réaliste pour un projet. Guillaume a prit l’option d’implémenter ici directement la logique et l’affichage, pourquoi pas ?
#{extends 'main.html' /} #{set title:'Home' /}${now.format("EEEE',' MMMM dd',' yyyy")}${now.format("hh'h' MM'mn' ss's'")}
La page list.html est chargée d’itérer la collection des Contacts et de les afficher dans un tableau. Play a un système de tag comme Grails, ce qui permet par exemple d’utiliser le tag list pour itérer une collection:
<ul> #{list items:products, as:'product'} <li>${product}</li> #{/list} </ul>
La page list.html est donc simple. J’ai essayé sans succès de coder des liens sur les entêtes de colonnes afin d’effectuer un tri en passant un orderBy et un order, mais fautes de temps, je n’ai pas réussi à améliorer la version de Guillaume Bort.
#{extends 'main.html' /} #{set title:'List' /} <table> <thead> <tr> <th class="name">Name</th> <th class="firstname">First name</th> <th class="birthdate">Birth date</th> <th class="email">Email</th> <th class="edit"></th> </tr> </thead> <tbody> #{list contacts, as:'contact'} <tr class="contact" contactId="${contact.id}" draggable="true"> <td id="name-${contact.id}">${contact.name}</td> <td id="firstname-${contact.id}">${contact.firstname}</td> <td id="birthdate-${contact.id}">${contact.birthdate?.format('yyyy-MM-dd')}</td> <td id="email-${contact.id}">${contact.email}</td> <td><a href="@{form(contact.id)}">></a></td> </tr> #{/list} <tr> #{form @save()} <td><input type="text" name="contact.name"></td> <td><input type="text" name="contact.firstname"></td> <td><input type="text" name="contact.birthdate"></td> <td><input type="text" name="contact.email"></td> <td><input type="submit" value="+"></td> #{/form} </tr> </tbody> </table> // note : code Javascript pour l'Edit-In-Place et le Drag-and-drop effacé.
La page permet de lister une collection, d’éditer directement dans le tableau le nom d’une personne, et d’appeler la page form.html pour éditer une fiche. Notez dans la démonstration que la version de Guillaume affiche un nom en Chinois sans problèmes, merci l’UTF-8.
Enfin la dernière page est le formulaire de création et d’édition d’une fiche.
#{extends 'main.html' /} #{set title:'Form' /} #{form @save()} <input type="hidden" name="contact.id" value="${contact?.id}"> <p class="field"> <label for="name">Name:</label> <input type="text" id="name" name="contact.name" value="${contact?.name}"> <span class="error">${errors.forKey('contact.name')}</span> </p> <p class="field"> <label for="firstname">First name:</label> <input type="text" id="firstname" name="contact.firstname" value="${contact?.firstname}"> <span class="error">${errors.forKey('contact.firstname')}</span> </p> <p class="field"> <label for="birthdate">Birth date:</label> <input type="text" id="birthdate" name="contact.birthdate" value="${contact?.birthdate?.format('yyyy-MM-dd')}"> <span class="error">${errors.forKey('contact.birthdate')}</span> </p> <p class="field"> <label for="email">Email:</label> <input type="text" id="email" name="contact.email" value="${contact?.email}"> <span class="error">${errors.forKey('contact.email')}</span> </p> <p class="buttons"> <a href="@{list()}">Cancel</a> or <input type="submit" value="Save this contact" id="saveContact"> </p> <script type="text/javascript" charset="utf-8"> $("#birthdate").datepicker({dateFormat:'yy-mm-dd', showAnim:'fadeIn'}) </script> #{/form}
Par rapport à la version Grails, le format de la Date est bien mieux géré. J’aime bien aussi l’utilisation de Groovy, ce qui permet de retrouver les astuces et de gagner du temps, comme lorsque l’on s’assure qu’un paramètre n’est pas null avant de l’appeler :
<input type="text" id="birthdate" name="contact.birthdate" value="${contact?.birthdate?.format('yyyy-MM-dd')}">
Bootstrap
Play permet de définir des entrées d’exemple dans un fichier externe, afin de remplir la base de tests au démarrage. Le format YAML est très simple à lire, et il est supporté <TROLL>par le meilleur éditeur du monde : IDEA IntelliJ </TROLL> maintenant aussi disponible en version open-source.
Contact(warry): name: Dantec firstname: Maxime birthdate: 1985-11-12 email: hello-xxx@warry.fr Contact(guillaume): name: Bort firstname: Guillaume birthdate: 1980-12-21 email: xxx-bob@gmail.com Contact(たの): name: 鈴木 firstname: 太郎 birthdate: 1970-05-13 email: contact@xxxjapan.com
La classe Bootstrap.java de Play permet de charger les Contacts, si la liste en base est vide :
@OnApplicationStart public class Bootstrap extends Job { public void doJob() { if(Contact.count() == 0) { Fixtures.load("data.yml"); } } }
Le résultat final
Le résultat final est très léger, et franchement, en terme de performances, le démarrage est moins poussif que Grails. La possibilité de débugger avec son éditeur, et le fait d’avoir des Stacktraces propres est aussi un gain par rapport à Grails. Je n’ai pas eu à relancer le serveur, en fait vous n’avez même pas besoin de compiler vos classes Java. Sur IDEA IntelliJ il n’y a pas de compilation comme dans Eclipse lorsque vous sauvez vos fichiers. En fait, vous ne sauvez jamais vos fichiers avec IDEA IntelliJ. Celui-ci le fait lorsque vous perdez le focus sur la fenêtre principal. Du coup, le couple IDEA + Play déchire en terme de vitesse de développement.
J’ai cependant pas mal de bémols, très suggestifs et que vous ne devez pas prendre pour argent comptant. Tout d’abord du côté de la vue, j’ai tellement l’habitude des syntaxes verbeuses, à la JSF ou à la GSP pour Grails, que j’ai eu un peu de mal à me faire à la syntaxe de Play. Par ailleurs, parfois certains tags ne sont pas très constistants entre eux, et il n’est pas très évident de rentrer facilement dans cette partie.
Un point positif de Play c’est la possibilité d’utiliser Selenium afin d’ajouter des tests d’intégrations très facilement. Il suffit de lancer l’application en mode test avec « play test zencontact » et ensuite d’ouvrir la page http://localhost:9000/@tests pour pouvoir lancer les tests Selenium. C’est une idée qui va encourager l’utilisation de TDD pour écrire une application Play, ce qui est vraiment bien.
Concernant un point sur lequel j’ai eu des soucis avec Grails, c’est le support des bases « Legacy ». Florent Ramière de Jaxio avec qui j’ai travaillé il y a une semaine, m’a montré les limitations de Grails dès qu’il s’agit de prendre une base existante. Oui cela fonctionne, c’est jouable, mais Grails perd de son charme lorsqu’il faut jouer à l’équilibriste avec des tables existantes. Je pense que Play! sera peut-être plus à l’aise grâce à l’intégration de JPA, mais nous devrions refaire le test sur la base de données de démarrage de Celerio pour voir le tout sur un cas d’entreprise.
Conclusion
Ce qui m’a séduit dans le code, c’est cette simplicité et ces idées novatrices qui sont proches de Grails et de Rails, mais le tout en Java. Avant tout pensé par des développeurs Webs, ce que je suis sans aucuns doutes, je trouve qu’il serait intéressant pour vous d’y jeter un oeil, et d’apprendre à vous en servir. Par rapport à Grails, je ne sais pas si la notion de Service et d’injection existe par exemple. Par contre, un moteur de Jobs permet d’exécuter périodiquement des actions, sans besoin d’avoir une requête HTTP.
Des modules de sécurité, d’authentification, le support de GWT et de Spring, il y a de quoi démarrer une application Web rapidement. J’ai quelques réserves sur la partie vue, mais je pense que c’est par manque d’expérience.
Je ne sais pas non plus si Play a la notion de Render pour afficher la liste des Contacts au format XML ou JSON très simplement, ou s’il faut passer par une API externe supplémentaire. Enfin on apprécie vraiment le fait de ne même pas devoir compiler son code Java. Vous pouvez dire adieu à Maven/Ant et même JRebel, là on parle de productivité.
La mise en production est simple, Guillaume m’a confirmé qu’il est facile de créer un WAR final pour le déployer comme avec Grails:
play war zencontact -o zencontact.war
Play! par rapport à Grails manque de modules, mais ce n’est qu’une histoire de temps. Le code est propre, la documentation plutôt bien écrite, et l’ensemble est codé sur de solides fondations, basées sur une expérience et un certain talent pour écrire cela de manière moderne. J’ai apprécié, et je le range en plus sur mon étagère de framework à connaître pour travailler efficacement.
Le code complet de l’application zencontact est disponible ici.
1. Téléchargez une version récente de Play
2. Décompressez dans un répertoire, ajoutez à votre PATH ce répertoire
3. Ouvrez un terminal, vérifiez que Play démarre en tapant « play »
4. Téléchargez l’application zencontact
5. Décompressez-là dans un autre répertoire comme C:\demo pour avoir au final un C:\demo\zencontact par exemple
6. Dans le répertoire C:\demo, tapez « play run zencontact » et ouvrez votre navigateur sur http://localhost:9000
Merci à Guillaume Bort de Zenexity qui a codé la version Play de l’application. Guillaume travaille pour Zenexity, un cabinet de conseils et d’architecture qui propose une démarche d’architecture autour du Web. Allez voir aussi leurs différents billets car il y a beaucoup d’idées intéressantes et novatrices. Enfin ils organisent des formations sur Play, ce qui pourra intéresser les équipes en phase de démarrage.
Allez Grails, fait pas la tête, t’es encore mon préféré, mais tu as un sérieux concurrent !
A bientôt.
0 no like
C’est l’AlpesJug qui a pu profiter en avant première de ce code lors du coding dojo qu’il a organisé avec le CARA ;o)
C’est cool d’avoir maintenant une vidéo car le dojo était très dense :o)
Emmanuel
On a eu la chance de recevoir Guillaume au GenevaJUG en Janvier et je joue pas mal Play! depuis et mes impressions sont les mêmes que les tiennes: framework très efficace avec un bémol sur les vues.
Mais avec le système de modules on va vite voir arriver plusieurs système de templating qui nous permettrons de trouver notre bonheur.
Et aussi, pour avoir ta liste en JSon ou XML, rien de plus facile: utiliser les méthodes renderJson() ou renderXml()
Bonjour,
Je pense pas être le seul choqué « éthiquement » par le fait que le code ne soit pas compilé, mais après tout c’est vrais que la vidéo de démo est impressionnante.
On voit apparaitre le développement java sur de petits projets et non plus uniquement sur de grandes applications pro.
Très intéressant cet article !
Fan inconditionnel de Grails, Play! est sans aucun doute un sérieux concurrent: ce framework mérite que l’on s’y attarde.
A suivre donc …
Bye
Cyril
@alain
Le code est bien évidemment compilé, mais c’est totalement transparent. Le compilateur d’Eclipse intégré à Play! est appelé (en mode dev) à chaque rechargement de page et compile les classes utilisées et modifiées. Tout se fait rapidement, et les erreurs de compilation apparaissent directement dans le navigateur (et de manière très détaillée dans le terminal et les logs). Quand on passe en mode prod, la recompilation est passée, les erreurs masquées, et les performances sont bluffantes !
Et Play! n’est à l’origine pas destiné à des petits projets bien au contraire, il tourne sur des grosses applis dans des banques et grands comptes. Exemples
Tu devrais jeter un oeil à la vidéo sur la page d’accueil du site de Play! pour voir un peu plus comment ça se passe au niveau du dev.
Merci pour le billet Nicolas!
Pour les curieux j’ai également une version scala de la même application. Vous pouvez la télécharger à cette adresse: http://guillaume.bort.fr/apps/zencontact-play-scala.zip
Pour tester, vous aurez besoin d’une version play-1.1 nighlty et d’installer le support scala avec ‘play install scala’.
Guillaume.