L’arrivée cet été du framework MVC Cairngorm dans le giron du site open source Adobe a été accompagnée d’un mouvement sur la toile assez bruyant et critique du framework en lui-même.
Examinons les deux principaux reproches récurrents :
- Le ModelLocator comme container de variables globales fourre-tout
- Le couplage fort du binding entre le ModelLocator et la vue
L’objet de ce billet est de pointer du doigt ce qui nous semble poser le plus de souci dans l’implémentation de Cairngorm et d’exposer le chainon manquant. Le périmètre concerne les applications d’entreprises dont les fonctionalités sont destinées à évoluer.
Ce qui pose problème
Les variables globales et le couplage fort sont deux principes honnis du développement logiciel, mais on les retrouve tout de même dans le CairngormStore et dans de nombreux articles et tutoriels.
Les problèmes apparaissent lorsque l’application à développer dépasse un certain seuil de complexité. Celui-ci peut être inhérent aux besoins exprimés ou induit par une conception confuse, l’ajout de fonctionalité devient alors très complexe et les régressions apparaissent.
Les variables globales
A l’usage on s’aperçoit que le ModelLocator est utilisé par les développeurs non adeptes de la POO pour y stocker des variables d’état de l’application, des listes de données, des données ‘courantes’ dans telle ou telle vue, des références aux vues mxml courantes, etc.
Prenons l’exemple minimaliste suivant :
Notre modèle contient une liste de personnes, une personne que l’on suppose sélectionnée dans un widget de type liste, probablement un formulaire affichant cette personne et une référence à un composant visuel affichant une partie des informations de cette personne (on suppose que les informations relatives aux personnes sont nombreuses et qu’il vaut mieux les segmenter sur plusieurs écrans).
Les tutoriels et articles sur les bonnes pratiques du développement avec Cairngorm montrent quasiment tous l’usage du Bindable dans les données stockées dans le ModelLocator.
Les bindings
L’usage des binding n’est pas un soucis inhérent au framework, bien au contraire, mais plus un problème lié aux tutoriels et à la pente naturelle prise par les développeurs lorsqu’ils ont entre les mains :
- un container de variables dans lequel on les invite à déposer ce qu’ils veulent
- la possibilité de rendre [Bindable] n’importe quelle variable ou classe.
On en arrive donc au pire des scénarios auquel un développeur, affecté à l’évolution d’une application, puisse être confronté :
... [Bindable] public class MyModelLocator implements ModelLocator ...
Tout va bien direz-vous, on a moins de code à écrire ! Cet argument est recevable et même souhaitable pour un prototype ou une maquette, mais pour une application d’entreprise destinée à évoluer, cela revient non seulement à plomber les performances dès que l’on commence à avoir des jeux de données conséquents mais aussi à installer une bombe à retardement pour la maintenance…
Que faire si on a plusieurs listes de ‘Person’, basées sur des filtres différents ? Faut-il ajouter des ArrayCollection ?
Exemple à faire dresser les cheveux (toujours dans le ModelLocator) :
... public var friendsList; public var ignoreList; ...
Autre possibilité : que se passe-t-il si notre application autorise l’affichage de plusieurs formulaires Person en même temps dans une vue à fenêtres multiples ?
Solution possible : remplacer selectedPerson¨par un tableau indexable par clé alphanumérique sur l’identifiant de Person ?
Où code-t-on la gestion de ce tableau ? Si l’on passe un des ‘friend’ en ‘ignore’, qui gère la maintenance des deux listes ? Le binding notifie les vues mais qui assure l’intégrité des données en mémoire ?
Tout devient d’un coup beaucoup plus compliqué et on finit inévitablement par avoir du spaghetti.
Le découplage par envoi de messages entre le modèle et la vue
L’objectif est de passer du couplage fort par références à un couplage faible par envoi de messages (notification ou évènements) :
A priori, cela revient à reproduire manuellement le mécanisme du Binding, à la différence près que les vues n’auront plus aucune références directes sur le ‘modèle’. En pratique, elles recevront des messages (des évènements) contenant les données qu’elle consommeront. Les conséquences immédiates à garder en tête sont les suivantes :
- les vues peuvent conserver les données reçues dans les messages dans des variables locales. Elles pourront les trier, les filtrer, et les manipuler localement sans avoir a priori d’impact sur le reste de l’application.
- les données envoyées par le modèle dans les messages sont littéralement déléguées ou données à la vue. Le modèle n’a plus aucun contrôle sur ce que les vues feront de ces données. Ce point n’a généralement aucune conséquence dans une petite application mais devient critique dans une équipe de 20 développeurs.
La première mesure est de déplacer les multiples instances référençant l’entité Person (friendsList, ignoreList, selectedPerson, etc.) dans une classe dédiée : PersonProxy. Le ModelLocator ne contiendrait plus qu’un et un seul attribut relatif aux personnes :
public var personProxy:PersonProxy;
-
> Lorsqu’on a affaire à un modèle de donnée modélisé et stocké dans une base de données relationnelle, l’usage est de coder une classe par Entité sauf cas particuliers.
La deuxième mesure est de supprimer des vues tous les appels au ModelLocator. Comment les vues font-elles alors pour récupérer leurs données ?
Réponse : elle ne ‘font’ plus, quelqu’un les leur fournira. Elles deviennent passives.
Cette tâche est la plus délicate à appréhender car elle inverse la dépendance et la technique de construction des interfaces.
On commence par déclarer dans la vue les données dont on va avoir besoin. Ainsi une liste de ‘friends’ est déclarée dans la vue, son statut passe de globale à locale :
[Bindable] public var friendList:ArrayCollection;
C’est cette liste Bindable qu’on fournit à la dataGrid :
<mx:DataGrid id="friendsDataGrid" dataProvider="{friendsList}" />
Au final, tout ce qui est derrière les proxies, du côté du modèle, n’est plus déclaré [Bindable]. Ne sont déclarés [Bindable] que les variables de données locales aux vues.
A ce stade, il est nécessaire de faire le grand saut. Celui-ci consiste à passer directement à un système d’envoi d’évènement contenant les données. La vue s’enregistre auprès d’un dispatcher d’évènements pour recevoir les données lorsqu’elles changent :
someDispatcher.addEventListener(Constants.FRIENDS_CHANGED, onFriendsChanged);
…
Le *hic* c’est que Cairngorm ne fournit pas de dispatcher pour ce besoin. On est obligé d’en coder un ou de réutiliser le CairngormEventDispatcher. Un examen rapide du code source de Cairngorm nous montre qu’il est parfaitementg possible de l’utiliser pour cet usage mais rien ne nous dit qu’un orientation future du framework n’introduira pas de régression dans nos applications.
Pour information, l’approche par message décrite ci-dessus est celle retenue par PureMVC.
Mais attention : il est parfaitement possible de transformer le PersonProxy en pseudo ModelLocator et de directement faire appel à celui-ci depuis les vues, sans jamais envoyer un seul message entre le ‘Model’ et la ‘Vue’. On obtiendrait au final un ModelProxy !
Plus que l’adoption de tel ou tel framework, c’est la technique d’implémentation et les principes sous-jacents qu’il est nécessaire de maîtriser.
Conclusion
On peut simplifier la situation en associant rapidité de développement à couplage fort, et à l’inverse maintenabilité à couplage faible. Le modèle actuellement proposé par Cairngorm ne satisfait ni l’un ni l’autre. Qu’en sera-t-il de la version 3.0 ? A moins de briser le couplage entre le modèle et la vue, ce framework restera d’après nous au milieu du gué et finira tôt ou tard par être emporté.
Pour des petites applications ‘jetables’, aucun framework MVC n’est nécessaire, il alourdirait considérablement la taille du code compilé, le temps de chargement dans le browser et multiplierait le temps de développement. Il y a tout ce qu’il faut dans Flex pour les réaliser dans un temps record. Dès que l’on a affaire à des données modélisées et à une application orientée gestion, la question de la maintenabilité du code se pose.
On peut parfaitement démarrer une première itération sur Flex sans framework MVC afin de démontrer par exemple une faisabilité technique, ou encore pour valider la compréhension du besoin. Passer en mode ‘projet’ nécessite autre chose que du Binding généralisé en variable globale.





