Migration vers IE9…

…ou la descente aux enfers d’une application JSF, RICHFACES, SPRING

Voici un retour d’expérience sur un événement projet comme on est appelé à en rencontrer beaucoup sur le cycle de vie d’une application, notamment lors de la maintenance, les enseignements à en tirer sont nombreux.

Il s’agissait de migrer de version une application développée en JSF1.2/RichFaces de navigateur Internet Explorer 8 (IE8) vers IE9.

Pour avoir passé il y a peu la redoutable montée de version de IE6 vers IE8, l’équipe en charge du développement, parfaitement rompue à ce contexte technique, avançait avec confiance sur ce sujet. Tout au plus devait-il s’agir d’ajustements cosmétiques isolés…. Mais ce petit battement d’aile de papillon a provoqué une belle tornade sur toute l’application, dont voici les détails.

Cadre technique: l’application en question est conçue selon une architecture multicouche avec les librairies & versions suivantes, qui ont leur importance pour la suite des événements :

dépendances JSF, Spring, RichFaces, JUnit

Après un premier test rapide, les comportements Ajax qui sont massivement utilisés par RichFaces ne fonctionnent pas sous IE9. C’est étonnant à double titre :

  • Les appels ajax sont motorisés par jQuery dont la portabilité est un gros point fort,
  • Les développements jQuery faits par ailleurs ne présentent aucune régression

RichFaces fonctionnant en ‘boîte noire’, il a été décidé de migrer en version 4.0, réputée compatible avec IE9.

Dépendances JSF, RichFaces, Spring, Junit 2

Des changements en cascade

Comme représenté ci-dessus, RichFaces 4 ne se branche que sur du JSF 2.0 : ce sera donc notre première étape de montée de version collatérale.

Puis s’enchainent les étapes suivantes :

  • JSF2.0 impose Spring Web Flow 2.3
  • Spring Web Flow nécessite Spring (Core & Security)>=3.0 et Spring Faces >=2.3.0
  • Spring 3.0 nécessite de passer JUnit en 4.9 ; en effet, le chargement du fichier de contexte applicatif bloquait lors des lancements de test.

En arrivant à la situation où les briques au cœur de notre application ont été remplacées… ainsi que celles qui sont plus ‘périphériques’ :

Dépendances JSF, RichFaces, Spring, JUnit 3

Il faut surtout se figurer que chacune de ces montées de version a provoqué de difficultés :

  • Bugs notoires ( a4j :commandLink non opérationnels dans Spring faces 2.3.0) : application d’un patch ou bien attente de la version corrective ? c’est selon vos priorités.
  • Changement de configuration xml (faces-config.xml, web.xml, tous ces fichiers au cœur de l’application sont impactés),
  • Incompatibilité de versions de servlet avec Tomcat,
  • Modification de certaines balises JSF : impact sur toutes les JSPs,
  • Perte des espaces de nom sous Eclipse (entrant en confilt au niveau des xsd préalablement utilisés : Une montée de version du plugin spring d’eclipse peut corriger ces problèmes
  • …et encore bien d’autres plus ou moins intrusives

De plus, ces incohérences étaient parfois à l’origine de problèmes rencontrés seulement à l’exécution du serveur au travers d’exceptions NoSuchMethodException et java.lang.UnsupportedOperationException pour lesquelles la difficulté était de trouver la librairie en cause. Elles survenaient au chargement de l’application ou bien au détour d’une page, ce qui exigeait de faire des tests exhaustifs.

Voici quelques conclusions que nous pouvons en tirer :

1) Basique : IE9 et RichFaces 3.3.3 ne font pas bon ménage !

2) Architecture : les applications composites sont une approche rationnelle pour tirer profit des innombrables librairies adressant chacune au mieux le rôle pour lequel elles ont été créées. Si elles mettent effectivement à disposition une grande richesse de fonctionnalités, elles peuvent se révéler instables quand on doit en modifier un élément technique.

3) JSF (et autres frameworks Web à forte cohésion avec le serveur): la structuration d’applications JSF est intrinsèquement orientée serveur (règles de navigation, de contrôle, cycle de vie des Beans…) et de ce fait entraîne des changements profonds dans les cas problématiques comme celui-ci. Les pistes désormais explorées vers une séparation plus nette entre client Web et un serveur orienté REST permettra de clarifier certaines pratiques et alléger la responsabilité du serveur dans la génération de l’interface utilisateur.

4) Gestion des dépendances : engagés dans une application composite, il faudra couvrir les versions parfois conflictuelles de librairies, d’implémentations. Même outillée avec Maven, cela reste difficile quand c’est à l’exécution, avec des erreurs absconses, que cela se caractérise.

5) Tests : sur une application riche fonctionnellement, qui va traverser malgré elle quelques générations techniques, un portefeuille de tests automatisés sera le garant d’une bonne maîtrise de la non régression (Unitaires, Fonctionnels) ; Dans ce cas présent, l’application dispose d’un riche panel de tests JUnit à connotation ‘Intégration’, faute de scripts Selenium exhaustifs (seuls quelques uns on été créés).

6) Projet : De la difficulté récurrente de connaître l’impact d’une évolution, même sur un environnement technique et fonctionnel bien maîtrisé.

7) Open source : à la fois riche et ouvert, il peut se rendre problématique pour des demandes de support. Nous sommes dans le cas de communautés très importantes, très réactives, ce qui facilite la prise en compte des anomalies / évolutions ; mais cela sera dommageable sur des librairies de niches, peu documentée ou suivies…. La pérennité est à prendre en compte.



Déployer son application dans CloudFoundry

Cet article a pour but de parcourir les étapes de création et de publication d’une application JEE dans la forge PAAS CloudFoundry. Celle-ci, supportée par VMWare, profite largement des nouveautés de Spring 3.1 (à ce jour encore en RC2).

La particularité est qu’elle se décline en une version portable (ou installable dans son infrastructure privée) au travers de sa version MicroCloudFoundry.

Pourquoi aller dans le Cloud ?

Les motivations peuvent être variées ; c’est sous l’axe suivant que nous avons mis en place nos applications dans la CloudFoundry :

  • La mise en ligne d’applications facilitée avec l’accès à des environnements en tant que services,
  • La simplification des démarches d’administrations afférentes,
  • La montée en charge, avec un ajout d’instances à la volée et une réserve de puissance illimitée … ou disons plutôt très importante.

… Mais tout d’abord, un petit tour de la forge de VM Ware :

Avec CloudFoundry, nous avons la possibilité de déclarer des services à partir d’un portefeuille déjà bien garni de briques techniques pour structurer notre socle applicatif adapté au cloud:

  • MySQL & PostgreSQL, pour les bases relationnelles,
  • RabbitMQ, un bus de message qui orchestrera les services et une part de la répartition de la charge,
  • MongoDB et Redis, des bases clé/valeurs très souples d’utilisation et très performantes.

Les applications sont packagées sous la forme de war et déployées par console sur un tomcat adapté à la sauce VMWare.

Remarque : celles-ci sont appelées à s’étoffer avec la version open-source sur cloudfoundry.org.

Se projeter vers une solution cloud, c’est se conformer à certains paradigmes techniques liés à la ‘dématérialisation’ des applicatifs : nous ne sommes pas figés sur un serveur physique ou une localisation arrêtée des données.
Ceci n’est pas sans conséquences sur la façon dont l’architecture de l’application est établie, ni sur le processus de publication. Par exemple :

  • Les ressources statiques ne sont pas enregistrées dans le système de fichier, mais sur sur une base clé / valeur,
  • Les sessions doivent gérées en fonction du load balancing,
  • Les transactions ne sont pas aussi nettement définie que sur une application classique, et on procède par solution approchante (exemple : http://www.mongodb.org/display/DOCS/Atomic+Operations)

Nous allons donner un aperçu des étapes de mise en place d’une application en utilisant les possibilités offertes par Spring pour orchestrer le tout.
La constellation Spring (Core, Data, Integration, Web-Services,…) est supportée par VM Ware qui en a fait son socle technique pour structurer les applications éligibles à son cloud, ce qui en fait une approche naturelle pour concevoir notre application.

Configuration du fichier de contexte Spring

Notre fichier applicationContext.xml est la pierre angulaire de notre configuration, et se distingue par :

  • 1- Les espaces de noms utilisés :
    1
    2
    3
    4
    5
    6
    7
    8
    
    	<beans xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:cache="http://www.springframework.org/schema/cache"
    	xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    	xmlns:p="http://www.springframework.org/schema/p"
    	xmlns:cloud="http://schema.cloudfoundry.org/spring"
    	xmlns:mongo="http://www.springframework.org/schema/data/mongo"

    Ils représentent la multiplicité des technologies en jeu dans notre architecture: du jdbc (pour MySQL), du data (pour Mongo), du cache (nouveauté 3.1 apportant une abstraction de solutions de cache)…

  • 2- L’utilisation de profiles et des balises ‘cloud’ qui facilite le raccrochement aux services déclaré dans notre espace cloud
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <beans profile="default">
    	<bean id="mongoDbFactory"  class="org.springframework.data.mongodb.core.SimpleMongoDbFactory">
    			<constructor-arg name="mongo">
    				<mongo:mongo  host="127.0.0.1"/>
    			</constructor-arg>
    			<constructor-arg name="databaseName" value="mongoimages"/>
    		</bean>
    		<bean class="org.apache.commons.dbcp.BasicDataSource" 			destroy-method="close" id="dataSource"><property name="testOnBorrow" value="true" />
    		</bean>
    </beans>
     
    <beans profile="cloud">
    		<cloud:mongo-db-factory id="mongoDbFactory"  service-name="mongoimages" />
    		<cloud:data-source id="dataSource" service-name="masqlbase"/>
    </beans>

    Les dépendances maven correspondantes sont, entre autres :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    <properties><org.cloudfoundry-version>0.8.1</org.cloudfoundry-version>
    		<org.cloudfoundry-group>org.cloudfoundry</org.cloudfoundry-group>
    		<org.spring-data-mongo-version>1.0.0.M4</org.spring-data-mongo-version>
    		<spring.version>3.1.0.RC2</spring.version>
    	</properties>
    	<dependencies>
     
    		<dependency>
      			<groupId>org.cloudfoundry</groupId>
      			<artifactId>cloudfoundry-runtime</artifactId>
      			<version>${org.cloudfoundry-version}</version>
    		</dependency>
     
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-core</artifactId>
    			…
     
    		<dependency>
    			<groupId>org.springframework.data</groupId>
    			<artifactId>spring-data-mongodb</artifactId>
    			<version>${org.spring-data-mongo-version}</version>
    		</dependency>

Les bonnes pratiques de développement avec Spring pour le cloud:

  • 1) Séparer la définition de ses beans pour séparer le local du mode production

    On initialise un profile avec la découverte automatique de l’environnement :
    using-bean-profiles-on-cloud-foundry

  • 2) Utilisation des balises ‘cloud’
    Elles rattachent le service correspondant sous le nom de bean réservé, en toute transparence. Par exemple :

    1
    2
    3
    
    <beans profile="cloud">
    		<cloud:mongo-db-factory id="mongoDbFactory"  service-name="acimage" />
    		<cloud:data-source id="dataSource" service-name="acibase2"/>
  • 3) Utilisation de Spring-Data pour la persistance avec MongoDB
    Les objets MongoTemplate injectés par Spring offrent des facilités de requêtage et de transtypage des résultats.

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Autowired
    MongoTemplate template;
     
    @Override
    public byte[] getImage(String nom) {
    	try {
    		final Acimage findOne = template.findOne(query(where("nom").is(nom)), Acimage.class,AppMongoConfig.MONGO_NOM_COLLECTION_IMAGE);
    ...
  • 4) Cacher les données peu volatiles avec Spring 3.1
    On définit des régions pour le cache de données pour y stocker les résultats de requêtes fréquentes et peu changeantes ; puis on attache aux méthodes susceptibles de changer les données les événements de nettoyage du cache.
    On sollicite d’autant moins les sources de données sous-jacentes.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    @Cacheable(value=MonAppCacheRegion.SUPER_CATEGORIE_REGION)
    	public List<SuperCategorie> toutesSuperCategoriesActives() {
    		final TypedQuery<SuperCategorie> requete = entityManager.createQuery(" select c from SuperCategorie c where c.etat = :status",SuperCategorie.class);
    		requete.setParameter("status", 'A');
    		return requete.getResultList();
    	}
     
    	@Cacheable(value=MonAppCacheRegion.CATEGORIE_ID)
    	public Categorie categorieParId(Integer id) {
    		return entityManager.find(Categorie.class, id);
    	}
     
    	@Override
    	@CacheEvict(value={MonAppCacheRegion.CATEGORIE_PAR_SUPER,MonAppCacheRegion.CATEGORIE_REGION,MonAppCacheRegion.SUPER_CATEGORIE_REGION,MonAppCacheRegion.CATEGORIE_ID},allEntries=true)
    	public Categorie merge(Categorie detachedInstance) {
    		return super.merge(detachedInstance);
    	}
  • 5) L’utilisation de Spring Tools Suite (>= 2.8) pour piloter les applications et leur déploiement.
  • Console CloudFoundry dans Spring Tools Suite

    Console CloudFoundry dans Spring Tools Suite

    Par ailleurs, CloudFoundry offre:

    • Un monitoring en cours d’ouverture au public (dans l’esprit de Spring Insight )
      Suivi des performances dans Cloud foundry

      Suivi des performances dans Cloud foundry

    • Un échange de données bi-directionnel entre le Cloud et l’entreprise au travers de Tunnels.
    • Une multiplicité de langages supportés au-delà de Java & Groovy : Scala, Ruby …

    Comme nous l’avons vu, les adhérences ne sont pas trop coercitives et la souplesse offerte par Spring se révèle très agréable quand il s’agit de jongler entre un environnement de développement partiellement représentatif du cloud et un environnement de production.
    Certains points, comme la mise en place de SSL restent un peu plus subtiles à mettre en place (Setup-SSL-on-cloudfoundry-landscape), ou en cours de construction (comme la cohabitation de la gestion des sessions et de répartition de la charge).



    Spring ROO, générateur d’applications JEE

    Real Object-Oriented

    ROO, c’est un générateur d’application JEE en mode ligne de commande, qui veut offrir une approche inspirée des meilleures pratiques retenues par Spring et offrir une grande productivité. Son positionnement en fait un candidat sérieux aux outils de mise en place technique d’un projet.
    (Remarque : La version utilisée dans cet article est la 1.1.2.)

    Console Roo sous Eclipse

    La console ROO sous Eclipse (STS)

    Principes généraux

    L’architecture est structurée selon les principes suivants :

    • Concision du code, par l’usage intensif d’aspects (avec AspectJ) ;
    • Affranchissement de la couche DAO, les instructions de persistance étant rattachées aux entités
      • on appelle par exemple Utilisateur.findUtilisateur(id)
      • ou bien utilisateur.persist();
    • Découplage du code généré et du code modifié, pour continuer à utiliser les lignes de commande après implémentations des spécificités du projet ;
      • Le code généré est maintenu dans les classes d’Aspect,
      • Le code personnalisé est intégré aux .java à la main du développeur.
    • Ouverture technologique, grâce à un portefeuille de plugins richement doté, et appelé à être complété au vu des développements en cours (JSF 2.0, JAXB,Vaadin,Wicket…),
    • Approche Top-Down du modèle : la génération de la base de données se fait à partir de la configuration JPA des classes métier.

    Un portefeuille d’extensions riche

    En termes de librairies et d’outillage, c’est du classique quand on est dans l’écosystème Spring :
    Maven, JPA, Spring MVC+ Security ,…
    Les bases de données couvertes sont: DB2, ORACLE, MYSQL, H2, POSTGRES…, et dernièrement, Google App Engine !

    Certains choix techniques sont en cours de changement, Dojo est appelé à être remplacé par jQuery dans la version 1.1.3.

    Pour ce qui est de la persistance, le choix est donné pour l’implémentation de JPA : Hibernate, EclipseLink, DataNucleus…

    De même, pour la présentation, on peut choisir entre Spring MVC associés à des JSPs ou bien GWT.

    Prise en main & Utilisation

    L’intégration dans STS (déclinaison d’Eclipse à la sauce Spring) est tout à fait opérationnelle, et même sympathique, grâce à une complétion conviviale et un accès à l’historique des commandes.
    Le cloisonnement entre le code écrit et celui généré en lignes de commande est particulièrement efficace, sur les parties Modèle et Contrôleur.

    Enrichissement de la classe modele

    Enrichissement de la classe modele : JSON et JPA

    Ce cloisonnement est plus sensible pour le code des tests en selenium ; on gardera à l’esprit qu’il vaut mieux demander à Spring ROO de générer les scenarios de tests après avoir stabilisé le développement du modèle.

    Comportements relevés au fil de l’utilisation de ROO

    Concernant GWT, le plugin nous a apporté quelques désagréments…. ayant conduit à l’abandon de cette approche, pour l’instant tout du moins; du code GAE est généré systématiquement, à tort (correction attendue en 1.1.3).

    De plus, Il faut noter que l’approche par AspectJ limite fortement la pertinence des tests de couverture, nous n’avons pas trouvé à ce jour le moyen de faire passer Cobertura dans les aspects… et ils sont nombreux avec Spring ROO.

    Certaines extensions sont seulement intégrées pour amorcer les développements, notamment l’utilisation de Spring Web Flow, qui est un simple outillage du projet pour amorcer des développements à la main.

    En revanche, l’approche REST associée à la génération simplifiée de code JSON (basée sur flexjson), plaide beaucoup en faveur de ROO quand il s’agit de mettre rapidement en place un backend de données, pour vos développements Mobile par exemple.

    Utilisation de Selenium

    L’outillage du projet avec selenium est intéressant. On pourra toujours gloser sur l’intérêt de tester ‘fonctionnellement’ des écrans qui sont surtout d’ordre ‘gestion de tables de référence’, mais on dispose en l’état d’une première brique opérationnelle pour notre campagne de tests :

    Commande Roo Selenium

    Ajout de scripts Selenium à un contrôleur Spring MVC

    Que l’on lance dans la fenêtre de commande avec :
    mvn selenium :selenese
    A noter que si on ajoute un champ dans l’entité Appartement, le script selenium n’est pas impacté et l’exécution suivante tombe en erreur…. Il vaut mieux construire ses tests en fin de cycle, autant que possible.

    De plus, les données en base ne sont pas nettoyées à chaque cycle. il faudra introduire un mécanisme de nettoyage ou de remise à niveau de notre jeu de test. On a l’assurance de pouvoir intégrer sans efforts les scripts générés grâce au plugin Selenium de FireFox dans le portefeuille de tests du projet ; mais dans ce cas, nous avons commencé à basculer vraiment vers le cœur de notre projet.

    Quelques avertissements et précautions

    ROO souffre encore de certains défauts de jeunesse qui, sans remettre en cause son utilisation, doivent conduire à prendre quelques précautions :

    • Les tests unitaires plantent à l’exécution (ceux de type ‘tests mock’) ;
    • Les relations ManyToMany ne sont pas correctement intégrées dans les JSPs ;
    • L’annotation @NotNull a provoqué des plantages d’écrans spectaculaires ;
    • Et quelques autres…

    Mais la communauté est active et les versions se succèdent à un rythme soutenu.
    On bénéficie en tout état de cause d’un référentiel de bonnes pratiques.