De plus en plus de développements d'applications d'entreprise s'orientent vers des frameworks de composants et des solutions intégrées. Pourquoi ? Les architectures à base de composants ont-elles un futur ? Je pense que oui, et bientôt tous les développements de framework seront basés sur des composants - c'est imminent. Laissez-moi vous expliquer pourquoi.
Comment construisez-vous votre maison ? Vous commencez à construire par blocs. Il est possible de comparer la construction d'une application web à la construction d'une petite maison de campagne. Vous pouvez mettre en place très rapidement une application de très bonne facture avec toutes les fonctionnalités requises. Chaque pièce de votre maison est créée pour des besoins spécifiques, par exemple la cuisine, le salon, la chambre ou la salle de bains. L'aménagement de la maison vous permet de passer facilement d'une pièce à l'autre en utilisant les couloirs et les escaliers.
Vous voulez faire mieux maintenant, et vous donner la possibilité de construire une maison plus grande et de meilleure qualité - vous aimeriez avoir un sauna, une piscine, une salle de cinéma et bien sûr un aquarium géant rempli de reptiles :-). Mais changer la conception de votre maison peut être assez difficile. Même si vous êtes en mesure d'ajouter des équipements supplémentaires à la maison, le résultat final peut ne pas être esthétique. C'est aussi moins utile, si vos ajouts doivent être placés à des endroits peu pratiques, de sorte que, par exemple, pour accéder à la pièce de billard, vous deviez passer par la pièce principale.
A la fin, votre maison, jolie et soignée, se transforme en une maison mal conçue et inconfortable avec un tas de fonctionnalités. La même histoire peut arriver avec le développement d'application.
La question est, est-il possible de concevoir une application de telle manière qu'elle puisse grossir et évoluer en accord avec vos besoins? Nous allons essayer de comprendre comment y arriver.
Les composants sont les blocs de construction de l'application
Les composants sont les principaux moyens d'étendre les fonctionnalités de l'application. Le processus de création de composants est un petit peu différent de celui de création d'applications basées sur ceux-ci. Le composant doit non seulement fournir une fonctionnalité utile, mais aussi être conçu pour être réutilisé.
Réutilisation de composants
Pour pouvoir facilement réutiliser des composants, ils doivent être conçus avec une approche "faible couplage". Pour rendre ceci possible, différents frameworks implémentent leur propre modèle d’événement basé sur le pattern "Observer". Cela permet à de multiples "bénéficiaires" de s'abonner au même événement. Le pattern "Observer" fut initialement implémenté en Smalltalk. Smalltalk est un framework d'interface utilisateur basé sur MVC et ce pattern est maintenant un élément clé des frameworks MVC. Je tiens à vous souligner le fait que le pattern "Observer" existe en Java depuis la version 1.0. Mais creusons plus profondément le sujet.
Le diagramme UML ci-dessous décrit le pattern "Observer" :
Ci-dessous, une implémentation Java de base:
public class ObservableX extends Observable { ... public void setAmount(double amount) { this.amount = amount; super.setChanged(); super.notifyObservers(); } } public class ObserverA implements Observer { public void public void update(Observable o) { // obtenir le montant actualisé } } public class ObserverB implements Observer { public void public void update(Observable o) { // obtenir le montant actualisé } } //instantier une classe concrete observableX ObservableX observableX = new ObservableX(); //quelque part dans le code observableX.addObserver(new ObserverA()); observableX.addObserver(new ObserverB()); //plus tard observableX.setAmount(amount);
Comment cela fonctionne :
Premièrement, nous avons créé une instance de la classe ObservableX
, ajouté les instances de ObserverA
and ObserverB
dans l'objet observableX
et alors, quelque part dans le code, nous avons défini la valeur pour "un montant" en utilisant la méthode setAmount
. La fonction de la classe "observable" est de notifier tous les "observers" à propos du montant reçu.
L'"Observer" se comporte comme un médiateur qui maintient la liste des abonnés. Lorsqu'un événement se produit à l'intérieur d'un composant, il est envoyé à tous les abonnés de la liste. Grâce au médiateur, le composant n'a pas connaissance de ses abonnés. Et les abonnés peuvent souscrire aux événements de différents composants d'un type particulier.
Une classe peut devenir un composant lorsqu'elle utilise des événements pour notifier les "observers" de ses changements. Et ceci peut être réaliser en utilisant le pattern "Observer".
Utiliser des composants est plus facile que de les créer
En utilisant des composants, vous pouvez rapidement créer divers formulaires, panels, fenêtres et autres éléments composites d'interface utilisateur. Cependant, pour être capable de ré-utiliser les nouvelles parties composites, elles doivent être transformées en composants. Pour parvenir à ceci, vous devez déterminer les événements externes qui vont être générés par le composant, aussi bien que les mécanismes d'envois de message. Vous devez au moins créer les nouvelles classes d'événement et en définir les interfaces, ainsi que les méthodes de "callbacks" nécessaires pour traiter ces événements.
Cette approche rend l'implémentation de composants applicatifs ré-utilisables plus complexe. Elle est suffisante si le système consiste en un petit nombre d'éléments composites - autour d'une dizaine. Mais que faire si le système consiste en des centaines de tels éléments ? Inversement, ne pas suivre cette approche, va mener à un couplage fort entre les éléments et va réduire à néant toute possibilité de réutilisation. Ceci, à son tour, mènera à de la duplication de code qui rendra la maintenance future plus compliquée, et va ainsi contribuer à la multiplication du nombre de bugs dans le système.
Le problème est aggravé par le fait, que, souvent, les utilisateurs de composants ne savent pas comment définir et envoyer de nouveaux événements. Mais ils peuvent facilement utiliser des événement "tout-prêts" fournis par un framework de composants. Ils savent comment recevoir des événements mais ne savent pas comment les envoyer. Pour résoudre ce problème, nous allons examiner comment simplifier l'utilisation du modèle d'événements dans les applications.
Trop de "Listeners" d'événements
En Swing(Java), GWT, JSP et Vaadin, le pattern "Observer" est utilisé dans l'implémentation du modèle d'événements où de multiples utilisateurs peuvent s'abonner à un événement. Les listes auxquelles sont ajoutés les "Event listeners" contribuent à la mise en œuvre. Lorsque que l'événement approprié se produit, il est envoyé à tous les abonnés de cette liste.
Chaque composant crée son propre ensemble d'"Event listeners" pour un ou plusieurs événements. Cela aboutit à un très grand nombre de classes dans l'application. Ce qui à son tour, rend le développement et la maintenance du système plus compliqués. Avec les annotations, Java a gagné un moyen de bénéficier de méthodes pouvant individuellement s'abonner à des événements particuliers. Par exemple, examinez l'implémentation du modèle d'événement de CDI (Contexts and Dependency Injection) dans Java EE 6.
public class PaymentHandler { public void creditPayment(@Observes @Credit PaymentEvent event) { ... } } public class PaymentBean { @Inject @Credit Event<PaymentEvent> creditEvent; public String pay() { PaymentEvent creditPayload = new PaymentEvent(); // populate payload ... creditEvent.fire(creditPayload); } }
Comme vous pouvez le voir, PaymentEvent
est déclenché quand la méthode pay()
de l'objet PaymentBean
est appelée. Ensuite, la méthode CreditPayment()
de l'objet PaymentHandler
reçoit paymentEvent
.
Un autre exemple est l'implémentation du "Bus" d'événements de la librairie Guava:
// Cette classe est typiquement enregistré par le conteneur class EventBusChangeRecorder { @Subscribe public void recordCustomerChange(ChangeEvent e) { recordChange(e.getChange()); } } // Quelque part pendant l'initialisation eventBus.register(new EventBusChangeRecorder()); // plus tard public void changeCustomer() { ChangeEvent event = getChangeEvent(); eventBus.post(event); }
L'"EventBus" enregistre l'objet de type EventBusChangeRecorder
. Ensuite l'appel à la méthode changeCustomer()
déclenche la réception d'un objet ChangeEvent
dans l'"EventBus" ainsi que l'appel de la méthode recordCustomerChange()
de l'instance d'EventBusChangeRecorder
.
Maintenant, vous n'avez plus besoin d'implémenter des "Event listeners" pour vos composants, rendant ainsi plus facile l'utilisation des événements dans l'application. L'utilisation d'un "Bus" d'événements est pratique, quand tous les composants sont affichés en même temps à l'écran et qu'ils utilisent le "Bus" d'événements pour échanger des messages, comme le montre l'image ci-dessous.
Ici, tous les éléments - l'en-tête, le menu de gauche, le contenu, le panneau de droite- sont des composants.
Abonnés aux événements - n'oubliez pas de vous désabonner
En remplaçant les Event Listeners par des annotations, nous avons fait un grand pas en avant en simplifiant l'utilisation du modèle d'événements. Mais même ainsi, chaque composant du système a besoin d'être connecté avec le Bus d'événements, et alors doit s'abonner à des événements, et s'en désabonner au bon moment.
Il est possible d'atteindre une situation où le même destinataire est abonné à plusieurs reprises pour le même événement, ce qui peut conduire à un certain nombre de notifications répétées. Une situation similaire peut se produire lorsque plusieurs composants du système souscrivent au même événement, ce qui peut déclencher une série d'événements en cascade.
Pour être en mesure de mieux contrôler le modèle d'événement , il est judicieux de déplacer le travail avec les événements vers la configuration et de rendre le conteneur d'application responsable de la gestion des événements. Des événements particuliers n'étant disponibles que pour des conditions particulières, il est ainsi raisonnable de déplacer la gestion de leur état vers la configuration.
Un exemple de configuration est présenté ci-dessous:
<?xml version="1.0"?> <application initial="A"> <view id="A"> <on event="next" to="B"/> </view> <view id="B"> <on event="previous" to="A"/> <on event="next" to="C"/> </view> <view id="C"> <on event="previous" to="B"/> <on event="next" to="D"/> </view> <view id="D"> <on event="previous" to="C"/> <on event="finish" to="finish"/> </view> <final id="finish" /> </application>
La transition vers la vue B sera initialisée par l'événement "next" de la vue A. À partir de la vue B, l'utilisateur peut retourner à la vue A grâce à l'événement "previous" ou aller à la vue C par l'événement "next". Depuis la vue D, l'événement "finish" conduit à l'état "final" qui donne à l'application l'instruction de terminer son workflow.
Les machines à état fini sont spécialement conçues pour de telles situations. Une machine à état est un modèle de calcul mathématique. Il est conçu comme une machine abstraite qui peut avoir un nombre infini d'états, mais un seul à la fois, connu comme l'état courant. Un événement ou une situation peut déclencher une transition vers un autre état. En utilisant cette approche, vous pouvez facilement définir un écran actif et avoir un événement déclencheur de la transition vers un autre écran.
Les avantages de l'utilisation de machines à états finis pour la configuration de l'application
Dans la plupart des cas, la configuration de l'application est défini statiquement. En Configurant l'application avec l'injection de dépendance, nous définissons sa structure au démarrage. Mais on oublie que, tout en parcourant l'application, son statut peut changer. Le changement d'état est souvent codée en dur dans le code de l'application, ce qui apporte des complications pour les futurs réglages et la maintenance.
Déplacer les transitions entre les états dans la configuration donne plus de flexibilité. Et c'est pourquoi, lors de la création d'éléments composites d'application, tels des formulaires, des fenêtres ou des panneaux, nous n'avons pas à nous soucier de l'état de l'application. Vous pourrez le faire plus tard, en paramétrant le comportement dans la configuration.
Tous les composants peuvent communiquer à l'aide d'un mécanisme standard pour l'envoi d'événements - à travers le Bus d'événements. Dans le même temps, la machine à états permet de contrôler l'abonnement aux événements de composant dans le Bus d'événements. Cette approche transforme toutes les composants de l'application (formulaires, fenêtres, panneaux) en composants réutilisables qui peuvent être facilement gérés à partir de la configuration externe.
Si vous êtes intéressé, vous pouvez jeter un œil à quelques exemples de configuration dans l'Enterprise Sampler.
Vous pouvez envisager la configuration d'états comme une carte routière de la ville, et les événements comme les voitures qui livrent des marchandises et des personnes vers les destinations souhaitées.
Je suis sûr qu'en utilisant à cette approche, il est facile de concevoir et de construire non seulement un petite maison évolutive, mais aussi toute une ville avec des gratte-ciel, avenues et autoroutes.
A propos de l'auteur
Aliaksei Papou est CTO, Architecte logiciel et co-fondateur de Lexaden.com. Il a plus de 10 ans d'expériences dans le développement d'applications d'entreprise. L'innovation des technologies est sa passion. Aliaksei ont développé le langage Lexaden Web Flow qui permet la création de très grandes et robustes applications d'entreprise.