GWT – Hibernate et les RequestFactory

Dans les architectures mettant en œuvre des clients RIA écrits en GWT, la communication client-serveur est une problématique récurrente. Dans une telle application où le développement du client et du serveur se font tout deux en Java, le domaine métier est implémenté une seule fois et partagé entre le client et le serveur. Les objets sont échangés au moyen de l’API GWT RPC, solution historique pour invoquer des services distants. Cette API met en place un mécanisme de sérialisation des données.
L’utilisation du framework Hibernate pour gérer la persistance des objets métier rend impossible le transfert de ces objets via le mécanisme de sérialisation de GWT. Nous allons voir dans quelle mesure cette limite est contraignante ainsi que les différentes manières de traiter cette problématique.

Problématique

GWT RPC permet d’effectuer des appels asynchrones du client vers le serveur. Tout objet transporté sur le réseau doit être sérialisable (les règles de sérialisation diffèrent un petit peu de l’API standard Java). On peut instinctivement penser qu’il nous suffit de respecter cette contrainte en rendant sérialisables tous les types impliqués dans les contrats d’interfaces pour se mettre à l’abri de toute erreur de sérialisation. C’est vrai tant que l’on conserve la maîtrise des types des objets que l’on souhaite sérialiser. Que se passe t-il si les objets à envoyer côté client sont des entités chargées depuis une base de données à l’aide de l’ORM Hibernate ?
Hibernate, pour rendre les classes persistantes, les instrumente en modifiant le bytecode durant l’exécution de l’application au moyen de la librairie Javassist. De cette manière, la définition des classes durant l’exécution est différente de la définition des classes issue du processus de compilation. Ce mécanisme bien qu’invisible du point de vue du développement rend impossible l’échange d’objets entre le client et le serveur au moyen du mécanisme de sérialisation.
Plusieurs solutions existent pour contourner ce problème. Dans la suite, je vous propose de comparer plusieurs méthodes, leurs mises en œuvre, leurs avantages et inconvénients.

Les différentes approches

J’ai choisi de traiter la problématique d’intégration de GWT avec le framework Hibernate. Cependant, cette problématique existe de manière identique avec d’autres ORM et d’une manière encore plus générale la problématique existe à partir du moment où les objets à envoyer coté serveur ne sont pas sérialisables.
Nous allons ici comparer trois approches permettant de traiter cette problématique :

  • Le pattern DTO (Data Transfert Object)
  • Le framework Gilead
  • Les RequestFactory

Je vais vous présenter chacune des approches en mettant particulièrement l’accent sur les RequestFactory car il s’agit de la solution la plus jeune, la moins connue, et certainement la moins triviale en terme de mise en œuvre.

Le pattern DTO

Il s’agit probablement de la solution la plus souvent implémentée à ce jour dans les applications GWT / Hibernate. C’est également la plus facile à définir et à mettre en place. Ce pattern d’architecture consiste à définir de nouveaux objets (appelés objets de transfert) qui sont de simples POJO. Ils ne sont pas instrumentés, ne contiennent pas d’intelligence mais seulement les données qui doivent êtres échangées entre le client et le serveur. Il est donc très facile de les rendre sérialisables afin qu’ils deviennent éligibles au transfert via des appels de services RPC.
Souvent, les données présentent dans un DTO correspondent exactement aux données affichées à l’écran ou sur une partie de l’écran. Les DTO peuvent avoir la même structure que les objets persistés mais ce n’est pas nécessairement le cas. La représentation des objets en base de données n’est pas toujours adaptée à une utilisation coté client.
La création et l’alimentation des DTO est en général faite de manière manuelle et reste à la charge du développeur. Cependant, il est possible d’utiliser des frameworks de mapping Objet/Objet qui permettent de définir au moyen d’un fichier de configuration XML des couples d’attributs qui doivent être mappés l’un sur l’autre. Le framework de ce type le plus connu est Dozer. Attention toutefois, dans certains cas l’usage de tels framework peut s’avérer être une mauvaise idée. Si par exemple, les DTO contiennent beaucoup de valeurs calculées ou si les types des attributs sont souvent différents entre les DTO et les objets persistés, la définition d’un tel mapping bien que toujours possible, peut être lourde à mettre en place et difficile à maintenir.

Le framework Gilead

La solution Gilead est présentée ici pour alimenter la discussion technique mais ce framework n’est pas une option d’avenir car il n’est plus maintenu. La dernière version (1.3.1), vieille d’un peu plus de deux ans, supporte les annotations JPA2, Hibernate 3.5 et GWT 2.0.
La solution consiste, contrairement au pattern DTO, à rendre sérialisables les entités persistantes sans définir de nouveaux objets. Pour cela, Gilead parcours les graphes d’objets et remplace les proxys Hibernate non sérialisables par des références null et les implémentations spécifiques Hibernate (e.g. PersistentSet) par des implémentations issues de JavaSE.
La plus-value apportée par le framework est sa capacité à stocker (soit côté serveur, soit dans les objets eux mêmes) les méta-données nécessaires à la reconstruction des graphes d’objets Hibernate. De cette manière, un objet persisté envoyé puis modifié côté client pourra sans problème être rattaché à une session Hibernate à son retour côté serveur.
Sa mise en œuvre est plutôt simple. Elle se résume en trois points :

Les entités persistantes doivent hériter de la classe LightEntity

Les classes d’implémentation RPC doivent hériter de PersistentRemoteService

Le PersistentBeanManager de Gilead doit être initialisé

Il n’y a que quelques lignes de code ou de configuration à écrire pour mettre en place le mécanisme qui s’exécute ensuite de manière transparente.
Comme annoncé en introduction, ce projet est au point mort. L’arrêt de la maintenance est intervenu à peu près au moment de l’arrivée d’une nouveauté dans GWT : Les RequestFactory.

Les RequestFactory

Les RequestFactory sont apparues dans la version 2.1 de GWT. Elles ont été introduites pour répondre à la problématique dont il est ici question.
Les RequestFactory vont nous permettre de faire transiter sur le réseau les données contenues dans des objets non sérialisables. Comme son nom l’indique, le but d’une RequestFactory est de créer des requêtes. Pour cela, une API plutôt minimaliste fournie une sorte de builder permettant de construire une requête HTTP qui sera plus tard envoyée en POST au serveur.
Le corps de cette requête est une chaîne de caractères au format json dans laquelle on trouve principalement les informations suivantes :

  • Les données d’identification du service à invoquer coté serveur
  • Les paramètres d’appel du service
  • Les noms des propriétés correspondantes aux relations qui devront être transmises côté client

Coté serveur, le traitement de cette requête va être délégué à une RequestFactoryServlet. Celle-ci se charge de parser la requête, d’effectuer l’appel de service, puis de sérialiser le résultat dans la réponse envoyée au client.
Par opposition au mécanisme RPC qui offre une vision service, les RequestFactory offre une vision requête. Nous verrons d’ailleurs plus tard dans un exemple que l’envoie des requêtes devra être codé explicitement par le développeur, ce qui est totalement transparent dans le cas d’un appel RPC.
Il y
a tout de même un point commun entre les deux approches, dans les deux cas les appels serveur sont fait de manière asynchrone. Là où en RPC on utilise un AsyncCallback pour traiter le retour de l’appel, on utilisera un Receiver avec les RequestFactory.
On lit souvent que les RequestFactory sont orientées ressources par opposition au mécanisme RPC qui est orienté services. Je ne serais pas si catégorique. Effectivement, initialement les RequestFactory étaient destinées à faciliter les opérations CRUD sur des entités persistantes dans le cadre d’une architecture orientée ressources. Cependant, il n’y aucun inconvénient à les utiliser pour effectuer tout type d’appel de service.
Passons maintenant à un exemple de mise en œuvre au travers d’un cas concret. L’exemple qui va suivre n’a pas vocation à être exhaustif mais simplement à mettre en avant les principales caractéristiques des RequestFactory.
Soit le domaine métier ci-dessous. Il s’agit d’un simple annuaire dans lequel une personne peut avoir plusieurs adresses.
AddressBook Class Diagram
Nous avons deux entités reliées par une relation qui sera déclarée Lazy. Il s’agit du cas le plus simple nous permettant de mettre en œuvre les RequestFactory de manière non triviale.
Ci-dessous le code des entités coté serveur (allégé des getters et setters ainsi que des méthodes equals et hashcode).
Entité Person

@Entity
@Table(
    name="person",
    uniqueConstraints=@UniqueConstraint(
        name="unique_firstname_lastname", columnNames={"firstname", "lastname"}
    )
)
public class Person {
	@Id
	@GeneratedValue
	private int id;
	@Version
	private int Version;
	private String firstname;
	private String lastname;
	private String phone;
	private String email;
	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
	@JoinColumn(name = "person_id")
	private Set<Address> addresses;
	...
}

Entité Address

@Entity
@Table(name="address")
public class Address {
	@Id
	@GeneratedValue
	private int id;
	@Version
	private int Version;
	private String line1;
	private String line2;
	private String zipcode;
	private String city;
	private String state;
	private String country;
     ...
}

Durant l’exécution de l’application, ces entités ne seront jamais visibles coté client. En effet, une contrainte imposée par la RequestFactory est de définir des proxys qui mappent les entités existantes coté serveur. Ainsi, coté client on manipulera toujours ces proxys. L’implémentation des proxys est générée durant l’exécution par la RequestFactory.
La création d’un proxy est simple et systématique. Il s’agit d’une interface qui étend EntityProxy et qui déclare les méthodes d’accès et de mise à jour des propriétés avec un nommage identique aux propriétés des entités en accord avec la spécification JavaBeans. L’interface est annotée par @ProxyFor pour indiquer le nom de la classe correspondante coté serveur.
Interface PersonProxy

@ProxyFor(Person.class)
public interface PersonProxy extends EntityProxy {
	public int getId();
	public void setId(int id);
	public String getFirstname();
	public void setFirstname(String firstname);
	public String getLastname();
	public void setLastname(String lastname);
	public String getPhone();
	public void setPhone(String phone);
	public String getEmail();
	public void setEmail(String email);
	public Set<AddressProxy> getAddresses();
	public void setAddresses(Set<AddressProxy> addresses);
}

Interface AddressProxy

@ProxyFor(Address.class)
public interface AddressProxy extends EntityProxy {
	public int getId();
	public void setId(int id);
	public String getLine1();
	public void setLine1(String line1);
	public String getLine2();
	public void setLine2(String line2);
	public String getZipcode();
	public void setZipcode(String zipcode);
	public String getCity();
	public void setCity(String city);
	public String getState();
	public void setState(String state);
	public String getCountry();
	public void setCountry(String country);
}

Nous allons ensuite définir la fameuse RequestFactory. Elle est unique pour toute l’application. Il s’agit d’une interface qui étend RequestFactory et qui contient des méthodes renvoyant des stubs des interfaces de services. Dans notre cas, nous aurons une seule interface de service.
La RequestFactory de l’application

public interface AddressBookResquestFactory extends RequestFactory {
	PersonRequest personResquest();
}

L’interface de service étend RequestContext. Elle est annotée de manière à définir la classe d’implémentation des services. Cette classe peut être l’entité elle même. C’est la solution la plus simple, c’est celle que nous allons mettre en place ici. La classe d’implémentation peut aussi être une classe DAO dédiée en définissant un ServiceLocator. Ce point ne sera pas abordé ici.
L’interface va comporter deux services :

Pour lire un objet Person par son nom et prénom

Pour sauvegarder ou mettre à jour un en base de données un objet Person

Interface de services PersonRequest

@Service(Person.class)
public interface PersonRequest extends RequestContext {
	Request<PersonProxy> read(String firstname, String lastname);
	Request<Void> saveOrUpdate(PersonProxy person);
}

Comme vu précédemment, ces services sont implémentés directement dans l’entité Person

public static Person read(String firstname, String lastname) {
     // lecture de la personne sans ses adresses
}
public static void saveOrUpdate(Person person) {
     // Mise à jour de la personne
}

En réalité, l’entité n’implémente pas l’interface au sens stricte, mais la classe d’implémentation doit être en phase avec l’interface. On constate que les méthodes de l’interface PersonRequest renvoient des objets de type Request paramétrés par le type de retour effectif des méthodes implémentées coté serveur.
NB. Le plugin Eclipse de développement GWT détecte les incohérences entre les interfaces RequestContext et leurs implémentations.
Par ailleurs, les méthodes d’implémentation sont déclarées statiques car elles ne s’appliquent pas à une instance en particulier. Il est en revanche possible de définir des méthodes d’instances lorsque cela a du sens. Le cas typique est la définition d’opérations CRUD.
Côté serveur, il reste à déclarer dans le descripteur de déploiement de l’application Web, la servlet qui va traiter les requêtes.

<servlet>
	<servlet-name>requestFactoryServlet</servlet-name>
	<servlet-class>com.google.web.bindery.requestfactory.server.RequestFactoryServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>requestFactoryServlet</servlet-name>
	<url-pattern>/gwtRequest</url-pattern>
</servlet-mapping>

Afin que le client GWT puisse utiliser la RequestFactory, nous devons ajouter une ligne de déclaration dans son descripteur de module.

<inherits name='com.google.web.bindery.requestfactory.RequestFactory' />

Le client peut maintenant instancier la RequestFactory et l’initialiser en lui passant en paramètre l’EventBus de l’application.

AddressBookResquestFactory requestFactory = GWT.create(AddressBookResquestFactory.class);
requestFactory.initialize(new SimpleEventBus());
Envoyer une requête de lecture

Créons maintenant une requête pour rechercher une personne par ses nom et prénom.

Request<PersonProxy> req =
    requestFactory
        .personResquest()
        .read("Barack", "Obama");

La requête est exécutée de manière asynchrone en appelant la méthode fire(Receiver). Le traitement du retour de l’appel est à implémenter dans la méthode onSuccess.

req.fire(new Receiver<PersonProxy>() {
	@Override
	public void onSuccess(PersonProxy person) {
		...
	}
});

En cas d’échec, la méthode onFailure(ServerFailure) est appelée. Dans la classe abstraite Receiver, l’implémentation de cette méthode consiste à lever une RuntimeException.
Si on effectue un appel à person.getAddresses() dans la méthode onSuccess on s’aperçoit que l’on obtient null. C’est normal me direz-vous, étant donné que l’implémentation de la méthode read coté serveur ne charge pas les adresses. Mais en réalité, la raison est ailleurs.
Par défaut, la RequestFactoryServlet ne sérialise pas les propriétés correspondantes aux relations si ce n’est pas explicitement spécifié dans la requête. Ce qui veut dire qu’ici, même si la relation entre les entités Person et Address était déclarée Eager cela ne changerait absolument rien aux données reçues côté client.
Pour cela, Il faut utiliser la méthode with(String...) de la classe Request pour indiquer les propriétés qui doivent être récupérées par le client.

Request<PersonProxy> req =
    requestFactory
        .personResquest()
        .read("Barack", "Obama")
        .with("addresses");

Ce qui précède soulève au moins deux questions intéressantes :

Comment la RequestFactoryServlet va t-elle exploiter cette nouvelle information ?

A qui incombe la responsabilité de charger la collection d’adresses ?

En fait, ces deux questions sont intimement liées.
La réponse à la première question vient assez intuitivement. La RequestFactoryServlet va simplement invoquer, sur l’objet de type Person récupéré de l’appel de service, les getters correspondants aux propriétés qui doivent être sérialisées.
Nous savons déjà que la méthode read ne charge pas les adresses. Que se passe t-il à ce stade lors du traitement de la requête par la servlet ? Si la session Hibernate est toujours active, la collection d’adresses sera chargée lors de l’appel du getter. Dans le cas contraire, nous serons inévitablement sanctionné par une LazyInitializationException.
Évidement, changer le mode de chargement de la collection d’adresses de Lazy à Eager rendrait notre scénario passant mais ce n’est pas ce que nous souhaitons ici. En gardant la relation en mode Lazy, ce problème peut être traité de deux manières :

  • En modifiant l’implémentation du service pour qu’il charge les adresses
  • En faisant en sorte que la session Hibernate reste active durant toute l’exécution de la requête

La première solution présente l’avantage de garder la maîtrise du chargement des graphes d’objets. Si par exemple, on souhaite charger les adresses par jointure en une seule requête. En mode lazy, le chargement des adresses sera inévitablement fait par l’envoi d’une requête supplémentaire. En revanche, en construisant la requête coté client, on peut être tenté d’omettre la clause with si on ne désire pas rapatrier les adresses. Le résultat sera conforme à l’attendu coté client mais coté serveur les adresses auront été remontées inutilement de la base de données.
Les avantages de la deuxième solution sont les inconvénients de la première, et vice et versa. A mon sens, cette deuxième approche est celle qui permet de tirer le meilleur profit des RequestFactory. Et cela car quelle que soit la requête construite côté client :

Tout risque de provoquer une LazyInitializationException est écarté

Aucune donnée non désirée n’est chargée inutilement (tant que les relations sont en mode lazy)

Modifier des entités coté client

Si vous tentez d’invoquer un setter sur un EntityProxy issu d’un appel serveur, vous obtiendrez une exception du type IllegalStateException avec comme message The AutoBean has been frozen. Ce message n’est pas vraiment explicite. Ici, il faut comprendre que le proxy n’est pas éditable. Il doit préalablement être marqué comme tel.

PersonProxy person = ...
PersonRequest editRequest = requestFactory.personResquest();
person = editRequest.edit(person);

Lorsqu’un proxy est rendu éditable, les proxy correspondants aux relations deviennent eux aussi éditables. Ensuite pour renvoyer coté serveur les données modifiées il faudra utiliser le même RequestContext que celui utilisé pour rendre le proxy éditable. Ici, nous envoyons une requête pour mettre à jour l’objet en base de donnée en appelant le service saveOrUpdate(Person).

editRequest.saveOrUpdate(person).fire(new Receiver<Void>() {
	@Override
	public void onSuccess(Void response) {
		...
	}
});

Un des avantages de la RequestFactory réside dans le fait que seules les données modifiées depuis le chargement de l’objet seront sérialisées dans la requête envoyée au serveur et non l’objet person dans sa totalité. Dans beaucoup de cas, cela permet de réduire la quantité de données qui transite sur le réseau.

Critique des différentes approches

Nous avons vu trois manières de traiter la problématique d’intégration GWT/Hibernate. Laquelle choisir dans le cadre de la réalisation de votre projet ? Cela dépend évidement du besoin.
De tout ce qui précède, il est évident que la solution la plus flexible consiste à utiliser le pattern DTO. Mais bien que facile à mettre en place, le coût de réalisation est élevé. Beaucoup de codes répétitifs à écrire, à tester et à maintenir. Cette approche reste cependant la seule valable si la définition d
es entités persistantes n’est pas adaptée à une utilisation coté client.
La solution Gilead est attrayante. C’est celle dont coût de mise en œuvre est le plus faible. Il n’est pas nécessaire de définir d’autres objets que les entités, et le framework se charge de tout pour les rendre sérialisable. Évidement, il faut avoir la volonté d’utiliser les entités coté client. Malheureusement, Gilead n’étant plus maintenu, il n’est plus une solution à envisager.
Les RequestFactory sont aujourd’hui la seule approche standard GWT pour traiter cette problématique. Leur utilisation n’est pas triviale. En comparaison avec GWT RPC, il faut un peu plus de temps pour les appréhender, mais une fois maîtrisées leur utilisation n’est pas vraiment compliquée. Cette solution impose d’écrire des proxys coté client pour chacune des entités mais comme vu précédemment, le code de ces proxys peut être déduit du code des entités. Dans le cadre d’une démarche MDA par exemple, ces proxys pourraient sans aucun problème faire l’objet d’une génération de code. Comme pour Gilead, il faut souhaiter utiliser coté client des objets similaires aux entités définies coté serveur. L’utilisation du lazy loading par la RequestFactoryServlet permet de définir des services plus génériques. Un autre avantage mentionné précédemment est la réduction de la taille des données qui circulent sur le réseau.

Conclusion

Comme vu tout au long de cet article, la solution à choisir dépend clairement des besoins tant fonctionnels que techniques. Les DTOvont permettre de mettre en place des solutions plus spécialisées, tandis que les RequestFactory permettent de définir assez facilement des solutions plus génériques.
Ces deux approches ne sont pas nécessairement exclusives. Dans un système complexe, les besoins sont souvent variés. L’approche la plus productive est certainement de tirer le meilleur de l’une et l’autre des solutions selon les cas d’utilisation. Par exemple, pour une simple saisie de formulaire qui va solliciter des services CRUD, l’approche RequestFactory est clairement la plus adaptée alors que dans d’autres cas plus complexes l’approche DTO sera à privilégier.
Depuis l’apparition des RequestFactory en version 2.1, il y a eu d’importantes évolutions ainsi que de nombreuses corrections. De nouvelles corrections sont également planifiées dans la version 2.5 à venir. Il s’agit donc d’une technologie vivante qui continue de s’améliorer de version en version. Elle semble promise à un bel avenir et continuera certainement de faire parler d’elle.
L’application exemple commentée dans cet article est disponible sur github.

9 pensées sur “GWT – Hibernate et les RequestFactory

  • 18 mars 2012 à 1 h 49 min
    Permalink

    Bonjour,

    Pour l’initialisation des adresses, il y a une solution intermédiaire également. En ajoutant les associations à charger en paramètre de la méthode read, on peut ensuite demander à Hibernate de fetch chacune des associations fournies :

    // Méthode read
    Criteria criteria = session.createCriteria( Person.class ).add( Restrictions.eq( « firstname », firstname ) ).add( Restrictions.eq( « lastname », lastname ) );
    if ( null != associationPaths )
    {

       for ( String associationPath : associationPaths )    {        criteria.setFetchMode( associationPath, FetchMode.JOIN );    }

    }
    return (Person) criteria.uniqueResult();

    // Build request
    Request<PersonProxy> req = null;
    if ( searchPanel.isFetchAdresses() )
    {

       String[] paths = new String[] { "addresses" };    req = requestFactory.personResquest().read( firstname, lastname, Arrays.asList( paths ) );    req.with( paths );

    }
    else
    {

       req = requestFactory.personResquest().read( firstname, lastname, null );

    }

    Cela permet à la fois de choisir la méthode de fetch (join ou select) tout en gardant l’avantage de ne pas charger inutilement. Il est cependant dommage de devoir indiquer en double (with() + paramètre) les associations à charger.

    Pour la génération des proxys, voici un plugin Eclipse que j’ai écris : https://github.com/nmorel/GenerateG

    Répondre
  • 18 mars 2012 à 11 h 42 min
    Permalink

    Bonjour,
    Très bon article bravo.
    Mais un petit bémol, vous oubliez de parler du cache au niveau du ServiceLocator ce qui empêche de coupler les requestfactory avec des EJBS… De ne pas pouvoir gérer ce cache est un gros inconvénient à mon avis. Pour utiliser GWT avec des EJBs les DTOs sont la bonnes vieilles solutions malheureusement..
    Si quelqu’un a réussi à remédier à ce problème, je suis intéressé 😉

    Répondre
  • 26 mars 2012 à 16 h 56 min
    Permalink

    Bonjour,

    Merci à tous les deux pour vos commentaires.

    @Nicolas Morel
    La solution que vous proposez est effectivement valable et c’est d’ailleurs la plus optimisée. Mais comme vous l’indiquez, cela oblige à préciser « en double » la volonté de charger la relation. Je trouve que c’est un inconvénient, principalement en terme de qualité et de maintenabilité du code mais parfois cela ne peut pas être évité.

    @Nicolas G
    Effectivement, je n’ai pas du tout abordé ce point dans l’article. J’ai volontairement choisi de ne pas trop parler du ServiceLocator, j’ai juste indiqué qu’il existe. Mais c’est en effet un point intéressant qui méritera peut-être de faire l’objet d’un prochain article 😉

    Répondre
  • 27 mars 2012 à 17 h 21 min
    Permalink

    Bonjour,

    @Nicolas G
    Dans le cas de Spring il est assez facile de récupérer le contexte dans le ServiceLocator. Pas d’équivalent avec les EJBs ?

    Si ça peut servir, pour Spring ça donne :
    HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
    ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
    Un context.getBean(…) et c’est bon.
    Peut-etre un début de piste là pour les EBJs : http://forum.springsource.org/showt

    Répondre
  • 30 mars 2012 à 15 h 59 min
    Permalink

    Bonjour,

    j’aimerais bien savoir comment vous faites pour garder la session hibernate ouverte pour que RequestFactory puisse charger un attribut lazy 🙂

    Répondre
  • 30 mars 2012 à 16 h 56 min
    Permalink

    Bonjour,

    Il s’agit du pattern OpenSessionInView. Dans mon exemple j’ai simplement étendu la RequestFactoryServlet en redéfinissant la méthode « service » pour ouvrir la session, appeler la méthode « service » de la classe mère puis fermer la session.

    https://github.com/ggiamarchi/GWTRe

    Mais ce n’est en réalité pas très propre. Ici, je suis allé au plus rapide. Dans la pratique, on fera cela plutôt avec un intercepteur AOP ou en définissant un « servlet filter » comme décrit dans l’article ci-dessous

    https://community.jboss.org/wiki/Op

    Répondre
  • 30 mars 2012 à 17 h 01 min
    Permalink

    J’avais déjà ce filtre sur mes appels rpc, je l’ai ajouté sur le RequestFactoryServlet et le .with(« attributEnLazy ») fonctionne maintenant. Merci de la réponse.

    Répondre
  • 5 avril 2012 à 19 h 37 min
    Permalink

    Bonjour,
    J’ai essayé la solution avec la RequestFactoryServlet et sa fonctionne très bien. Il faudrait seulement ajouter que le beginTransaction() et le commit() devraient être présent seulement dans la RequestFactoryServlet, ce qui est logique, puisque toute les requêtes passent par cette servlet .

    Très bon post !

    Répondre
  • 6 juin 2012 à 12 h 26 min
    Permalink

    Avec RF, les methodes appelées sur un RequestContext sont « batchées » dans une seule requête HTTP, mais RF n’a aucune notion de transaction, et c’est à mon avis une erreur de le considérer comme tel (pour commencer parce que ça nous empêche d’utiliser le même RequestContext pour différentes parties de l’application, cf. RequestBatcher et RequestContext#append).

    Il vaut mieux faire ses transactions dans chaque méthode de service, quitte à définir des services plus « coarse grained ».

    Et pendant que je suis là: l’article n’aborde pas non plus le fait que RequestFactory peut être utilisé dans un client Java (par exemple une application Android, ou une autre appli web serveur (approche SOA)) contrairement à GWT-RPC; et que RF est un protocole, et pas seulement une API: exit donc les « serialization policy » de GWT-RPC qui changent à chaque recompilation de l’appli, le client qui doit être mis à jour à la moindre virgule changée sur un DTO (parce que ça invalide la serialization policy), le protocole obscure qui change à chaque nouvelle version de GWT (bon OK, c’est pas vrai, mais n’empêche qu’il y a une demi-douzaine de versions de GWT-RPC) et rend du coup quasi-impossible l’écriture de clients qui ne seraient pas des applis GWT.

    En bref, RequestFactory corrige tous les problèmes de GWT-RPC, que deRPC avait tenté de supprimé aussi, pour se voir finalement remplacé par RF: cf. https://developers.google.com/web-t… (et comparer avec https://developers.google.com/web-t…)

    Au passage, si vous avez testé RF avec GWT 2.4 et rencontré des problèmes, re-testez avec GWT 2.5 quand il sortira (dans un petit mois), puis éventuellement avec GWT 2.5.1 (prévu pour la rentrée environ)

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :