Soyons décontracté pendant l’exercice qui va suivre. Dire du bien ou dire du mal, c’est facile. On peut passer des heures sur des blogs à s’envoyer des commentaires à la figure, ou sinon on peut prendre son temps pour regarder les choses. Moi vous voyez, je suis un gars avec un esprit ouvert. Il y a 3 ou 4 ans je me serai coupé un bras pour vous faire apprendre JSF à la place de Struts. Je me suis ensuite coupé un deuxième bras pour vous parler de Seam et de la gestion de la conversation, par rapport aux soucis des premiers frameworks Webs. Je vous ai bassiné les oreilles avec Grails l’hiver dernier, et en ce moment je ne vous parle que de Play! Framework. Donc dans la logique des choses, je devrai tomber amoureux d’un nouveau framework PouetPouet d’ici quelques semaines. En attendant, et là je me sens à l’aise dans mes baskets, je peux vous parler de ce que j’observe. Quand je dis « JE » cela veut dire que j’ai eu le temps et l’occasion de tester ce que je vois sur de vrais projets.
Il n’y a pas de mauvais frameworks Webs. Selon votre projet je vous encourage à tester différentes approches et à évoluer selon vos besoins fonctionnels. Essayez aussi de prototyper avec un outil comme Balsamiq ou iRise Studio par exemple.
Pensez incrément lorsque vous faîtes du Web. Essayez de construire par petites étapes votre solution, et faites valider par l’utilisateur final les écrans. Le plus important : un logiciel qui marche et une interface qui répond à la demande du client. C’est cela qui doit vous guider pendant votre développement.
And now… JSF 2 and Play! Framework
Si vous souhaitez suivre le code de cet article, je vous encourage à suivre le petit tutorial que j’ai écris sur NetBeans 6.9. Cela vous permettra de générer rapidement une petite application en JSF 2 et Java EE 6 pour la partie Bean. Vous pouvez télécharger le code complet de la partie JSF ici afin de pouvoir suivre simplement la discusssion.
Pour écrire cet article, j’ai écris la partie Play! Framework après la partie JSF 2.0. Je vous guiderai donc sur la partie Play! Framework.
Création d’une application avec Play! Framework
– Télechargez Play! Framework sur le site de Play!
– Décompressez l’archive, ajoutez ensuite le répertoire de play à votre PATH.
– Ouvrez un terminal, dans un répertoire de test, tapez « play » afin de vérifier qu’il est bien installé. J’ai utilisé la version 1.0.3
macbook-pro-de-nicolas-martignole:NetBeansProjects nicolas$ play
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.0.3, http://www.playframework.org
~ framework ID is macbookpro
~
~ Usage: play cmd [app_path] [--options]
~
~ with, new Create a new application
~ run Run the application in the current shell
~ help Show play help
~
– Tapez ensuite « play new micromarket » afin de créer une application de test
macbook-pro-de-nicolas-martignole:NetBeansProjects nicolas$ play new micromarket
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.0.3, http://www.playframework.org
~ framework ID is macbookpro
~
~ The new application will be created in /Users/nicolas/NetBeansProjects/micromarket
~ What is the application name? micromarket
~
~ OK, the application is created.
~ Start it with : play run micromarket
~ Have fun!
~
macbook-pro-de-nicolas-martignole:NetBeansProjects nicolas$
– Placez vous dans le répertoire micromarket puis tapez play run et vérifiez que la page de démarrage de Play! se lance sur http://localhost:9000/
Bravo, vous avez créé une application Play.
Entité JPA
Vous pouvez prendre l’entité JPA MicroMarket du projet NetBeans et la placer dans le répertoire de Play!, cela fonctionne. En effet, il utilise JPA et Hibernate, donc pour lui, cela ne change pas grand chose. Voici le code initial de l’entité MicroMarket avant modification :
package org.letouilleur.demo; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; /** * * @author nicolas */ @Entity @Table(name = "MICRO_MARKET") @NamedQueries({ @NamedQuery(name = "MicroMarket.findAll", query = "SELECT m FROM MicroMarket m"), @NamedQuery(name = "MicroMarket.findByZipCode", query = "SELECT m FROM MicroMarket m WHERE m.zipCode = :zipCode"), @NamedQuery(name = "MicroMarket.findByRadius", query = "SELECT m FROM MicroMarket m WHERE m.radius = :radius"), @NamedQuery(name = "MicroMarket.findByAreaLength", query = "SELECT m FROM MicroMarket m WHERE m.areaLength = :areaLength"), @NamedQuery(name = "MicroMarket.findByAreaWidth", query = "SELECT m FROM MicroMarket m WHERE m.areaWidth = :areaWidth")}) public class MicroMarket implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "ZIP_CODE") private String zipCode; @Column(name = "RADIUS") private Double radius; @Column(name = "AREA_LENGTH") private Double areaLength; @Column(name = "AREA_WIDTH") private Double areaWidth; public MicroMarket() { } public MicroMarket(String zipCode) { this.zipCode = zipCode; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public Double getRadius() { return radius; } public void setRadius(Double radius) { this.radius = radius; } public Double getAreaLength() { return areaLength; } public void setAreaLength(Double areaLength) { this.areaLength = areaLength; } public Double getAreaWidth() { return areaWidth; } public void setAreaWidth(Double areaWidth) { this.areaWidth = areaWidth; } @Override public int hashCode() { int hash = 0; hash += (zipCode != null ? zipCode.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set if (!(object instanceof MicroMarket)) { return false; } MicroMarket other = (MicroMarket) object; if ((this.zipCode == null && other.zipCode != null) || (this.zipCode != null && !this.zipCode.equals(other.zipCode))) { return false; } return true; } @Override public String toString() { return "org.letouilleur.demo.MicroMarket[zipCode=" + zipCode + "]"; } }
A cet instant, je vais vous proposer une approche un peu différente. J’insiste lourdement sur le fait que ce n’est pas obligatoire mais intéressant. Play! propose d’utiliser une approche orientée DDD où les objets du domaine ne sont pas anémiques, mais capables d’effectuer des services simples. Pour cela, le pattern que j’utilise est de donner une référence à ce que l’on appelle le « Repository » dans DDD, et de l’injecter dans mon Entité. Grâce à une classe générique, dans laquelle l’EntityManager sera injecté, et dans laquelle je vais déclarer mes méthodes standards type CRUD, toutes mes Entités pourront alors exécuter du code d’accès au Repository.
Ce que nous allons voir n’est pas mieux ou moins bien que l’approche services/par couche que vous connaissez tous. J’ai envie de vous dire de laisser tomber les couches et de grandir pour faire un bon mot.
Play! propose (n’impose pas) d’étendre soit la classe JPASupport, soit la classe Model, afin que l’accès à l’EntityManager soit injecté par le framework. C’est un couplage fort, mais sur la modélisation du domaine ce n’est pas un souci. Je n’ai pas vu de projets avec de l’héritage d’Entité, et j’ai souvent vu que les problèmes de composition ou d’agrégation étaient résolus sans faire appel à de l’héritage de classes.
J’effectue d’abord 3 modifications : le package, un import en plus et l’extends :
// Fichier micromarket/app/models/org/letouilleur/demo/MicroMarket.java // dans le répertoire de l'appli Play! // Changement 1 package models.org.letouilleur.demo; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; // Changement 2 import play.db.jpa.*; @Entity @Table(name = "MICRO_MARKET") @NamedQueries({ @NamedQuery(name = "MicroMarket.findAll", query = "SELECT m FROM MicroMarket m"), @NamedQuery(name = "MicroMarket.findByZipCode", query = "SELECT m FROM MicroMarket m WHERE m.zipCode = :zipCode"), @NamedQuery(name = "MicroMarket.findByRadius", query = "SELECT m FROM MicroMarket m WHERE m.radius = :radius"), @NamedQuery(name = "MicroMarket.findByAreaLength", query = "SELECT m FROM MicroMarket m WHERE m.areaLength = :areaLength"), @NamedQuery(name = "MicroMarket.findByAreaWidth", query = "SELECT m FROM MicroMarket m WHERE m.areaWidth = :areaWidth")}) // Changement 3 public class MicroMarket extends JPASupport implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "ZIP_CODE") private String zipCode; @Column(name = "RADIUS") private Double radius; @Column(name = "AREA_LENGTH") private Double areaLength; @Column(name = "AREA_WIDTH") private Double areaWidth; public MicroMarket() { } public MicroMarket(String zipCode) { this.zipCode = zipCode; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public Double getRadius() { return radius; } public void setRadius(Double radius) { this.radius = radius; } public Double getAreaLength() { return areaLength; } public void setAreaLength(Double areaLength) { this.areaLength = areaLength; } public Double getAreaWidth() { return areaWidth; } public void setAreaWidth(Double areaWidth) { this.areaWidth = areaWidth; } @Override public int hashCode() { int hash = 0; hash += (zipCode != null ? zipCode.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set if (!(object instanceof MicroMarket)) { return false; } MicroMarket other = (MicroMarket) object; if ((this.zipCode == null && other.zipCode != null) || (this.zipCode != null && !this.zipCode.equals(other.zipCode))) { return false; } return true; } @Override public String toString() { return "org.letouilleur.demo.MicroMarket[zipCode=" + zipCode + "]"; } }
Ensuite, éditez le fichier micromarket/conf/application.conf de Play! afin d’activer une base de données. Nous allons utiliser une base en mémoire. Pas besoin de s’embêter avec le schéma, car Play! va créer une table MicroMarket identique à notre spécification, sans même avoir besoin de relancer le serveur.
A cet instant, si vous sauvez et que vous rechargez votre page, Play! compile le code, et vous serez notifié d’éventuelles erreurs. Pour l’instant nous n’avons pas d’entrées dans notre base, ni de page. Il faut continuer.
Allons encore plus loin. Les NamesQuery sont utilisables, mais je vais utiliser des fonctions de Play! afin d’avoir le support de la pagination intégré, sans devoir écrire cette logique dans une autre classe. Cela équivaut au code utilisant PaginationHelper de la classe MicroMarketController. Je n’invente rien. Avec ce que je vous montre nous n’avons pas besoin de déclarer ce système de PaginationHelper comme dans la version JSF, puisque c’est Play! qui vous le donne. Deuxième modification : je passe en public les attributs, puisque les getters/setters ne me servent à rien ici.
L’entité au final est donc encore plus simple :
package models.org.letouilleur.demo; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import play.db.jpa.*; import java.util.*; @Entity @Table(name = "MICRO_MARKET") public class MicroMarket extends JPASupport implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "ZIP_CODE") public String zipCode; @Column(name = "RADIUS") public Double radius; @Column(name = "AREA_LENGTH") public Double areaLength; @Column(name = "AREA_WIDTH") public Double areaWidth; public MicroMarket() { } public MicroMarket(String zipCode) { this.zipCode = zipCode; } @Override public int hashCode() { int hash = 0; hash += (zipCode != null ? zipCode.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set if (!(object instanceof MicroMarket)) { return false; } MicroMarket other = (MicroMarket) object; if ((this.zipCode == null && other.zipCode != null) || (this.zipCode != null && !this.zipCode.equals(other.zipCode))) { return false; } return true; } @Override public String toString() { return "org.letouilleur.demo.MicroMarket[zipCode=" + zipCode + "]"; } /** Retourne la liste des MicroMarket en utilisant la méthode find hérité de JPASupport * zipCode : critere de recherche * page: numéro de la page à charger, si vous affichez un tableau paginé par exemple * length: taille de la page, par exemple 10 pour charger 10 entrées */ public List<Micromarket> findByZipCode(String zipCode, int page, int length) { return find("from MicroMarket m where m.zipcode like :p").bind("p",zipCode).fetch(page,length); } //.. autre finder }
Voilà, comme vous pouvez le constater, ce n’est pas une révolution, simplement une approche différente pour le domaine.
La partie Java EE 6
Pour commencer, je vais regarder comment fonctionne la page qui donne la liste des Entités, avec la pagination. Dans la version JSF, nous avons un ManagedBean, la class MicroMarketController. Celui-ci utilise un EJB Stateless, MicroMarketFacade, qui donne accès à l’EntityManager, un DAO en quelque sorte.
public abstract class AbstractFacade<T> { private Class<T> entityClass; public AbstractFacade(Class<T> entityClass) { this.entityClass = entityClass; } protected abstract EntityManager getEntityManager(); public void create(T entity) { getEntityManager().persist(entity); } public void edit(T entity) { getEntityManager().merge(entity); } public void remove(T entity) { getEntityManager().remove(getEntityManager().merge(entity)); } public T find(Object id) { return getEntityManager().find(entityClass, id); } public List<T> findAll() { javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); cq.select(cq.from(entityClass)); return getEntityManager().createQuery(cq).getResultList(); } public List<T> findRange(int[] range) { javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); cq.select(cq.from(entityClass)); javax.persistence.Query q = getEntityManager().createQuery(cq); q.setMaxResults(range[1] - range[0]); q.setFirstResult(range[0]); return q.getResultList(); } public int count() { javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); javax.persistence.criteria.Root<T> rt = cq.from(entityClass); cq.select(getEntityManager().getCriteriaBuilder().count(rt)); javax.persistence.Query q = getEntityManager().createQuery(cq); return ((Long) q.getSingleResult()).intValue(); } }
Sa spécialisation en classe MicroMarketFacade est super simple et propre :
package org.letouilleur.demo; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Stateless public class MicroMarketFacade extends AbstractFacade<Micromarket> { @PersistenceContext(unitName = "WebApplication1PU") private EntityManager em; protected EntityManager getEntityManager() { return em; } public MicroMarketFacade() { super(MicroMarket.class); } }
Jusqu’ici, on voit qu’avec 2 classes et Java EE 6 vous pouvez faire l’équivalent de ce que vous avez l’habitude de faire avec Spring par exemple. Je trouve le code simple et efficace.
JSF et la partie ManagedBean
Attention là c’est un poil plus compliqué. La classe MicroMarketController est un ManagedBean JSF, capable de gérer la pagination. Le moins que l’on puisse dire, c’est que ce n’est pas simple.
Pour être plus équitable, j’ai retiré la pagination, et l’on voit alors que le code est simple, pas plus compliqué que la version Play! Framework. La version originale est plus complexe, avec la gestion de la pagination et un Converter pour retenir l’élément sélectionné. S’il y a possibilité de critiquer JSF, c’est sur cette partie, que je vous laisse regarder dans le fichier original.
package org.letouilleur.demo; import javax.ejb.EJB; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.FacesConverter; import javax.faces.model.DataModel; import javax.faces.model.ListDataModel; @ManagedBean (name="microMarketController") @SessionScoped public class MicroMarketController { private DataModel items = null; @EJB private org.letouilleur.demo.MicroMarketFacade ejbFacade; public MicroMarketController() { } private MicroMarketFacade getFacade() { return ejbFacade; } public String prepareList() { recreateModel(); return "List"; } public DataModel getItems() { if (items == null) { items = new ListDataModel(getFacade().findAll()); } return items; } private void recreateModel() { items = null; } }
Et si nous écrivions le controleur de la version Play ? Vous êtes prêt ?
Ouvrez la class app/controllers/Application.java et ajouter une nouvelle méthode static list comme ci-dessous:
// CODE PLAY dans app/controllers/Application.java package controllers; import play.mvc.*; import models.org.letouilleur.demo.MicroMarket; import java.util.List; public class Application extends Controller { public static void index() { render(); } public static void list() { ListlistOfMicroMarkets=MicroMarket.findAll(); render(listOfMicroMarkets); } }
Voilà, c’est tout.
La vue
Si vous le voulez bien, terminons par la vue. Pour Play c’est assez simple, voir un peu simpliste. Créez une page list.html dans le répertoire views/Application. La page portant le même nom que la méthode du contrôleur, elle sera chargée automatiquement par Play!
#{extends 'main.html' /} #{set title:'List of MicroMarket' /}List of MicroMarket
#{list items:listOfMicroMarkets, as:'currentMicroMarket'} ${currentMicroMarket.id} ${currentMicroMarket.zipCode} ${currentMicroMarket.radius} ${currentMicroMarket.areaLength} ${currentMicroMarket.areaWidth}
#{/list}
Si vous sauvez cette page et que vous testez… petit déception. Il n’y a rien dans notre base de données. Comment tester ? Et bien nous allons créer un Bootstrap (comme avec Grails) afin de charger un fichier YAML. Ce fichier contiendra 3 entrées pour tester.
Créez un fichier BootStrap.java dans le répertoire app de Play:
import play.Play; import play.jobs.*; import play.test.*; import models.*; import models.org.letouilleur.demo.*; @OnApplicationStart public class BootStrap extends Job { public void doJob() { if (Play.mode == Play.Mode.DEV) { Fixtures.load("test-data.yml"); } } }
Ensuite créez un fichier test-data.yml dans le répertoire conf:
models.org.letouilleur.demo.MicroMarket(m1): zipCode: 95051.0 radius: 255.59 areaLength: 689.856 areaWidth: 478.479 models.org.letouilleur.demo.MicroMarket(m2): zipCode: 33740.0 radius: 12.59 areaLength: 389.52 areaWidth: 528.41 models.org.letouilleur.demo.MicroMarket(m3): zipCode: 89211.0 radius: 120.3 areaLength: 39.856 areaWidth: 452.52
Relancez le serveur, et chargez alors la page pour lister nos entrées (http://localhost:9000/application/list). Hop voilà la liste des entrées de la base. Notez que la page est moche par rapport à la version JSF.
La version JSF est plus complète, mais on va revenir là dessus à la fin:
Et pour terminer, regardons le code la partie JSF, dans le fichier List.xhtml. J’ai retiré la pagination afin d’alléger la page :
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <ui:composition template="/template.xhtml"> <ui:define name="title"> <h:outputText value="#{bundle.ListMicroMarketTitle}"></h:outputText> </ui:define> <ui:define name="body"> <h:form styleClass="jsfcrud_list_form"> <h:panelGroup id="messagePanel" layout="block"> <h:messages errorStyle="color: red" infoStyle="color: green" layout="table"/> </h:panelGroup> <h:outputText escape="false" value="#{bundle.ListMicroMarketEmpty}" rendered="#{microMarketController.items.rowCount == 0}"/> <h:panelGroup rendered="#{microMarketController.items.rowCount > 0}"> <h:dataTable value="#{microMarketController.items}" var="item" border="0" cellpadding="2" cellspacing="0" rowClasses="jsfcrud_odd_row,jsfcrud_even_row" rules="all" style="border:solid 1px"> <h:column> <f:facet name="header"> <h:outputText value="#{bundle.ListMicroMarketTitle_zipCode}"/> </f:facet> <h:outputText value="#{item.zipCode}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="#{bundle.ListMicroMarketTitle_radius}"/> </f:facet> <h:outputText value="#{item.radius}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="#{bundle.ListMicroMarketTitle_areaLength}"/> </f:facet> <h:outputText value="#{item.areaLength}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="#{bundle.ListMicroMarketTitle_areaWidth}"/> </f:facet> <h:outputText value="#{item.areaWidth}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value=" "/> </f:facet> </h:column> </h:dataTable> </h:panelGroup> <br /> </h:form> </ui:define> </ui:composition> </html>
Alors pour enfoncer le clou, je propose de piquer la CSS de JSF, de mettre un tableau HTML simple, et de voir ce que cela donne. Play! Framework utilise le langage Groovy comme moteur de template dans la vue. Cela rend plus simple l’écriture des pages, mais ne permet pas de se faire aider lors de la création des pages, comme avec JSF dans NetBeans. Il y a donc un risque d’erreur. La bonne nouvelle, c’est que vous n’avez pas à arrêter Play! pour voir vos modifications. Il suffit de recharger la page dans votre navigateur.
Et le code HTML associé :
#{extends 'main.html' /} #{set title:'List of MicroMarket' /} <h2>List of MicroMarket</h2> <P>Play! Framework</P> <table border="0" cellpadding="2" cellspacing="0" rules="all" style="border:solid 1px"> <thead> <tr class=""> <th scope="col">ZipCode</th> <th scope="col">Radius</th> <th scope="col">AreaLength</th> <th scope="col">AreaWidth</th> </tr> </thead> <tbody> #{list items:listOfMicroMarkets, as:'currentMicroMarket'} <tr class="jsfcrud_${currentMicroMarket_parity}_row"> <td>${currentMicroMarket.zipCode}</td> <td>${currentMicroMarket.radius}</td> <td>${currentMicroMarket.areaLength}</td> <td>${currentMicroMarket.areaWidth}</td> </tr> #{/list} </tbody> </table>
Analyse des 2 approches
Que pensez-vous de la partie vue de la version JSF ? Objectivement, j’ai l’impression d’écrire en tag JSF du code HTML. Je sais la puissance de JSF et que je peux gagner du temps, mais au prix d’une complexité dont je n’ai pas besoin dès le départ. Côté Play! Framework, les tableaux paginés et triés existent aussi, grâce aux librairies de jQuery. D’ailleurs, le code généré par JSF utilise aussi la librairie jQuery. Au final cela ne change pas grand chose dans le navigateur. Donc je préfère la simplicité de Play! Framework à l’approche complète de JSF, mais il faut reconnaître que JSF sera en mesure de construire des interfaces plus riches que Play!, qui reste très basique.
Côté serveur, la partie Entité et DAO ne change pas beaucoup. La philosophie est différente, mais cela correspond à une manière de coder. Le ManagedBean et son Converter dans la version complète est lui aussi un peu compliqué. Pourquoi avoir un DataModel (regardez le code de la méthode List) alors qu’une List d’entité ferait l’affaire ?
Conclusion
Après avoir vu tout ce code, on voit bien la différence d’approche. Il n’y a pas de vainqueur, il y a deux approches différentes. Côté Model et Controleur, c’est du pareil au même. A part l’approche orientée DDD de Play! nous n’avons pas vu de grosses différences. Côté vue, j’ai le choix entre JSF qui me permet d’écrire plutôt une application type client riche, ou Play! qui me laisse me débrouiller avec ma page HTML.
La conclusion ?
C’est parce que Play! Framework est proche de ce que nous avons déjà l’habitude de faire que je vous recommande de regarder. Il vous faudra quelques heures à peine pour vous en servir, et vous serez surpris. Productivité, plaisir et impression d’avancer.
Références:
Le code du projet Play! zippé et du projet JSF sont disponible ici: https://touilleur-express.fr/divers/jsf/.
c’est assez marrant, on a un peu le même cheminement au niveau des frameworks. comme toi j’ai cherché comment alléger le travail de développement, j’ai fait une application dans mon entreprise en struts et je n’ai vraiment pas été emballé. j’ai trouvé ça très bavard pour pas grand chose. j’ai testé click framework il y a quelques années et j’avais trouvé ça tellement intéressant que j’avais même fait un article dessus ! après, ruby on rails, ça m’emmerdait un peu de démarrer sur ruby donc forcément j’ai bricolé sur grails mais le coté script me gène un peu. quand jboss a lancé seam j’ai plongé à fond dedans mais finalement sans être emballé. un copain m’a envoyé un jour l’url de play! et j’ai joué dessus un WE et j’ai enfin trouvé le framework ultime ! on programme en java, ça compile, on peu faire des modifs à chaud et on évite de copier-coller du code dans tous les sens. je vais essayer d’évangéliser dans ma boite…
JSF c’est naz, c’est trop lourd ! Grails c’est du groovy c’est pas type safe, Play! c’est pas du JEE ça s’intègre pas ça sert que pour les protos, Spring ya trop de classes à connaître le coût d’entrée est trop élevé, la seule VRAI solution : les servlets !
Bon, un peu gros mon troll, je crois que je n’arriverai pas à lancer un débat rageux 🙂 .
J’aime bien ton introduction en forme de « prenez du recul ». C’est une approche qu’il faut avoir dès qu’on réalise des comparaisons techniques 🙂 .
« JSF sera en mesure de construire des interfaces plus riches que Play! »
Alors certes, JSF apporte des composant prêt à l’emploi, mais les possibilités au final sont les même si tu maitrise un peu HTML et jQuery.
De toute manière, quoi qu’il arrive, il me parait utopique de penser qu’avec JSF on va juste utiliser des composants, sans jamais mettre les mains dans le cambouis et coder ses propres bibliothèques.
Le mécanisme de tags de Play! permet de faire plus ou moins la même chose. La « richesse » des interfaces est la même quelque soit le framework, c’est juste le moyens d’y arriver qui diffèrent. D’ailleurs si un fwk web t’impose des limites en termes d’IHM, c’est qu’il y’a un gros GROS problème.
Personnellement je préfère écrire du bon vieux HTML et un peu de JS plutôt que de devoir me palucher les docs de composants sur-complexes, pour arriver à quelque chose de décevant. Évidement tout cela dépend du contexte, mais en général, écrire directement du HTML, ça fonctionne.
J’ai adoré PHP. J’ai apprécié le côté pro et cadré de Struts et de J2EE sur les gros projets. J’ai souri en voyant comment les spécialistes de Java ont levé la tête du guidon pour découvrir avec 10 ans de retard les facilités d’utilisation des langages comme PHP ou Python. J’ai assisté à l’essor de Javascript et des librairies jQuery et autres Dojo et YQL qui permettent aujourd’hui de faire des applis autrement, pour un simple navigateur ou directement pour un iPhone, Android ou Blackberry (PhoneGap par exemple).
Pour moi Play! s’intègre parfaitement dans ce tableau. Original dans sa démarche et dans sa conception – ce sont des scripts Python qui génèrent le code Java – facile à comprendre (pour s’en convaincre il suffit de parcourir les sources), facile à utiliser, non invasif, léger, modulable, évolutif, bref idéal pour quiconque souhaite être efficace au quotidien (certains bien pensants de Java appellent ça parfois du prototypage, les développeurs PHP et Ruby ne comprennent pas ce terme…). L’article de Nicolas montre bien qu’il y a de la place pour tout le monde. De la place pour les frileux, pour les respecteux des normes, pour ceux qui gèrent des gros projets et/ou des effectifs conséquents, pour ceux qui n’ont pas le choix, ou encore pour ceux qui voient dans le changement uniquement une forme de continuité. Mais aussi de la place pour les équipes qui ont un objectif autre que d’implémenter telle ou telle JSR dans le dernier server JBoss qui met 2 minutes à se lancer ou encore pour ceux qui partagent leur temps entre Javascript et Java parce qu’ils considèrent qu’il y a une vie en dehors du Server. J’ai pris une matinée pour découvrir Play! et une seconde pour m’apercevoir que les tests unitaires, les « Validators », et les appels type WS sont intégrés. Une troisième pour tester rapidement différents modules – dont MongoDB. Et définitivement Play! est tout simplement une autre façon de concevoir et de travailler.
@Piwaï: Play! dispose aussi d’un mode de déploiement sous la forme d’un war, une bonne vieille servlet quoi ! 😉
« JSF sera en mesure de construire des interfaces plus riches que Play! »
Je pense que l’auteur souhaite dire que dans la logique « out of the box », JSF va plus loin que Play! framework pour l’IHM et donc propose plus de choses directement utilisables.
@jto
Effectivement on peut toujours faire une interface riche en HTML et Javascript(jQuery) en codant à la mimine. Ce qu’on faisait finalement quand on faisait du Struts qui ne propose pas de composants d’IHM tout fait.
Je crois que la force de JSF n’est pas dans les bibliothèques de composants « sur-complexe » mais au contraire dans les composants classiques mais néanmoins difficiles à gérer en HTML (surtout si y a une pointe d’Ajax): système d’onglets, popup, captcha, menu contextuels, datatables paginées…
Nous utilisons en ce moments pas mal de composants PrimeFaces parce qu’ils sont simples à intégrer et on gagne pas mal de temps.
@Thibaud
Le problème avec Struts, c’était pas tant l’IHM que les 4 pages de conf xml et les 12 classes Java qu’il fallait coder pour chaque formulaire de l’appli 😉
A mon sens, on peut faire bien plus de chose en écrivant le HTML à la main, sans pour autant y passer plus de temps (voir même moins).
Par exemple, faire un système d’onglet, avec jQuery, « $(‘#onglets’).tabs() », ça ne me parait pas plus long qu’avec JSF, et le code sera lisible par n’importe qui, sans avoir à faire 3 semaines de formation JSF.
« datatables paginées » => La c’est exactement ce que j’appel un composant sur-complexe, faire un liste paginée, même seulement avec des servlets / JSP c’est pas bien compliqué, avec Play! c’est presque trivial.
En utilisant les composants par default JSF (ou asp.net, ne soyons pas raciste, j’ai touché aux deux) ça deviens soudainement complexe…
Attention, je ne suis pas en train de dire, « JSF caylemal » (même si pour le coup, je suis pas fan) ou « Les frameworks web orienté composant sapu »
Je pense que le principale intérêt de cette approche est la possibilité de partager des bibliothèques au sein d’une (grosse) entreprise. L’équipe A développe des composants spécialement pour la société, et les équipe B, C, et D peuvent les utiliser sans trop se poser de questions.
Le principale reproche que je ferait à JSF dans les vue, c’est que parfois (voir souvent), comme dans l’exemple de Nicolas, le code deviens tres tres moche sans pour autant en tiré de la valeur ajoutée.
ex:
[…] qui n’apporte rien du tout par rapport à une « bete » table HTML.
Bon j’avais copié/collé un bout de code de List.xhtml de l’article, mais il à été supprimé 🙁
« Pourquoi avoir un DataModel (regardez le code de la méthode List) alors qu’une List d’entité ferait l’affaire ? »
C’est exactement ce genre de détail qui m’énerve avec jsf. On peut faire facilement et en fin de compte assez proprement des belles applications web assez puissantes. L’intégration avec Ajax (jsf2, mais ça vient de jsf 1.2+facelets+richfaces) est vraiment superbe.
Mais la sélection d’un bidule dans une liste…
En fait c’est lié à quelque chose dont tu ne parles pas (bizarrement!. JSF est statefull et Play est stateless. D’après ce que j’ai compris, Jsf fait correspondre une liste en mémoire avec un événement généré (clic sur un bouton etc…) par l’IHM . Donc il doit garder la liste en session, donc garder une façon de choisir dans la liste (DataModel) . Alors qu’en html stateless, on passe l’ID et l’objet sera rechargé.
J’ai passé pas mal de temps au début à essayer de passer des paramètres en jsf. Or, il y a des comportements différents dans le passage de paramètre entre deux manières de faire des liens (je ne me souviens plus le détail mais en gros dans un cas le paramètre était récupérable et dans l’autre pas) J’ai commencé à maîtriser la sélection d’un item dans une liste quand j’ai laissé jsf le faire à ma place avec un datamodel. Quand on connait html, http, quand le mot « stateless » a du sens pour soi, et ben ça énerve grave… Et puis après on s’y fait! 🙂