AppFuse par l’exemple

Les IDE d’antan – on pense tout particulièrement au vénérable JBuilder – proposaient un ensemble de « wizards », permettant de générer en quelques clics le squelette de classes, de composants voir d’applications complètes. Rien à voir, bien sûr, avec les ambitions démesurées du MDA : les wizards se contentaient de générer un ensemble de sources plus ou moins prêtes à l’emploi dans l’objectif d’accélérer certaines tâches de développement répétitives et peu valorisantes. Ruby on Rails a remis cette approche au goût du jour, et l’a baptisé du nom barbare de « scaffolding« .

Le scaffolding fait le bonheur des rubyistes en leur permettant, au prix de l’exécution de quelques lignes de commandes, de disposer d’une application web opérationnelle, autorisant la manipulation élémentaire des données de leur modèle au travers d’écrans de saisie et de listes – charge au développeur d’enrichir ensuite le code généré pour implémenter un comportement plus sophistiqué.

Le projet AppFuse, aujourd’hui dans sa version 2.0, s’appuie sur les archétypes Maven 2 pour offrir aux développeurs Java des fonctionnalités équivalentes. Dans cet article, nous vous proposons une introduction par l’exemple à AppFuse.

Maven : rappels

Pour ceux qui ne sont pas encore familiers avec Maven, voici quelques rappels de base pour pouvoir suivre cet article.

Maven est un outil open-source de build pour les projets Java. Il a été conçu pour simplifier les tâches difficiles du processus de build. Maven offre la possibilité de démarrer un projet en utilisant des squelettes d’application. Ces squelettes, dans la terminologie Maven, sont appelés « archétypes ». Grâce à eux, une ligne de commande permet de démarrer avec une hiérarchie de répertoire standardisée (sources, resources, builds).

Tous les archétypes Maven comportent un fichier « pom.xml » contenant toutes les directives de construction du projet, ses dépendances et différents plugins accessibles. Les plugins Maven couvrent, entre autres, des outils d’analyse de code, de formatage et de génération de rapport.

Pour plus d’informations sur ces notions, je vous conseille de jeter un oeil sur les sites suivants :

AppFuse

Concept

Initialement créé par Matt Raible, AppFuse est un projet open source pour développer des applications web J2EE. Il permet aux développeurs de démarrer rapidement et facilement en utilisant des technologies open-source telles que Spring, Hibernate et différents frameworks web (Struts2, Spring MVC, Tapestry, …).

AppFuse est un ensemble de scripts de génération de code permettant de faire le travail le plus pénible très rapidement, typiquement les écrans CRUD (Create, Read, Update, Delete) de votre modèle de données.

Utilisation

Initialement conçu avec Ant, Appfuse s’appuie, depuis sa version 2, sur Maven via des archétypes et des plugins de génération de code.

Archétypes Maven

Les différents archétypes fournis par le projet AppFuse couvrent différents frameworks Web, couplés avec Hibernate et Spring :

AppFuse propose pour tous ces outils de généré un projet d’application simple ou un projet multi-modules contenant un projet noyau et des projets modulaires. Il est également possible de générer uniquement le backend d’application. Vous pourrez brancher dessus votre framework préféré.

Chaque archétype AppFuse crée une application complète comprenant :

  • la gestion de la sécurité
  • une interface d’administration des utilisateurs et des rôles.
  • plusieurs thèmes CSS
  • la création automatique de la base de données
  • la population automatique de la base avec un jeu de données de test.

Plugins de génération de code

Une fois votre projet démarré avec l’archétype de votre choix, vous pourrez ajouter vos beans de données et les mapping Hibernate associés selon la méthode que vous préférez (annotations ou fichiers hbm.xml).

Une fois votre modèle posé, le plugin Maven « appfuse:gen » s’occupera pour vous de créer les classes de DAO, Managers et vues correspondantes pour obtenir les écrans de gestion élémentaires (CRUD).
Chaque lancement du plugin se fait sur une classe du modèle de données et fournit :

  • une vue avec un tableau triable contenant l’ensemble des instances persistées en base
  • un formulaire de saisie/suppression
  • un jeu de données pour les tests
  • les DAO et Managers associés

Conformément aux principes du scaffolding, la génération de code et de configuration possède ses limites et il est souvent nécessaire d’effectuer quelques corrections manuelles lorsque le modèle de données est un peu évolué.

Par exemple, dans le cas de relations ManyToOne, le formulaire de saisie d’un bean doit comporter une liste permettant de sélectionner avec quel objets faire le lien. Ce cas nécessite de faire des modifications dans l’Action Struts pour que la liste s’affiche effectivement dans le formulaire web. Nous allons voir ce genre de manipulation dans la partie pratique.

Hands-on : Hibernate, Spring, Struts2

Un bon exemple valant souvent mieux qu’un long discours, nous vous proposons de créer pas à pas une application web simple, permettant de saisir des constructeurs automobiles et des voitures liées à ces constructeurs.

L’environnement technique est le suivant :

Démarrage du projet

Création de notre squelette de projet avec l’archétype appfuse-basic-struts :

mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes
-DarchetypeArtifactId=appfuse-basic-struts
-DremoteRepositories=http://static.appfuse.org/releases
-DarchetypeVersion=2.0.1
-DgroupId=fr.xebia.blog.voiture -DartifactId=voiture-app

Un répertoire « voiture-app » contenant notre squelette applicatif vient d’apparaître. Les amateurs d’Eclipse pourront ajouter :

cd voiture-app

mvn eclipse:eclipse

… pour que Maven créé les fichiers indispensables à Eclipse pour importer le projet.

Les archétype AppFuse placent la majeure partie du code de l’application en dépendances dans le pom.xml.
Pour pouvoir modifier le comportement de l’application et avoir tout sous les yeux, nous devons couper ces dépendances avec AppFuse et rapatrier le code.

Cela se fait très simplement en lançant :

mvn appfuse:full-source

Écriture du modèle

Ajoutons maintenant notre modèle de données. Pour plus de simplicité, nous allons y intégrer directement les annotations utiles à la persistance. Les classes que l’on souhaite passer au générateur de code doivent se trouver directement dans le package « model » du projet. Dans notre cas il s’agit de « fr.xebia.blog.voiture.model ».

Classe : ConstructeurAutomobile

src/main/java/fr/xebia/blog/voiture/model/ConstructeurAutomobile.java

package fr.xebia.blog.voiture.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class ConstructeurAutomobile {

  private Long id;
  private String nom;

  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @Column(length=50)
  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }
}

Classe : Voiture

src/main/java/fr/xebia/blog/voiture/model/Voiture.java

package fr.xebia.blog.voiture.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Voiture {

  private Long id;
  private String nom;
  private ConstructeurAutomobile constructeur;

  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @Column(length=50)
  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  @ManyToOne
  @JoinColumn(name="constructeur_id")
  public ConstructeurAutomobile getConstructeur() {
    return constructeur;
  }

  public void setConstructeur(ConstructeurAutomobile constructeur) {
    this.constructeur = constructeur;
  }
}

Génération des CRUD

Maintenant que nous disposons des classes, nous pouvons lancer la génération des écrans CRUD :

mvn appfuse:gen -Dentity=ConstructeurAutomobile

mvn appfuse:gen -Dentity=Voiture

N.B. : Si vous omettez l’option -Dentity le nom de la classe ciblée est demandé en prompt.

Le générateur de code comporte cependant une lacune. Les classes sont bien ajoutées aux mappings Hibernate, mais pas pour les tests unitaires. Par conséquent, un mvn test tombera en erreur.

Le palliatif à cela est de copier les lignes de mapping dans :

src/main/resources/hibernate.cfg.xml



    
        
        

        
        

    


et de les coller dans le fichier src/test/resources/hibernate.cfg.xml pour garder ces deux fichiers en phase.

Vous pouvez le vérifier par vous même en lançant :

mvn clean test -P hsqldb

{quote}
Plusieurs profils de base de données sont disponibles dans le pom.xml fourni par AppFuse (MySQL, PostgreSQL, Oracle, etc). Par souci de simplicité nous utilisons ici HSQLDB.
{quote}

Support du lien ManyToOne

Le générateur AppFuse, bien que pratique, n’est pas omnipotent. Nous avons donc quelques manipulations à réaliser manuellement pour que le formulaire de saisie d’une Voiture soit complet.

Dans le fichier src/main/webapp/WEB-INF/pages/voitureForm.jsp vous pouvez voir cette ligne :


Il s’agit du tag Struts qui affiche la liste de sélection des constructeurs. Cependant, la classe VoitureAction, générée, ne fournit pas cette liste à la JSP, qui restera en conséquence désespérément vide à l’exécution. Notons également que par défaut, AppFuse affiche l’identifiant de l’entité dans la liste – ce qui ne correspond qu’exceptionnellement au besoin. Remplaçons la valeur de l’attribut listValue du tag select par « nom ». Ainsi, le nom du constructeur sera présenté dans la liste de sélection, plutôt que son identifiant.

Attaquons-nous au support de cette fameuse liste. Nous allons rajouter ceci au fichier :

src/main/java/fr/xebia/blog/voiture/webapp/action/VoitureAction.java

[...]

import fr.xebia.blog.voiture.model.ConstructeurAutomobile;

[...]

  private GenericManager constructeurAutomobileManager;

  public void setConstructeurAutomobileManager(GenericManager constructeurAutomobileManager) {
    this.constructeurAutomobileManager = constructeurAutomobileManager;
  }

  public List getConstructeurList () {
    return (List) constructeurAutomobileManager.getAll();
  }

[...]

Comme l’attribut que nous rajoutons respecte les conventions de nommage et que la configuration Spring générée par AppFuse est correcte, l’injection de dépendances se fait automatiquement pour le constructeur AutomobileManager.

La méthode getConstructeurList, quant à elle, sera appelée par la JSP pour obtenir le contenu de la liste de sélection.

Démarrage

Nous pouvons maintenant effectuer un premier lancement de notre application grâce au plugin Jetty de Maven. :

mvn jetty:run-war -P hsqldb

Je vous laisse faire le tour du propriétaire en allant avec votre navigateur web sur localhost:8080 pour vous mettre l’eau à la bouche, voici une liste non exhaustive de tout ce que nous avons obtenu en une demi-heure de travail :

  • filtres de sécurité
  • gestion des utilisateurs en base
  • envois de mail en cas d’oubli d’un mot de passe ou de création de compte
  • écrans CRUD pour les constructeurs automobiles
  • écrans CRUD pour les voitures
  • un jeu de données inséré à chaque lancement des tests
  • export des listes de constructeurs et de voitures en CSV, XLS, PDF ou XML (Merci à DisplayTag)
  • une CSS propre (ça fait toujours plaisir)
  • une foule de plugins très utiles placés dans le pom.xml : Cobertura, PMD, DBunit et d’autres
  • le support de l’internationalisation

Et j’en oublie.

Conclusions

AppFuse est, à mon avis, un excellent plugin Maven pour démarrer un projet d’application Web classique. Il n’est pas très polyvalent mais permet de se libérer d’une part assez fastidieuse du développement web : l’écriture des CRUD.

Le squelette d’application standardisé permet de s’y retrouver rapidement quand on gère plusieurs applications et de respecter un bon découpage des fichiers de configuration.

En fait ce plugin en fournit presque trop : trois semaines après je trouvais encore des morceaux de code inutilisés provenant du squelette, et qui m’ont facilité la vie, notamment pour l’exposition de WebServices grâce à XFire.

À mon avis un très bon outil, bien ciblé, à essayer.

Quelques ressources complémentaires :

9 commentaires

  • Article très intéressant comme toujours.

    Quelques remarques toutefois :

    - Le plug-in que vous mentionnez pour l’intégration Maven/Eclipse est sérieusement concurrencé par Q4Eclipse. Que pensez-vous de ce plug-in ? De mon côté, je l’ai trouvé infiniment plus clair, rapide, et surtout stable que m2eclipse.

    - Sur le fond de l’article, croyez-vous vraiment à cette approche de génération de code ? Je veux dire; dans le cadre d’un projet avec une équipe hétérogène en termes de savoir et d’expérience, je ne suis pas sur que ce genre de solutions soit recommandées pour les débutants. En effet, ils apprendront moins que s’il réalisaient eux-même et il auront donc d’autant plus de mal a « affiné » le résultat de la génération. Ce genre d’outils n’est-il pas à réserver à des développeurs d’un bon niveau ou des débutants ne cherchant pas à atteindre ledit niveau ?

    Pour être honnête, je suis intervenant en école d’ingénieur et je déconseille fortement l’usage de générateurs de quelques sortes que ce soit afin que les étudiants effectue un réel apprentissage. Le monde professionnel étant un apprentissage perpetuel (surtout dans nos métiers), ne devrait-on pas déconseillé par conséquent ce genre d’outils ?

  • Merci pour cet article très intéressant et qui donne envie de tester.

    Pour répondre à Waddle, très souvent les jeunes développeurs arrivent sur des projets où le socle technique existe déjà et l’écriture de ce socle est rarement remis en cause. Là où je suis en ce moment, Le socle technique existe et sert à toutes les applis depuis 2002. Résultat : beaucoup de développeurs sont incapables d’écrire un socle technique rapidement ou bien de mettre en place des patterns a bon escient.

    Du coup, l’utilisation d’outils tels que Appfuse ou de framework implémentant les différents pattern n’arrangent pas les choses mais arrangent souvent les projets contraints par les coûts de développement.

    En entreprise, on essaye de minimiser les dévelopements « techniques » pour se concentrer sur le « métier » pour des raisons évidente de coûts. En école, c’est l’inverse, donc vouloir éviter ce développement technique n’a effectivement pas de sens.

    Ce que j’appel développement technique et qui devrait être appris en école :
    - Mise en place et compréhension des patterns
    - Conception de socles techniques avec une multitudes de frameworks sans code métier

    Ce qui est fait en entreprise par les développeurs 95% de leurs temps:
    - Utilisation des patterns
    - Utilisation de socle technique pré-établi (via appfuse ou pas) pour écrire du code métier.

    Alors oui dans l’idéal, le débutant devrait continuer son apprentissage technique, en pratique, il fait d’abord ce dans quoi il sera le plus productif :) Le reste étant réservé aux séniors ou aux Architectes ;)

  • Merci pour vos commentaires.

    J’avoue n’avoir pas essayé le plugin Q4Eclipse, plus par habitude qu’autre chose. Je n’ai eu aucun souci avec m2eclipse jusqu’à présent. Mais je ne manquerai pas d’y jeter un oeil.

    Pour répondre à Waddle : oui, je crois à cette approche, mais avec quelques bémols. Je suis d’accord avec vous pour dire que ce genre d’outils n’est pas une panacée. D’un côté, la génération de code offre aux débutants des exemples fonctionnels complets, desquels s’inspirer. D’un autre côté, ils ne peuvent espérer « maitriser » les notions mises en oeuvres uniquement par ce biais. Seule la présence de développeurs expérimentés permet de donner les clés utiles pour affiner le projet.

    Pour se former, il est clairement plus profitable de tout faire soi-même. Cependant, AppFuse n’a pas de vocation pédagogique. C’est, à mon sens, un outil pour les développeurs, qui souhaitent un démarrage rapide des développements sur une base standardisée. Et, pour l’avoir utilisé dans un contexte projet, je pense qu’il remplit bien ses objectifs.

  • Je trouves vos avis très pertinent. Je suis également chef de projet technique et il est clair que le socle n’est jamais mit en place ni même remit en cause par les juniors d’un projet (heureusement ?).

    Toutefois, j’ai l’impression que cette démarche ainsi que celle visant à utiliser des outils « à la Appfuse » tendent à sérieusement raréfier les ressources précisemment capables de mettre en place une architecture projet correcte (ou même de seulement « corriger » celle mise en place par un outil comme Appfuse). Ne reste que ceux qui ont pu profité d’un réel suivit et/ou d’une diffusion de la connaissance, ce qui vous en conviendrez, est de plus en plus rare dans nos métiers…

  • L’intérêt d’un projet comme AppFuse est de générer du code qui respecte les bonnes pratiques de programmation, souvent mieux que les exemples que nous sommes tous amenés à utiliser pour apprendre une technologie.
    J’ai constaté que les Juniors prenaient de meilleurs habitudes sur du code généré et qu’il était plus facile de faire intervenir des développeurs moins expérimentés en Java mais plus fort en fonctionnel.
    Pour moi AppFuse a sa place, mais je recommande plutot un produit comme Celerio de la société Jaxio. C’est un outil commercial mais il est bien plus puissant, et il permet de se concentrer sur la partie métier, sans perdre de temps à réimplémenter pour la n-ieme fois un DAO et compagnie

  • Bonjour,

    J’ai constaté une limitation technique à AppFuse apparemment il gère très mal l’héritage dans la génération du code.

    Avez-vous des idées de workaround?

    http://www.mail-archive.com/users@appfuse.dev.java.net/msg09997.html

  • Merci beaucoup pour ce tutoriel, c’est très utile. Mais c’est un cas très particulier.

    Pour j’ai un ensemble de « Entity » reliées entre eux par des relations @ManyToOne, OneToMay, héritage …
    SVP, Merci de m’envoyer un document où des liens dans lesquels la procédure de génération de code dans le cas complexe (différentes relations entre différents entités).

  • …Article vu quelque part sur le net…
    Et là on ne voit aucune référence à l’article d’origine!!!

  • @EL KHAOUY et @b.francis : Je n’ai malheureusement pas rencontré ces cas puisque nous avons débranché AppFuse pour passer en ‘manuel’ quand nous avons rencontré des difficultés. Je n’ai pas trouvé de ressources parlante sur des cas plus complexes. Si je trouve plus de temps je ferais un update, AppFuse ayant évolué depuis l’écriture de cet article

    @Hacker : Si vous avez vu un article traitant du même sujet sur le net, je suis intéressé par un lien. Mais « toute ressemblance avec un article existant ou ayant existé serait purement fortuite » puisque j’ai bien écrit cet article de A à Z de mes blanches mains. Si vous avez le lien vers l’article en question je serais ravi de compléter ma culture en le lisant. :-)

Laisser un commentaire