| « Hibernate, ce grand incompris | POJO strikes back » |
GWT + Hibernate = DTO
Après quelques jours de labeur, des discussions passionnées (voir acharnées) et quelques neurones en moins, je commence à voir le bout du tunnel technique en faisant cohabiter Hibernate et GWT.
Voyons voir ça…
GWT + Hibernate + Domain Driven Architecture = mariage impossible ?
L’idée de base, ô combien séduisante, est d’utiliser les objets du Domaine du bout en bout de l’application (Domain Driven Architecture). Google Web Toolkit utilisant Java, cela semblait même l’une des forces de cette union.
Hélas, des divergences techniques font que c’est à ce jour impossible pour les raisons suivantes :
- Héritage différent : les classes Hibernate implémentent
Serializable, tandis que GWT attend que les classes à transformer en JavaScript implémententIsSerializable. De plus, le compilateur GWT ne supporte pas l’interface JavaSerializableet affiche une erreur. Ce problème devrait cependant rapidement disparaître et fait partie de la roadmap GWT. - Types incompatibles : Hibernate utilise un
sqlDateTimeet unPersistentSeten lieu et place desDateetSetdéfinis par Java. A l’utilisation, pas de problème (les premiers héritant des seconds). A la sérialisation JavaScript par contre, GWT ne reconnaît pas ces types comme étant primitifs et sort logiquement en erreur. - Lazy-loading : c’est l’objet de mes deux précédents billets, je ne vais donc pas (trop) en rajouter. Pour faire simple, GWT (tout comme JAXB, XStream, etc…) ne sait manipuler que des POJO, et non des PHJO (Plain Hibernate Java Object ;)). La présence de Proxies, qui assure le fonctionnement transparent d’Hibernate, posent des problèmes de sérialisation et de lazy-loading exception évoqués précédemment.
Pour autant, faire cohabiter Hibernate et GWT est possible, il faut juste abandonner le doux rêve d’utiliser des objets du Domaine sur la couche de Présentation…
Une démarche pratique
L’utilisation de POJO dédiés Présentation (des DTO), outre les arguments que l’on trouve de-ci de-là (taille limitée des objets transitant sur le réseau notamment), présente l’avantage… de fonctionner !
Comme évoqué dans mon premier billet, j’utilise la librairie Dozer pour convertir mes objets du Domaine en DTO (qui héritent de IsSerialisable et n’utilisent que des types « primitifs » Java).
La solution la plus sûre consiste à définir des DTO qui couvrent exactement le graphe des données chargées par Hibernate, ce qui évite que la librairie ne lève une LazyIntializationException. Par contre, il devient nécessaire de définir autant de DTO que de variantes du graphe d’objets chargés par la couche de Données. Outre la dépendance induite, je n’aime pas trop le copier/coller nécessaire à cette solution, mais bon… Je réfléchis à une solution qui permettrait d’industrialiser élégamment cette phase.
Lors d’une mise à jour, il suffit de récupérer le POJO PHJO du Domaine (dans la session si elle est stateful, par requête sinon), et de mapper de nouveau la DTO avec Dozer. L’objet du Domaine est alors mis à jour et peut être sauvé en base avec Hibernate.
Tout est bien (même si ce n’est pas encore parfait) qui finit bien :D
10 commentaires
Contrairement à une approche d'utilisation de haut en bas des POJO du domaine, Mats Helander invoque le besoin d'avoir un modèle de présentation (PM) connecté au modèle du domaine (DM). Cela est nécessaire pour ajouter des propriétés et des méthodes de présentation qui pollueraient les classes du domaine : par exemple, le nom complet d'une personne, qui est la concaténation du prénom et du nom. Il évoque ensuite les deux solutions que sont le mapping et le wrapping pour faire parler les deux modèles.
Je me laisse petit à petit séduire par cette idée et je pense finalement que tu as peut-être tout à gagner avec ta solution DTO (je suis pas convaincu que ce soit le terme idéal).
Au plaisir de te relire.
As tu essayé d'utiliser tes beans modèle (les "vrais" POJO) comme cible pour Dozer afin d'y recopie le contenu des proxies hibernate ?
Client$EnhancedByCGlib -> [Dozer] -> Client
Ceci ne lèvre pas le problème du Serializable, mais ça évite au moins de construire un nouveau graphe objet pour chaque méthode métier.
La solution que tu préconises pose le problème du chargement du graphe d'objet sans son entier. De deux choses l'une : soit la session Hibernate est fermée, et Dozer s'arrête à cause d'une LazyInitializationException à le première propriété non chargée; soit ta session est ouverte, et dans ce cas, Dozer appelle tous les getters du POJO Hibernate, et là tout ton objet est chargé, ce qui peut être très, très volumineux...
Je ne m'avoue pas vaincue et j'espère trouver une solution élégante sous peu...
Bruno
Il s'arrète complètement ou passe à la propriété suivante ? Si GWT faisait ce travaille (try catch à chaque accès à une association) le problème serait résolu, non ? Je pars du principe que la session est fermé. Reste l'interface IsSerializable et les datetime.
Bref, il s'agit d'un bug, mais en l'état, la duplication est complétement arrêtée.
- incompatibilité de GWT avec Serializable (ce qui me semble aberrant) rendant impossible d'utiliser les objets du modèle
- stopOnErrors non supporté correctement par Dozer rendant impossible la recopie "génrique" des HJO en DTO
- utilisation de proxies par hibernate.
Je vois mal comment Hibernate peut se passer de proxies si on veut conserver les facilités du lazy-loading. La gestion du LazyInitializationException devrait (?) pouvoir être prise en charge de manière générique, avec un Dozer corrigé par exemple.
Reste le problème du Serializable et des types dérivés utilisés par Hibernate. Dozer doit pouvoir assurer la conversion des sqlDateTime en Date, non ?
Pour l'héritage avec Serializable, il s'agit d'une limitation de GWT qui est extrêmement structurante, cette interface étant utilisée un peu partout... Je n'ai pas encore expérimenté GWT aussi je ne sais pas dans quelle mesure on peut le customizer ou l'étendre.
On dépend donc du bon vouloir de Google pour qu'il fasse évoluer son produit pour le rendre + compatible avec Hibernate.
Mais est-ce que la compatibilité avec Hibernate est prioritaire pour Google ?
En open-source, la communauté java aurait pu développer cette fonctionnalié en réponse au réel besoin dont tu fais part.
Je partage également ton idées par rapport à la conversion des objets Hibernate en DTO pour une utilisation avec GWT. Pour le faire aisément, il suffirait d'implémenter au niveau de la couche service une sorte d'Assembleur qui se chargerait d'attacher et de détacher les objets Hibernate. (Conversion Objet Hibernate vers DTO et vice-versa)
L'utilisation d'un générateur pour produire les DTOs à partir de la configuration ou des annotations Hibernate éviterait de coder tout cela.
Quelques réponses en vrac :
- le support de Sérializable est prévu sur la roadmap GWT : on peut donc espérer que ce problème soit résolu sous peu.
- GWT est complétement Open-Source depuis la version 1.3. Cela concernen tout autant les Widgets que le framework et le compilateur Java/JavaScript.
Ça fait déjà un problème de résolu par Google