Accueil > Non classé > Retour d’expérience : 2 ans avec Spring-Webflow

Retour d’expérience : 2 ans avec Spring-Webflow

Il y a deux ans et demi, lorsque nous avons commencé à travailler sur Kwixo, il a fallu choisir un (des) framework(s) pour gérer la partie MVC. Le cahier des charges était énorme, et Spring MVC paraissait un peu juste pour gérer les enchaînements de pages demandés par la MOA.

Par exemple, le formulaire d’inscription est réparti sur plusieurs pages et implique l’envoi d’un code via SMS sur le téléphone (qui peut-être ignoré). Bref, une cinématique complexe.

Le choix d’un framework

Genèse

Kwixo est le successeur de Receive’n’Pay qui couvre un périmètre plus complet de part son intégration de fonctionnalités Customer To Customer. Receive’n’Pay est une application écrite en PHP et CGI. Pour Kwixo, il fallait que ce soit du Java. Grâce à ce prédécesseur, nous savions que les cinématiques de paiements étaient (très) complexes, sur plusieurs pages, avec une nécessité absolue de traiter le bouton retour (imaginez la double soumission d’un formulaire de paiement !).

Sachant ces contraintes, les frameworks candidats étaient peu nombreux :

  • Seam
  • “Webbeans” qui est né en décembre 2009, soit 3 mois après le démarrage du projet
  • Spring Webflow : le favori.

Contraintes

Nous avions aussi un pré-requis : pouvoir utiliser Spring et les différents outils associés. En étudiant plus attentivement Seam (et Webbeans), nous nous sommes rendu compte qu’il s’agissait d’un produit à part entière, mettant à mal ce pré-requis.

Par ailleurs, nous souhaitions avoir un framework qui s’occupe de la navigation. Seam et Webflow le font tous les deux, de manière un peu différente. Seam utilise le jPDL de jBPM, Webflow utilise son propre format, en XML, qui, finalement reprend les mêmes concepts que le jPDL (decisions, transitions, page, etc.). Il existe un excellent article qui liste de manière presque exhaustive les différences entre Webflow et Seam

Avec nos cinématiques complexes, nous avons estimé qu’il était très important d’avoir la possibilité de les représenter “simplement”. C’est pourquoi nous avons écarté des outils comme Wicket et GWT.

Le choix

Finalement, nous avons jugé que s’enfermer dans une technologie comme Seam pour un projet “neuf” n’était pas une bonne idée et nous avons choisi Webflow. Nous pouvons l’enlever sans remettre en cause la totalité de l’architecture du projet.

Pour le pire

Vous avez dit documentation ?

De manière assez concise : “La documentation, c’est le code”. En effet, la documentation donne le minimum, c’est-à-dire la mise en place des différents composants nécessaires à l’intégration de Webflow dans Spring MVC et quelques astuces de syntaxe (parfois pas à jour : currentEvent.attributes.guest et currentEvent.guest). Malgré tout, le code est suffisamment documenté (via javadoc), et les sources sont du même acabit que celle de Spring : très compréhensibles.

“Flow definition langage”

Écrit comme ça, c’est vendeur. Bien qu’il s’agisse d’un des arguments pour lequel nous l’avons choisi, je n’ai jamais aimé le XML. Et bien qu’il rende d’énormes services, j’aurais préféré une intégration à la Grails, avec du Java et des annotations. En effet, il n’existe pas à ma connaissance d’IDE capable d’effectuer des “refactorings”, des “call hierarchy”, des “usages” sur du code appelé depuis un EL dans le XML. Idem pour les assistants d’auto-completion, même si parfois, IntelliJ ne s’en sort pas trop mal, il reste très capricieux.

J’ai le droit d’appeler un DAO depuis du XML o_O ?

Une des fonctionnalités de Webflow est de gérer un contexte de persistance au niveau du flow.

That is it. When your flow starts, the listener will handle allocating a new EntityManager in flowScope. Reference this EntityManager at anytime from within your flow by using the special persistenceContext variable.

Oui. Au niveau du flow. N’est-ce pas une inélégante manière d’écrire un service directement dans le contrôleur ? D’autant plus quand on se souvient qu’il s’agit d’un fichier XML imperméable à tous les outils permettant de rendre un code maintenable ?

Vous l’aurez compris, nous n’avons pas utilisé cette fonctionnalité. Non seulement cela va à l’encontre de toutes les bonnes pratiques d’architectures (DDD, SOA, etc.), mais c’est aussi se tirer une balle dans le pied en terme de maintenance, refactoring et réutilisation de code (au hasard, des WS pour le mobile ?). Fort heureusement, la fonctionnalité n’est pas activée par défaut.

Et le meilleur

Malgré tous ses défauts, Webflow reste un bon outil, paramétrable et dans lequel presque tout est possible. Nous développerons ici quelques-unes des personnalisations possibles utilisées dans Kwixo, mais réécrites pour l’occasion en partant de AppFuse. AppFuse est un projet open-source permettant de construire un squelette d’application depuis le POM jusqu’à la Webapp. Les configurations sont classiques. Vous pourrez trouver toutes les sources liées à cet article sur mon Github.

Ces personnalisations ont été faites (pour celles que je vais vous présenter) sur un Webflow non-patché.

Back to future

Selon la documentation officielle, la gestion correcte du bouton précédent est triviale. C’est même un des arguments en faveur de webflow :

[…]
Proper browser back button support seems unattainable. […]
Spring Web Flow provides a solution to the above issues.

Le site officiel

“Si mes calculs sont exacts, lorsque ce petit bolide atteindra 88 miles à l’heure, attends-toi à voir quelque chose qui décoiffe.”
Premiers essais

Dans les faits, si on ajoute un history=”discard” sur une transition et que l’on clique sur le bouton précédent après avoir passé cette transition, Webflow nous gratifiera d’une exception FlowExecutionRestorationFailureException. “Allez directement à la case erreur sans passer par la case départ”. Et notre conteneur de servlet nous gratifiera quant à lui d’une belle page d’erreur à sa façon. On a connu mieux en terme d’expérience utilisateur.

En revanche, lors du clic sur le bouton suivant, la page en cours s’affiche. L’idéal serait que sur la page d’erreur, on puisse retourner au dernier endroit valide, avec un lien. Et comme rien ne nous garantit que l’utilisateur n’a pas fait un combo “double-back” ou “triple-back”, on ne pourra pas se contenter de la bidouille javascript avec l’historique du navigateur.

En fouillant un peu dans la documentation, le lecteur attentif verra qu’il existe des “Listeners” sur les flows. La documentation reste très vague sur les fonctionnalités. En revanche, la javadoc est beaucoup plus locace : les Listeners permettent de suivre le flow d’execution, et d’enregistrer l’historique pour faire des bread-crumbs, par exemple. Tout à fait ce qu’il nous faut.

L’idée de base consiste à enregistrer l’URL de la dernière page vue, et, lorsqu’une exception de type FlowExecutionRestorationFailureException est levée, utiliser les ExceptionResolver de Spring pour afficher un lien vers cette URL. Facile.

Essais grandeur nature

Sauf que webflow est par nature capable de gérer plusieurs conversations au cours d’une même session. Voici un exemple :

Schéma d'exécution dans webflow

Si l’utilisateur démarre plusieurs flow (exécutions e1sX et e2sX), il peut tout à fait afficher E2S3, puis E1S3, et enfin tenter de faire retour arrière sur l’onglet qui abrite E2S3. L’implémentation naïve qui consiste à utiliser simplement la session (comme ici) affichera un lien vers E1S3 (puisque c’est la dernière vue générée pour le serveur), alors qu’il aurait fallu un lien vers E2S3.

C’est pourquoi la version suivante est un peu plus compliquée et utilise la session pour stocker une Map avec une clé correspondant à l’exécution, et en valeur l’URL de la dernière vue pour cette exécution.

L’ensemble de la mise en place se trouve sur mon github filtré sur les commits qui nous interessent. La seule chose qui reste à faire est d’enregistrer le Listener auprès du flow-executor de Webflow.

Architecture

Après avoir beaucoup tâtonné avec l’outil, nous avons finalement adopté une architecture qui semble (pour le moment) porter ses fruits. Nous n’avons pas utilisé le persistenceContext dans le flow (vous aviez compris que je n’aime pas !), mais nous avons conçu nos flows comme des applications “mobiles”, à la différence près que nous appelons directement les services en Java.

Cadrer les communications XML-Java

Le vrai souci avec les appels de Java dans le XML, comme j’ai pu l’écrire, c’est l’impossibilité des outils actuels à tracer/refactorer/inventorier ces appels. Voici une liste de don’ts que nous cherchons à éviter le plus possible :

  • Ne jamais appeler des services/DAO depuis le XML
  • Ne jamais modifier les champs d’un POJO directement dans le flow (<set value=”pojo.name” value=”pojo2.name” />)
  • Limiter au strict minimum les variables de flow, en ayant pris soin de les déclarer en début de flow
  • Limiter au maximum le besoin de refactoring des méthodes appelées depuis le XML, en passant le minimum de paramètres.
Centraliser les appels

En d’autres termes, il s’agit de limiter au plus les interactions du XML dans le Java. Toutes ces contraintes imposées nous ont conduit à créer des Façades ainsi que des POJOs dont le but est de maintenir l’état du Flow. Dans l’idéal, pour un flow, on trouvera :

  • Une façade, qui peut être partagée par plusieurs flows, surtout en cas d’héritage de flow. Cette façade aura pour chaque état une (ou plusieurs) méthode(s), dont les paramètres seront l’état du flow et la commande (si nécessaire).
  • Plusieurs commandes : une par view-state. Il faut éviter à tout prix de remplir une commande sur deux vues, au risque de s’exposer à
    des failles de sécurité.
  • Un état de flow, sous la forme d’un POJO. D’expérience, même si la quantité de code est un peu plus importante, remplir l’état de flow plutôt que garder chaque commande rend le code plus maintenable. Pour les même raisons que le remplissage de commandes sur plusieurs vue, il ne faut pas modifier les données de l’état de flow via du binding.

Grâce à ces règles simples, l’état du flow n’est manipulé que par du Java ; on peut identifier dans quelle partie du flow tel ou tel appel est effectué (grâce à une convention de nommage des méthodes de la façade) et enfin, on réduit au strict minimum les appels XML-Java.

Architecture schématique des Flows

Command ou pas command ? DTO ou pas DTO ? Binding des objets du domaine ou pas ? Finalement, l’architecture à laquelle nous sommes arrivés et qui semble fonctionner le mieux est de prévoir un service, qui prend en paramètre un objet immutable contenant des paramètres simples (ou une liste de paramètres simples). Ce service est appelé depuis la FlowFacade. Ainsi, le passage au WS est plus simple.

Gérer les entrées/sorties des flow

La documentation parle souvent des entrées et sorties de flow. Mais en revanche, elle reste assez vague sur la manière de les utiliser. Pourtant, un peu à la manière de Spring MVC avec les HandlerInterceptor, Webflow propose un mécanisme intéressant de FlowHandler. L’exemple donné par la documentation de webflow est plutôt bon. Il permet de récuperer le FlowExecutionOutcome qui contient le “end-state id” ainsi qu’une Map des sorties du flow. Le mapping d’un FlowHandler avec son flow se fait par Id : il faut nommer le FlowHandler dans le contexte Spring de la même manière que le flow.

Afin de bien séparer les différents beans, nous avons tiré parti des fonctionnalités de Spring. Au lieu d’annoter les FlowHandler en @Component pour qu’ils soient pris en compte par le ComponentScan, nous avons utilisé une annotation personnalisée @FlowHandler. Celle-ci est triviale :

1
2
3
4
5
6
7
8
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface FlowHandler {
    /** * The bean name must match the id of the flow the handler should apply to. */
    String value();
}

De même que sa mise en oeuvre :

1
2
3
4
5
<context:component-scan base-package="com.excilys.webapp"
        use-default-filters="true">
    <context:include-filter type="annotation"
    expression="com.excilys.webapp.flow.handler.FlowHandler" />
</context:component-scan>

Nous avons aussi utilisé des “component scan” personnalisés pour :

  • les FlowListeners
  • les Validators

Il y aurait encore beaucoup à dire sur Webflow, notamment sur les notions d’héritage, mais lorsque les règles de cet article sont utilisées, finalement, ce n’est plus très compliqué. La règle ABSOLUE étant de ne surtout jamais faire d’appel directement à des services depuis le XML !

Un exemple ?

Sur github, j’ai développé un exemple rapide avec le formulaire d’inscription. Le formulaire est séparé sur deux pages, la première contient les données personnelles, quant à la seconde, elle concerne les données subsidiaires (telle que l’adresse).

Alors, pour la vie ?

Finalement, après tout ce chemin parcouru ensemble, Webflow nous aura facilité le développement sur beaucoup de points. Il souffre d’erreurs de jeunesse (le manque de documentation, certaines fonctionnalités incomplètes), mais l’architecture globale de Webflow est suffisamment bien pensée pour passer outre !
La seule chose que le code de Webflow n’autorise pas est la modification du langage de définition du flow, sans passer par une phase massive de refactoring, la plupart des classes responsables de l’instanciation des FlowModels étant package-private.

Malheureusement, il y a 2 ans, lorsque nous avions choisi Webflow, une version 3 était dans les cartons, avec une date de release à mi-2011, et cette version 3 est encore dans les cartons, la date de release a juste disparu. Est-ce dû à un désintéressement de la communauté des applications “stateful” ? Peut-être. Mais il y a fort à parier que l’engouement pour les frameworks javascript M V C y soit pour quelque chose. Et les outils de google continuent de pousser Webflow vers la sortie.
Je ne peux m’empêcher de penser que sur les projets d’une certaine envergure, où les délais sont courts et les spécifications ne sont pas négociables, Webflow est une excellente solution. Surtout quand IE6 est en cible. Et surtout quand la navigation est complexe.

Notes

  • Les graphiques ont été générés avec Graphviz, outil de layout automatique, et les sources sont, devinez quoi ? disponible sur mon github !
  • L’image de la DeLorean provient de ~guynietoren@deviantART

Contrat Creative Commons Article écrit sous Creative Commons 3.0 BY-SA.

Share
  1. Pas encore de commentaire
  1. Pas encore de trackbacks