Spring Roo

le 22/09/2009 par Marc Bojoly
Tags: Software Engineering

Le projet Roo de SpringSource, présenté au printemps dernier, propose un outil promettant de générer rapidement un code CRUD (écrans Create/Read/Update/Delete) avec l'ambition de rester utilisable avec du code développé manuellement. Même si l'idée n'est pas nouvelle, elle est traditionnellement opposée à la notion de légèreté et de design propre qui ont fait la renommée de SpringSource. Ce vent de nouveauté au sein de l'écosystème Java mérite bien un article pour l'approfondir.

Roo en quelques mots

Roo est un outil visant à améliorer la productivité d'un développement Java basé sur Spring. Cet outil est basé sur un générateur de code aller-retour (round-trip code generator). Concrètement il se présente comme une ligne de commande qui avec des instructions de haut niveau comme new persistent class jpa -name ~.domain.Employee génère des blocs de code implémentant la fonctionnalité, par exemple la persistance. Il est ainsi capable de générer en moins de 20 instructions, comme dans l'exemple ci-dessous, une petite application fonctionnelle qui peut être lancée avec mvn tomcat:run. create project -topLevelPackage com.octo.mbo.roo.perftests install jpa -provider HIBERNATE -database HYPERSONIC_IN_MEMORY new persistent class jpa -name ~.domain.Address add field string street -notNull add field number streetNumber -type java.lang.Integer add field number zipCode -type java.lang.Integer add field string city add field string country new persistent class jpa -name ~.domain.Account add field number accountId -type java.lang.Integer add field number amount -type java.math.BigDecimal new persistent class jpa -name ~.domain.Customer add field string name -notNull add field string surname -notNull add field date jpa birthdate -type java.util.Date add field reference jpa -class ~.domain.Customer -fieldName address -type ~.domain.Address -notNull add field set jpa -class ~.domain.Customer -fieldName accounts -element ~.domain.Account -mappedBy accountId -notNull false new controller automatic -name ~.web.AddressController -formBackingObject ~.domain.Address new controller automatic -name ~.web.AccountController -formBackingObject ~.domain.Account new controller automatic -name ~.web.CustomerController -formBackingObject ~.domain.Customer La complétion implémentée sur la ligne de commande accentue encore la rapidité de prise en main. De cette façon, Roo cherche à adresser la lourdeur qui peut ressentie pour adresser un besoin simple en Java. En effet, pour afficher un simple écran CRUD il faut écrire traditionnellement une quantité de code assez importante avec peu de valeur différenciante d'un projet à l'autre. Roo propose de limiter cette lourdeur en générant le code correspondant. Pour l'instant Roo en est au stade de la RC1 et les remarques de cet article s'appliqueront à cette version.

Comment Roo fonctionne ?

Roo est basé sur l'utilisation avancé de l'AOP à travers le framework AspectJ. Le code généré est séparé en un fichier souche minimaliste et en plusieurs fichiers d'aspects qui sont assemblés par le processeur AspectJ au moment de la compilation. C'est cette séparation qui rend possible la génération de code et l'écriture manuelle en parallèle. Voici un exemple de souche de code : @Entity @RooEntity @RooJavaBean @RooToString public class Employee { @NotNull private String firstName; @NotNull private String lastName; @Temporal(TemporalType.TIMESTAMP) private Date birthDate; } Les tags du type @RooXXX ne persistent pas dans le bytecode et permettent à Roo d'effectuer le lien avec les aspects au moment de la compilation. Le système mis en place se base sur la notion de métadonnées (dont les annotations font partie), ce qui le rend suffisamment souple pour supporter un mécanisme d'add-ons. Chaque type de code (getter et setter, méthodes de persistance, méthodes de contrôleur) est généré par un add-on différent, ce qui rend possible l'apparition de générateurs pour d'autres technologies telles que JSF.

Le support à la mise en oeuvre

Clairement Roo tire son inspiration de Ruby on Rails : package clé en main, génération d'un code compatible avec les dernières pratiques de développement (modèle MVC, prise en charge des tests unitaires...). Il place encore la barre un peu plus haut avec la complétion dans la ligne de commande, la génération de mocks pour les classes générées et de tests sélénium pour les contrôleurs. Cela va dans le sens d'un abaissement du ticket d'entrée pour la mise en place d'un projet Java à l'état de l'art. Même si avec l'habitude la création d'un squelette d'application n'est pas un réel souci, un package clé en main est un accélérateur important surtout avec des ressources peu expérimentées. La contrepartie sera une incitation à utiliser l'intégralité de la pile technologique de SpringSource, les add-on pour supporter d'autres technologies que SpringMVC sont aujourd'hui hypothétiques. Mais à mon sens, le couple Spring/Hibernate étant très majoritaire, l'utilisation de la pile complète avec SpringMVC peut faire sens dans un grand nombre de cas.

La génération des écrans

Spring Roo permet de générer, à partir d'un simple script, du code mais surtout une application exécutable. On obtient ainsi rapidement, aussi simplement qu'avec un L4G des écrans utilisables. Comme dans un Rails l'interface produite par génération est spartiate et correspond rarement à une expérience client satisfaisante. Plus que le graal d'une génération de l'application de bout en bout, Roo peut apporter rapidement une base de travail. Voir un écran, des données rassure les utilisateurs et donne un support concret pour identifier les fonctionnalités complémentaires à implémenter. Vient alors la question de la réutilisation du code généré : peut-on bâtir une application complète en générant de temps à autre une partie du code? Je trouve que le respect des patterns java : MVC, injection de dépendances, ORM rendent le code plutôt lisible et compréhensible, du moins beaucoup plus qu'avec certains autres générateurs. Dans la philosophie de Spring Roo, le développeur peut utiliser de manière transparente Roo tout en implémentant des fonctionnalités spécifiques dans des classes telles que Employee ci-dessus.

Le langage Java n'étant pas aussi souple qu'un Ruby ou un Groovy, il a fallu avoir recourt à la génération de code et à l'AOP ce qui a des impacts sur l'utilisation de l'outil. Tout d'abord il faut se conformer à la philosophie de Roo de générer le code au fil des besoins. Régénérer tout le code à partir d'un script Roo, par exemple pour modifier un template, doit être exceptionnel car très couteux (près d'une demi-heure pour mon test avec 100 entités). De plus l'AOP et/ou le côté un peu verbeux du code généré a un impact sensible sur les performances de compilation, surtout sur de gros volumes : environ 1 minute pour 100 entités. L'impact de ce surcoût pour tester l'application sur la fluidité des développements est à surveiller. Enfin l'une de ces conséquences est que dans la classe Employee seuls les champs privés se trouvent dans ce code. Roo peut s'utiliser avec un simple éditeur de texte, mais si le développeur souhaite bénéficier de la complétion sur les méthodes générées, il devra utiliser le plugin Eclipse Spring Tool Suite fourni par Spring. Par ailleurs, le code généré induit un pattern de développement où toutes les méthodes sont rassemblées dans un objet : recherche, mise à jour, etc. Afin de conserver une certaine logique, les méthodes écrites par le développeur devront se trouver de la même façon dans cet objet. Ce n'est qu'un exemple, mais la conception proposée peut devenir une limitation. Je pense qu'il faut se préparer à l'idée de devoir à un moment refactorer le code, même si ce coût est supérieur à l'effort déjà fourni. C'est à mon sens le principal challenge lorsqu'on utilise ce type d'outil. Seuls les retours d'expérience permettront à l'avenir de connaître s'il existe de telles limitations et à quel niveau. Les IDE graphiques .Net ou même Java savent gérer du code d'interface graphique pour les clients lourds et aujourd'hui on sait faire cohabiter ce code avec du code écrit manuellement en respectant certaines bonnes pratiques.

Pour l'instant dans le cadre de Roo, le plugin AspectJ d'Eclipse permet, même si cela me paraît encore limité, de réinjecter chaque méthode dans la classe principale Employee. Cela assure de pouvoir à tout moment abandonner l'utilisation de Roo ce qui est une bonne garantie. Selon moi un mécanisme plus automatisé supprimant tout un aspect ou tous les aspects pour une classe via une seule commande aurait cependant été plus efficace.

Roo face à Ruby on Rails et à Grails

Comme le début de l'article a pu le montrer, Spring Roo semble être une réponse directe du monde Java à Ruby on Rails et Grails. Comment le comparer par rapport à ces deux produits, d'autant plus que Grails fait tout comme Roo parti du portefeuille de Spring Source? On peut commencer par comparer les fonctionnalités fournies au niveau de la génération des écrans car c'est ce qu'il y a de plus visuel. GrailsRoo

Grails et Roo ont des fonctionnalités de génération native très comparables. Sur l'exemple d'une classe Customer possédant une relation 1-1 avec une classe Address et une relation 1-n avec une classe Account, ils génèrent tous les deux des écrans qui prennent en compte les relations mais les présentes sous un angle technique (identifiant, ou représentation toString()). Rails quand à lui propose une génération très simple qui ignore les relations, mais un plugin active scaffold réalise la fonctionnalité de génération d'écrans de la façon la plus complète des trois. Comme le montre les copies d'écrans cependant, l'interface issue d'une génération ne pourra être livrée en l'état, et comparer ces outils sur la seule génération d'écran a finalement assez peu d'intérêt.

RailsRails avec plugin active scaffold

La meilleure façon de positionner ces différentes technologies me semble encore être celle de Ben Alex l'un des auteurs de Roo : Grails se base sur un langage interprété ce qui produit un maximum de fonctionnalités au runtime. Rails possède la même caractéristique. Roo au contraire se base sur le langage Java qui est très largement implanté en entreprise mais qui de par sa conception est beaucoup moins souple que les langages Ruby et Groovy. Ces deux langages étant dynamiques, Rails et Grails font un usage relativement modéré de la génération de code. De nombreuses fonctions sont disponibles directement au runtime grâce à la dynamicité du langage. Ecrire du code sur la base de conventions est également une caractéristique importante des langages dynamiques. A l'inverse, Roo utilise la génération de code et l'AOP (Programmation Orientée Aspect) pour fournir les mêmes fonctionnalités. Les développeurs Java sont habitués au typage statique et à la complétion pour trouver les fonctions disponibles. Le plugin Eclipse STS est là pour fournir cette complétion et masquer la complexité. Roo est à mon sens moins léger qu'un Rails ou qu'un Grails, mais le fait qu'il ne se base que sur le langage Java est son plus grand atout. Du strict point de vue de la gestion des compétences, il est plus facile d'envisager utiliser un outil comme Roo dans la majorité des équipes d'entreprises formées à Java que de les amener à maîtriser un nouveau langage comme Ruby ou Groovy. La maintenance restera une maintenance de code Java.

Mais selon moi la limite de ce raisonnement est atteinte lorsque les développeurs ont besoin d'étudier la logique du code généré pour comprendre les interactions de leur code avec le code généré ou bien ont besoin de prendre en compte dans leur réflexion les mécanismes d'AOP. Fournir un environnement prêt à l'emploi et de la génération de code apporte le maximum de valeur à des équipes assez peu expérimentées. Maîtriser une technique avancée comme de l'AOP est complexe et me semble peu importun dans ce cas. L'AOP et la génération de code sont là pour pallier à la rigidité du langage Java. Roo est utile tant que cette technicité peut être ignorée dans le code écrit. Si ce n'est plus le cas, il faut selon moi soit réintégrer le code généré, refactorer et développeur manuellement, soit changer d'outil. Les frameworks comme Rails ou Grails basés sur de la génération au runtime et un langage dynamique sont plus souples mais demandent la maîtrise d'un autre type de programmation.

Même si Roo apporte les mêmes fonctionnalités qu'un Rails ou un Grails, il n'est pas selon moi totalement concurrent car il ne s'adresse pas exactement aux même types d'équipes de développements. Des petites équipes dynamiques lui trouveront à juste titres des limites ou des retards par rapport aux frameworks dynamiques, de larges entités verront plutôt l'intérêt de donner à leurs projets un cadre structurant et un accélérateur pour débuter un projet.

Conclusion

En somme, la génération d'écrans CRUD me semble un semble être un moyen pratique pour prendre pied dans un projet. Cela permet de répartir la montée en compétence dans le temps et de commencer le dialogue avec les utilisateurs autour d'écrans concrets. Cela donne une base à modeler par la suite pour répondre au besoin. Par contre, aujourd'hui, je pense que la génération de code active, c'est-à-dire permettant en parallèle d'écrire du code et de régénérer du code existant nécessite un cadre qui pourrait être limitant. Plutôt que de conserver à tout prix Roo tout au long du projet, il sera à mon sens préférable de ne pas hésiter à payer l'investissement nécessaire le moment voulu pour revenir à du code entièrement maîtrisé. Cet outil peut apporter des atouts mais comme tout outil il faut savoir évaluer ses limites.

Référence

Trois articles introduisant Spring : Jump into Roo for extreme Java productivity, Getting Started with Spring Roo, Exploring Roo's Architecture. Un article de infoq sur la génération de code dans l'univers Java.