insideIT.fr : le blog des architectes IT de SFEIR

Aller au contenu | Aller au menu | Aller à la recherche

jeudi 10 février 2011

Retour d'expérience d'un projet GWT : 5 Implémentation Modèle-Vue-Présenteur (5/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Voyons maintenant comment créer une architecture Model-View-Presenter.

Le présenteur doit étendre la classe Presenter qui fournit des fonctionnalités de base à notre application. Comme l'accès au global event bus et à la factory de vue.

public class TestPresenter extends Presenter {

    /**
     * Interface represents the test view
     */
    public interface Display extends DisplayView {
        public void setUserName(String username);

        public HasClickHandlers getUserNameClickHandler();
    }
   
    // fields
    /** View */
    Display display;
   
    // constructor
    public TestPresenter(Display display, HandlerManager globalEventBus, DisplayFactory factory) {
        super(globalEventBus, factory);
        this.display = display;
       
        bind();
    }
   
    // Bind all event
    private void bind(){
                getGlobalEventBus().addHandler(
            OnUserLoggedEvent.getType(),
            new  OnUserLoggedEvent.Handler() {
            @Override
            public void onUserLogged(OnUserLoggedEvent event) {
                display.setUserName(event.getUser()
                        .getLogin());
            }
        });
        display.getUserNameClickHandler().addClickHandler(
            new ClickHandler() {
                public void onClick(ClickEvent event) {
                    getGlobalEventBus().fireEvent(
                        new OnUserLogout());
            }
        }       
    }

    public void go(Placer panel) {
        panel.setView(display);
    }
}

Nous avons ici l'architecture de base d'un presenter :

  • Le constructeur prend en paramètre toutes les interfaces qu'il va utiliser :
    • sa vue interfacé par l'interface interne au presenter : Display
    • le global event bus (Bus d'évènement global à l'application pour la communication entre les presenter)
    • la factory de vue (utilisée pour instancier les vues des sous-presenters)
    • Éventuellement des services RPC
  • la méthode bind() qui s'occupe d'enregistrer le présenteur aux évènements globaux de l'application (ici le login de l'utilisateur) et aux évènements de la vue (ici le click sur le label affichant le login de l'utilisateur).
  • la méthode go(), qui permet au présenteur de s'afficher
    • la classe Placer est une abstraction d'un Panel GWT, pour être complètement indépendant de GWT, cette interface possède une simple méthode setView qui prend en paramètre un DisplayView. L'interface DisplayView qui doit être étendu par toutes les vues demande juste d'implémenter la méthode asWidget() qui permet de retourner la vue (this) en tant que Widget GWT.

Créons maintenant la vue, en utilisant UiBinder :

public class TestView extends Composite implements TestPresenter.Display {

    private static TestViewUiBinder uiBinder = GWT
            .create(TestViewUiBinder.class);

    interface TestViewUiBinder extends
        UiBinder<Widget, TestView> {}

    @UiField Label label;
   
    public TestView() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public HasClickHandlers getUserNameClickHandler() {
        return label;
    }

    @Override
    public void setUserName(String username) {
        label.setText(username);
    }

    @Override
    public Widget asWidget() {
        return this;
    }
}

Avec le fichier TestView.ui.xml associé :

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    xmlns:g="urn:import:com.google.gwt.user.client.ui">
    <g:Label ui:field="label" />
</ui:UiBinder>

Cette classe étend donc notre Display du présenteur et accède aux composant GWT.

On peux ainsi tester facilement le code de notre présenteur car il ne possède aucune adhérence à GWT.

Nous avons choisi dans un premier temps de se passer de ces framework et d’appliquer les pattern MVP en suivant uniquement les pratiques conseillées par Google. Maintenant que nous avons une première expérience avec le développement MVP, nous pouvons penser à utiliser un de ces framework pour bénéficier de leurs utilitaires comme la gestion de l’historique, du pattern action etc. Si nous devions en choisir un à ce jour, ce serait gwt-platform qui est issu de gwt-presenter et gwt-dispatch.

lundi 24 janvier 2011

Retour d'expérience d'un projet GWT : 4 Architecture Modèle-Vue-Presenteur (4/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Le MVP ou modèle vue présenteur est un modèle de conception qui a les objectifs suivants:

  • Découplage : le découpage en couche permet de construire des applications modulaires compatibles avec les contraintes de dépendances du chargement à la demande
  • Testabilité : Chaque classe destinée a être testée out-of-the-box possède une interface qui permettra de bouchonner certains comportements.
  • Optimisation des appels de service
  • Centralisation des erreurs
  • Gestion de l'historique
  • Anticipation, sur les évolutions du web comme HTML5...

Les 3 concepts du MVP sont :

  • Le modèle, les données de l'application, souvent issues de la base de données, il est destiné à être manipulé par l'UI. Il n'a pas d'adhérence avec les autres classes MVP, il est isolé.
  • La vue, la partie de la page web affichée dans l'interface utilisateur, qui se compose des widgets et des conteneurs de widgets.
  • Le présenteur, le controleur du pattern MVC sauf qu'il rend totalement étanche les échanges entre la vue et le modèle (alors que MVC permet à la vue de référencer directement ses modèles). Il porte leur le comportement de l'application.

MVP

La vue est passive et c'est le présenteur qui s'occupe des traitements et de la logique évenementielle. La vue et le présenteur partagent un contrat grâce à une interface. Pour que le contrôleur puisse être correctement tester avec JUnit sans utiliser la version de GWT qui utilise HtmlUnit pour lancer les tests, il faut que le contrôleur ne possède que du code Java hors code GWT (composant GWT, GWT.create(), Internationalisation ...).

Le MVP s'appuie sur plusieurs briques dont certaines sont facultatives :

  • Un gestionnaire d'évènement ou EventBus qui a pour but de découpler les différentes couches qui communiquent par l'émission d'évènements.
  • Un moteur d'IOC pour faciliter la création et la liaison des différents object. (facultatif)
  • Le pattern action pour simplifier les appels de services et offrir une meilleur testabilité. (facultatif)

Pour comprendre au mieux, le fonctionnement concret du modèle de conception MVP, il faut lire les 2 articles de Google suivants :

Un projet d'exemple est proposé par Google

Schéma de fonctionnement de GWT avec MVP sans UiBinder:

MVP ss UiBinder

Schéma de fonctionnement de GWT avec MVP avec UiBinder:

MVP avec UiBinder

Il existe également d'autres articles expliquant le fonctionnement de MVP :

Il existe un projet d'exemple illustrant le fonctionnement du MVP : http://code.google.com/p/gwt-mvp-sa...

Dans le livre "la programmation GWT2" de Sami Jaber, un article détaille le fonctionnement de MVP en s'appuyant sur une framework existant : http://code.google.com/p/gwt-presenter/

Il existe de nombreux framework MVP pour GWT comme gwt-presenter, gwt-platform ou mvp4g.

Dans GWT 2.1, GWT a introduit un framework MVP, cependant celui-ci décrit le principe.

jeudi 4 novembre 2010

Nouveautés GWT 2.1

GWT 2.1 vient de sortir en version finale, voici les nouveautés apportées par cette version :

  • Data Presentation Widgets
  • MVP Framework (Activities and Places)
  • RequestFactory
  • Editor
  • Server-side Speed Traces
  • Logging
  • Safe HTML

Détaillons un peu ces nouvelles fonctionnalités :

Data Presentation Widgets

Ce nouveau framework permet de construire des tableaux dynamiques (liste, arbres). Ces composants peuvent afficher un très grand nombre de données, car il va construire les données en minimisant les appels au DOM de la page.

MVP Framework (Activities and Places)

Depuis 2 ans, Google pousse le design patern Model View Presenter (MVP) grâce à une présentation faite par Ray Ryan à Google IO ainsi que deux articles :

http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture.html

http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture-2.html

De nombreux frameworks ont permit d'implémenter ce nouveau design pattern :

A la différence des frameworks précédents, la version de GWT 2.1 utilise une autre terminologie, en voici un petit lexique, et explication.

Vue

Tous d'abord, la notion de vue n'existe plus, une vue est un simple widget GWT ou plutôt un composite qui peut être une classe UiBinder.

La nouveauté c'est que tous les widgets Gwt implémentent maintenant l'interface IsWidget, qui demande une seule méthode Widget asWidget();

Maintenant une vue dont l'implémentation est un simple Widget peut être définie par une interface, tout en pouvant ensuite l'ajouter dans un conteneur.

Une vue étend une interface du Presenter qui permet à celui-ci de communiquer avec la vue.

Activité (Presenter)

Un Presenter se nomme maintenant une activité (Activity), et s'occupe de toute la logique métier de l'écran.

Un Presenter peut implémenter une interface venant de sa vue pour simplifier la gestion des événements avec UiBinder, c'est à dire qu'on crée dans la vue la méthode onClick accompagné de l'annotation @UiHandler, dans laquelle on appelle une méthode du Presenter (Activity) qui gérera ensuite l'action. L’ancienne technique avait pour principe de définir une méthode dans la vue (getter) qui retourne une interface permettant d’ajouter un Handler par le Presenter (i.e. HasClickHandlers).

Une activité doit implémenter deux méthodes :

  • start(AcceptsOneWidget containerWidget, EventBus eventBus) : Appelé lorsque l'activité doit s'afficher
  • onStop() : Appelé lorsque l'activité doit disparaître (lors qu'une autre activité viendra la remplacer ou que l'on quite la page)

Deux autres méthode doivent aussi être implémentées :

  • mayStop() demande un message à afficher si on veux demander à l'utilisateur si il veux réellement changer de page (indiquer qu'ils n'a pas sauvegarder un formulaire par exemple). Il suffit de retourner null si l'activité accepte le changement de page.
  • onCancel() : Appelé si l'activité n'a pas encore eu le temps d'être afficher et qu'elle doit déjà être remplacer par une autre activité.

Remarque: En étendant la classe AbstractActivity, seule la méthode start() doit être implémentée.

AcceptsOneWidget est aussi une nouvelle interface ajoutée à Gwt laquelle est implémentée par SimplePanel, et dans laquelle la vue doit être ajoutée. Cela évite de devoir utiliser l'interface HasWidgets ainsi que le Presenter fasse un clear() avant de faire un add() pour remplacer le widget composant la vue principale.

ActivityManager

L'ActivityManager, c'est un peu le pendant de l’AppControler (l'autre partie est remplacée par le PlaceController), car il s'occupe de changer les activités affichées lorsqu'une nouvelle page doit être affichée. Cette classe est directement implémentée par GWT 2.1. Chaque zone pouvant afficher des activités aura son propre ActivityManager.

Places

C'est là, la grosse nouveauté de ce nouveau framework MVP, car on ne retrouve pas cette information dans les autres frameworks MVP. Pour ceux qui connaissent la programmation Android, une Place a le même rôle qu'un Intent. Une Place représente le lieu où l'on se trouve, s'est à dire la page (activité) qui est affichée. Changer de page ou d'activité revient à changer de Place, d'où la méthode goTo(Place newPlace) qui est dans le PlaceControler.

Le PlaceControler représente ainsi la deuxième partie de l'AppControler, présenté par les anciens articles. A la différence de l’ActivityManager, le PlaceController est unique pour l’application.

Il ne peut pas y avoir deux Places affichées simultanément. Une Place peut contenir des données, c'est une simple classe à créer qui étend Place où l'on met les propriétés que l'on veut.

Pour qu'une Place puissent être utilisée comme URL (et ainsi utiliser les fonctionnalités précédent et suivant du navigateur), il faut utiliser le PlaceHistoryMapper qui permet de transformer une Place en String et vice versa.

Gwt propose de générer automatiquement cette classe. Il nous faut alors créer un Tokenizer pour chaque place qui va sérialiser et désérialiser les attributs d'une Place de la façon que l'on veut. Il va ensuite afficher dans l'URL le nom de la Place (nom de la classe pouvant être redéfinis par l'annotation @Prefix("")) La classe PlaceHistoryHandler s'occupera ensuite de changer l'url et la gestion de l'historique automatiquement.

ActivityMapper

Cette classe permet de faire la conversion entre une Place et une Activity. Elle s'occupe d'instancier l'Activity pour une Place donnée. C'est à nous de la créer mais avec Gin, cela peut se faire facilement.

EventBus

C'est un bus d'événement, il permet de faire communiquer tous les composants d'une application avec les autres applications. L'ancien HandlerManager n'existe plus, l'interface EventBus est désormais utilisée. Une implémentation simple existe : SimpleEventBus. Attention, une activité doit ajouter ses évènements dans le "start" car une fois qu'elle est supprimée les événements sont retirés automatiquement.

Model

Le model est implémenté par la RequestFactory qui permet de faire transiter des entities JPA /JDO entre le server et le client. Il apporte automatiquement les méthodes nécessaires pour construire un CRUD (Créer, Lire, Mettre à jour, Supprimer). RequestFactory n'a pas pour vocation à remplacer RPC et peux être utilisé indépendemment. Seules les données modifiées de l’entité transiteront sur le réseau.

Editor

Apporte un système de binding entre les objets java et les représentations graphiques en particulier pour créer les formulaires de création et de modification. Plus d'informations Vous pouvez retrouver la documentation officielle avec un projet d'exemple :

http://code.google.com/intl/fr/webtoolkit/doc/trunk/DevGuideMvpActivitiesAndPlaces.html

et voici quatre autres articles qui expliquent le fonctionnement des Places et Activities :

Server-side Speed Traces

Les informations de performances venant du serveur (Google App Engine ou Spring TC Server) peuvent maintenant être affichées dans l'outil Speed Tracer.

Logging

Il existait déjà Gwt-Log qui a été maintenant intégré directement à GWT 2.1. On utilise l'Api standard Java pour logger (java.util.logging). Par-contre la configuration de logging se fait dans le fichier .gwt.xml

# In your .gwt.xml file
<inherits name="com.google.gwt.logging.Logging"/>
# To change the default logLevel
<set-property name="gwt.logging.logLevel" value="SEVERE"/>  
# To disable logging        
<set-property name="gwt.logging.enabled" value="FALSE"/>      
# To disable a default Handler      
<set-property name="gwt.logging.consoleHandler" value="DISABLED"/>  

# In your .java file
Logger logger = Logger.getLogger("NameOfYourLogger");
logger.log(Level.SEVERE, "this message should get logged");

Les logs peuvent être ensuite affichés de différentes façons,

  • SystemLogHandler - Ecrit dans la sortie du mode de développement de GWT
  • DevelopmentModeLogHandler - Utilise l'ancienne méthode GWT.log (les logs ne sont visibles qu'en mode développement)
  • ConsoleLogHandler - Ecrit dans la console Javascript (Firebug ou console Chrome)
  • FirebugLogHandler - Ecrit dans la console Firebug
  • PopupLogHandler - Affiche les logs dans une popup
  • SimpleRemoteLogHandler - Envois les logs par un RPC où l'on peut logger ensuite sur le serveur.

Safe HTML

Safe HTML permet de filtrer du texte pour protéger le HTML qui le contient et éviter les injections XSS. Il fonctionne comme un StringBuilder, mais peut échapper automatiquement les caractères spéciaux. Un système de template permet d’écrire du code HTML facilement en utilisant un mécanisme similaire aux Messages mais en protégeant le code HTML.

public interface MyTemplates extends SafeHtmlTemplates {
   @Template("<span class=\"{3}\">{0}: <a href=\"{1}\">{2}</a></span>")
   SafeHtml messageWithLink(SafeHtml message, String url, String linkText,
       String style);
 }

 private static final MyTemplates TEMPLATES =
     GWT.create(MyTemplates.class);

 public void useTemplate(...) {
   SafeHtml message;
   String url;
   String linkText;
   String style;
   // ...
   InlineHTML messageWithLinkInlineHTML = new InlineHTML(
       TEMPLATES.messageWithLink(message, url, linkText, style));
   // ...
 }

Voilà un rapide tour d'horizon des nouveautés de GWT 2.1, d'autres articles suivront pour expliquer plus précisément certains points. Ces nouveautés une fois maitrisées et utilisée sont vraiment puissante et apporte encore plus de simplicités à la fois pour le développeur mais aussi pour l'utilisateur.

samedi 18 septembre 2010

Mvp avec mvp4g

Depuis la conférence Google I/O 2009, le pattern architectural MVP fait parler de lui à un point où un grand nombre de framework open source s’est répandu, chacun proposant son implémentation plus ou moins élaborée. Tous contiennent au moins la gestion du présenteur et ensuite on y trouve ou non un event bus, une gestion de l’historique, injection de dépendances, pattern action, ....

C’est sur google code que j’ai découvert Mvp4g  qui couvre une bonne partie de ses fonctionnalités.

Pour vous le faire découvrir, nous allons reprendre le code de l’application de gestion de contacts de l’article de google sur le mvp : http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture.html

Pour cet exercice, nous prendrons la dernière version de mvp4g qui vient l’être releasée ainsi que de ses dépendances :

  • mvp4g-1.2.0

  • commons-lang 2.4

  • commons-configuration 1.6

  • gin 1.0

  • guice 2.0

  • aop-alliance.

Comme pour n’importe quel bibliothèque gwt, il faut la déclarer dans notre fichier gwt.xml :


<inherits name='com.mvp4g.Mvp4gModule' />


Maintenant, faisons un tour d’horizon des modifications que nous allons devoir apporter :

  • La partie Model ne changera pas : Mvp4g ne couvrant pas l’Action pattern, nous ferons appel au méthodes exposée en rpc tout comme dans le code original.

  • La partie Presenter, elle sera une des parties qui changera le plus : nous devrons étendre une classe du framework et répondre à son contrat.

  • La partie View ne changera que très peu : nous n’aurons qu’à changer l’interface implémentée.

  • L’Event Bus : un des autres gros changement. Mvp4g propose en effet son propre modèle avec un système d’évènements haut niveau assez souple.

  • La gestion de l’historique, là aussi tout est à refaire.

  • L’Entry Point : Avec mvp4g, nous n’avons pas forcément besoin d’en écrire un ...

A cela, nous rajouterons une injection de dépendances via gin qui n’est pas dans le code original. C’est aussi dans cet esprit que se configure mvp4g. Que ce soit pour lier les différents composants ou spécifier le comportement dus bus ou de l’historique. Elle peut être soit réalisée en xml ou par annotations. J’ai préféré l’utilisation des annotations.

J’imagine que le point sur l’entry point, vous a laissé perplexe mais attention ce n’est pas parce que nous n’allons pas en écrire un que nous n’en aurons pas. C’est justement parce que l’application s’assemble en fonction des annotations que cette partie peut devenir générique. Nous utiliserons donc celui que nous propose mvp4g en incluant cette ligne dans le gwt.xml :


<entry-point class='com.mvp4g.client.Mvp4gEntryPoint' />


Pour les plus curieux, voici le code qui y correspond :


Mvp4gModule module = (Mvp4gModule)GWT.create( Mvp4gModule.class );

module.createAndStartModule();

RootPanel.get().add( (Widget)module.getStartView() );

Et c’est tout. Le code étant assez simple, il se comprend de lui même. Tant que nous n’aurons qu’un seul module gwt, il nous sera suffisant.

Passons maintenant au centre névralgique de notre application : le bus évènementiel. Dans une application mvp4g, il est indispensable. C’est bien sûr par lui va passer la distribution des événements haut niveau mais aussi quelques autres notions liées comme la gestion de l’historique et surtout l’action “start”.

A 3ème ligne de l’entry point, vous aviez sans doute remarque le getStartView. Il n’y a rien de magique, c’est par la configuration du bus que nous la connaîtrons.

Allez, mettons nous en route , pour coder notre bus. En fait, nous n’aurions rien à implémenter puisqu’il s’agit d’une interface. Elle devra néanmoins étendre l’interface EventBus de mvp4g et être annotée :


@Events( startView = RootView.class)

public interface   ContactsEventBus extends EventBus {

}


C’est sur l’annotation @Events que nous définissons RootView en tant que vue chargée s’affichant au démarrage. Fidèle à son nom, elle sera notre vue racine. Mvp4g conseille de disposer d’une telle vue qui servira de container aux autres vues. Comme toute vue dans un modèle MPV, elle sera statique et sera associée à un présenteur. L’interface qui servira de contart avec le présenteur sera celle ci :

public interface RootViewInterface {

     Panel getBody();

}

et l’implémentation sera la suivante :


public class RootView extends  Composite  implements  RootViewInterface {

private  SimplePanel body = new SimplePanel();

      public RootView() {

            VerticalPanel mainPanel = new VerticalPanel();

            mainPanel.add(body);

            initWidget(mainPanel);

      }

      @Override

      public Panel getBody() {

            return body;

      }

}


Le présenteur quant à lui sera de la forme suivante :


@Presenter( view = RootView.class )

public class RootPresenter  extends  BasePresenter<RootViewInterface, ContactsEventBus>


Là encore, nous avons du code assez simple à comprendre . Nous annotons le présenteur pour qu’il puisse être détecté par mvp4g et étendons la classe BasePresenter<V, E extends EventBus> pour nous permettre d’interagir avec la vue mais aussi avec notre bus. De la même manière, nous aurons : un ContactsPresenter et un EditContactPresenter avec des vue telle qu’on les a dans le code original.

Nous avons déjà la base de notre application mais pour l’instant, elle ne fait rien. Dans notre cas, nous voudrions qu’au lancement, la liste des contacts soit affiché. Tiens, nous tenons un évènement haut niveau et nous voudrions qu’il soit lancé au lancement... C’est encore une fois le bus qui va s’en occuper, encore un fois par une simple annotation.

Puisque nous voulons lister, nous allons créer un évènement “list” tout simplement. A la différence du mvp proposé par google dans l’article, nous n’aurons pas à créer une classe héritant de GwtEvent et de lui associer une handler. Nous allons seulement ajouter une méthode qui porte le nom de cet évènement à l’interface de notre bus :


@Start

@Event(handlers = ContactsPresenter.class)

void list();


C’est grâce à l’annotation @Start que l’évènement sera lancé au lancement client de l’application et sera délégué à notre ContactPresenter. A noter que dans notre cas, nous n’avons qu’un seul handler. Il aurait été tout à fait possible d’en avoir plusieurs en les séparant par une virgule.

Il nous reste à implémenter la réaction du présenteur face à cet évènement. Le modèle évènementiel de mvp4g repose sur une convention simple : pour un méthode XXX du bus, le présenteur devra implémenter une méthode onXXX tout simplement. Bien qu’il ne soit pas possible à s’assurer de la concordance entre l’évènement est sa méthode réceptrice lors de la compilation java, ce contrôle est réalisé lors de la phase de compilation gwt.

Revenons à notre cas, nous aurons donc une méthode onList() dans notre ContactsPresenter :


public void  onList(){

      contactsService.getContactDetails(newAsyncCallback<ArrayList<ContactDetails>>() {

            @Override

            public void  onSuccess(ArrayList<ContactDetails> result) {

                  doList(result);

            }

            @Override

            public void  onFailure(Throwable caught) {

                  eventBus.errorMessage(ErrorMessages.RPC_CALL_FAILED);

            }

      });

      eventBus.changeBody(view.asWidget());

}

Pour déclencher un évènement haut niveau, il suffit d’appeler la méthode associée du bus.C’est ce que nous faisons par exemple avec le changeBody qui permet à la vue ContactView de devenir active.

Nous pouvons aussi transmettre une valeur ou plusieurs valeurs lors du déclenchement de l’évènement. Nous rencontrons ce cas pour l’édition :


@Event(handlers = EditContactPresenter.class)

void  edit(String id);

Vous avez aussi sûrement remarqué, l’appel au service rpc. Sa déclaration est très simple, nous utiliserons l’injection GIN par une simple annotation sur son setter :


@Inject

public void setContactsService(ContactsServiceAsync contactsService) {

      this.contactsService = contactsService;

}

Quant à la phase de binding des événements bas niveau par les présenteurs, elle est est analogue à celle du code original : nous n’aurons qu’à implémenter la méthode bind().

Nous aurons par exemple :


view.getAddButton().addClickHandler(new ClickHandler() {

      public void  onClick(ClickEvent event) {

            eventBus.add();

      }

});

Si vous avez peur de ne pas savoir où vont vos évènements, l’annotation @Debug sur le bus vous aidera en loggant en console ce qui se passe autour du bus. Il existe 2 modes de LogLevel : SIMPLE et DETAILLED, par défaut ce sera la première valeur. Regardons ce qui se passe dans les 2 modes sur un évènement edit .

Tout d’abord en mode simple :

16:47:05.355 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: edit || param(s): 18

16:47:06.551 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: changeBody || param(s): [...plein de html...]

et en mode détaillé :

16:50:53.187 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: edit || param(s): 18

16:50:53.751 [INFO] [contactsmvp4g] com.sfeir.contacts.client.presenter.EditContactPresenter@640782 handles edit

16:50:54.052 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: changeBody || param(s): [...plein de html...]

16:50:54.058 [INFO] [contactsmvp4g] com.sfeir.contacts.client.presenter.RootPresenter@16e4f00 handles changeBody

Intéressons nous maintenant à l’historique. Mvp4g utilise la notion de convertisseur d’historique. Ce qui signifie que nous devrons implémenter la façon dont les évènements seront convertis en url et vice et versa. Nous allons devoir implémenter l’interface HistoryConverter<E extends EventBus> et comme toujours l’annoter. Notre application étant relativement simple, nous n’en aurons qu’un seul mais sachez qu’il est tout à faire possible d’en avoir plusieurs. Pour répondre à son contrat, nous devrons implémenter les 2 méthodes suivantes :

  • convertFromToken( String eventType, String param, E eventBus ) : réaction lors d’une url tokenisée.
  • isCrawlable() : indique si le l’url sera crawlable en renvoyant un booléen.

La gestion de l’historique viendra se greffer sur le bus événementiel. Pour gérer l’historique, nous rajouterons :


@Events( startView = RootView.class, historyOnStart = true)


Réfléchissons d’abord un peu. Sur quoi aimerions nous avoir une historique avec des urls identifiables ? J’en vois 3 :

  • La liste des contact

  • Ajout d’un contact

  • Edition d’un contact. Idéalement, il serait intéressant d’avoir un paramètre permettant d’identifier le contact que nous éditons.

Le modèle d’historique de mvp4g utilise un token situé après un "#" dans l’url. Ce token correspond tout simplement au nom de l’évènement. Nous avons aussi la possibilité d’assigner un valeur à ce dernière en le placant derrière un "?".

Exemple : #token?value

Cette solution convient à notre exigeance de mieux marquer l’url lors de l’édition d’un contact. Pour lier, un évènement à notre convertisseur d’historique, il suffit de lui assigner sur le bus :


@Start

@InitHistory

@Event(handlers = ContactsPresenter.class, historyConverter=ContactHistoryConverter.class)

void  list();


L’annotation @InitHistory sert à marquer quand l’historique doit être initialisé. Celle est obligatoire si vous voulez utiliser l’historique. Revenons à notre convertisseur :


@History

public class  ContactHistoryConverter implements HistoryConverter<ContactsEventBus> {

      @Override

      public void convertFromToken(String  eventType, String param, ContactsEventBus eventBus) {

            if(“edit”.equals(eventType)){

                  eventBus.edit(param);

            } else if(“add”.ADD.equals(eventType)){

                  eventBus.add();

            } else if(“list”.equals(eventType)){

                  eventBus.list();

            }

      }

      @Override

      public boolean isCrawlable() {

            return  true;

      }

}


La première méthode est assez claire : nous appelons la méthode correspondante au token avec une particularité pour edit où nous nous servirons de la valeur en paramètre du token comme paramètre edit.

Même si l’IDE ne signale aucune erreur, la compilation gwt en provoquera. Il nous faut la réciproque de la méthode convertFromToken : c’est à dire indiquer quelle sera la valeur du paramètre d’un token. Ainsi, pour chaque évènement XXX historisé,il nous faudra une méthode onXXX qui renvoi une valeur sous forme de chaîne de caractères. Nous aurons ainsi par exemple :


public String  onEdit(String id){

      return id;

}

public String  onList(){

      return "";

}


Un reproche possible à faire à mvp4g est

Un des reproches possible à faire est l’assemble dynamique et instanciation des composants. Nous n’avons pas toujours besoin d’en disposer au moment au l’ont accède à l’application. Dans notre cas, nous n’avons pas besoin d’édition d’un contact tant que n’a pas cliqué sur un contact. Rassurez vous, la construction paresseuse est là. Pour cela, nous allons changer la super classe pour LazyPresenter<V extends LazyView, E extends EventBus>. En plus de cela, notre interface de vue devra étendre l’interface LazyView. Cela va perturber un peu notre code et des erreurs de compilation vont s’inviter. Avant de les corriger, intéressons nous à la classe LazyPresenter et en particulier sa méthode bind()


final public void bind() {

      view.createView();

      createPresenter();

      bindView();

}


Voilà la clé de nos corrections :

  • Les constucteurs de vue et de présenteur deviendront respectivement createView() et createPresenter()

  • La méthode bind() du présenteur deviendra bindView()

Personnellement, j’ai remarqué qu’après ses modifications, le lancement était plus rapide.

Le principal avantage d’un découpage mvp est la testabilité. Notre présenteur étant décorélé de problématique d’affichage, il sera alors plus simple de le tester. En effet, en mockant la vue, les services et le bus, nous pourrons nous concentrer nos tests sur la logique métier. De plus avec le système évènementiel haut niveau de mvp4g, il est simple de tester la réception d’un événement.

Un aspect qui peu rebuter les plus intégriste du modèle mvp est la gestion évènement de haut niveau spécifique de mvp4g et principalement ses performances. Quelles sont elles ? Le wiki du projet répond à cette question :

http://code.google.com/p/mvp4g/wiki/Mvp4gPerformances

Les performances semble honorables. Bien sûr, tout produit vante ses performances, mais bon joueur les tests sont disponibles.

La question à se poser avant tout est de savoir ce que l’on teste exactement. En observant le code, on voit qu’un évènement est lancé est attrapé par son réceptacle. Rien de plus. Pour éviter un conflit entre les deux, un test se fait en temps : d’abord mgp4g puis gwtEvent.

Pour la partie benchmarking, j’ai réalisé depuis le lien appspot les même tests sous 3 navigateurs dans leur version courante :

  • Chrome 6

  • Internet Explorer 8

  • Firefox 3.6

Mes résultats diffèrent, mais attention cela ne signifie pas les résultats sur la page mvp4g sont faux. En effet, pour avoir reproduit ces même tests sur différentes machines possédant le même navigateur, j’obtenais des résultats avec une différence assez importante (jusqu’à x 10 entre un bon pc fixe et un simple netbook) :

Voici mes résultats pour 1000 évènements lancé :



mvp4g

gwt

Chrome 6

31

122

Internet Explorer 8

477

3609

Firefox 3.6

206

312

Ces résultats vont dans le même sens, c’est à dire que le modèle événementiel de mvp4g est plus rapide que celui de gwt.

Bien sûr, GWT 2.1 va arriver avec son modèle MVP intégré. Quand il sortira, on pourra re-comparer les temps de réaction événementielle. En attendant, sa sortie vous pouvez vous amuser au mpv avec mpv4g, qui propose bien d’autres fonctionnalités encore comme les filtres d’évènements, la gestion du multi-module, ....


Le code source complet est joint à cet article.