All posts in JPA

Portail Liferay avec GWT intégré en SOA pour Pierre et Vacances

Aujourd’hui je viens vous faire partager une très belle expérience projet. Je vous parlerai donc d’un projet que nous avons réalisé pour notre client Pierre et Vacances, qui a su nous faire confiance jusqu’au bout pour le développement et l’infogérance de son extranet commercial.

Faire des choix en matière d’architecture logiciel en 2010 peut être un exercice difficile, c’est pourquoi l’approche de DocDoku en matière de choix techniques est basée sur les principes suivants :

  • pragmatisme, dans le choix des langages et des technologies,
  • ouverture d’esprit, nous effectuons une veille technologique permanente,
  • mise en contexte, chaque problème étant différent, les réponses à apporter doivent l’être également,
  • industrialisation, il ne s’agit pas de s’emparer de la toute dernière nouveauté mais de bâtir des solutions robustes, matures et maintenables,
  • respect des standards établis, pour garantir la pérennité des applications, et maximiser l’investissement consenti par nos clients, nous nous appuyons sur les standards unanimement reconnus.

Dans le cadre de l’extranet commercial développé pour Pierre et Vacances, les grands principes architecturaux retenus ont été les suivants :

  • utilisation du composant portail open source Liferay permettant un gain de productivité certain en particulier sur les modules génériques (droits d’accès, profils utilisateurs, agenda, publication de news…) mais respectant également la JSR 286, norme de communication entre le portail et les applications qui y sont hébergées, supportée par de nombreux éditeurs (IBM, Sun, Oracle, RedHat). Ceci permet de surcroît de limiter notre dépendance vis-à-vis d’une solution portail en particulier.
  • développement d’une architecture en couches permettant notamment de découpler les composants les uns des autres et de les faire évoluer indépendamment.  Une séparation en couches permet d’adapter en effet l’architecture physique en fonction des besoins de distribution pour une bonne capacité à monter en charge.  Une architecture n-Tiers en 4 couches distinctes a été choisie :
    • La couche d’accès aux données avec JPA-Hibernate dont le rôle est la connexion à la base de données, l’exécution des requêtes ou l’appel des procédures stockées.
    • La couche de service EJB3 et JAX-WS-Metro, ensemble des règles métiers et processus de l’application. Cette couche sera implémentée en Java via des composants transactionnels.
    • La couche métier POJO qui constitue le diagramme de classes des entités (le modèle objet).
    • La couche de présentation avec GWT et Portlet Liferay : l’interface utilisateur affiche les résultats traités par la couche métier.  Ici le framework Ajax GWT a été choisi pour à la fois offrir une ergonomie poussée et une forte maintenabilité.
  • mise en place d’une architecture de type SOA pour pouvoir exposer de façon universelle les fonctions de l’extranet pour les futures applications du SI.

Tout ceci est en production évidemment depuis la fin octobre 2009.

Agilement.

Le cache d’entités JPA

Si le recours à un ORM (Object-Relational Mapping) permet des gains de productivité et facilite la maintenance des applications, ce type de framework introduit néanmoins des problématiques nouvelles, qui, pour être évitées, exigent une très bonne connaissance des mécanismes internes au moteur de mapping.

Parmi ces difficultés classiques, on peut citer la gestion du cache d’objets.

La validation finale de la spécification de JEE 1.6 (JSR 316) qui inclut la version 2 de JPA, intégrant des évolutions appréciables en matière de prise en compte du cache, est l’occasion de revenir ici un peu sur le sujet.

En réalité les ORM possèdent deux caches distincts :

jpa_caches

Cache de premier niveau (L1)

Le cache de premier niveau est rattaché à l’objet EntityManager, il s’agit en fait du « persistence context » qui lui est associé. Pour rappel, c’est à partir d’une instance d’EntityManager que les entités (les objets persistés en base de données) sont créés, récupérés ou encore supprimés. La classe EntityManager est définie par l’API standard JPA, elle est l’équivalente de la classe Session dans l’API native Hibernate.

Le contexte de persistance regroupe un ensemble d’entités en garantissant que pour une même entité il n’y aura qu’une seule instance d’objet. En prenant garde à ne pas dupliquer les objets de même identité, le contexte de persistance agit donc comme un cache d’objets : les recherches, qu’elles soient faites au travers d’une Query ou avec la méthode find(Class<T> entityClass, Object primaryKey), retourneront toujours les mêmes instances d’objets, piochées dans le cache, pour matérialiser un même enregistrement en base. Le contexte de persistance est lié à la transaction en cours, il sera vidé à la conclusion de celle-ci.

Le fonctionnement du contexte de persistance qui vient d’être décrit est celui qui est le plus couramment employé, il s’agit du mode PersistenceContextType.TRANSACTION. Néanmoins, il existe un autre mode dit « étendu », le paramétrage du contexte de persistance se fait dans le code Java, lors de l’injection de l’EntityManager :

@PersistenceContext(type=PersistenceContextType.EXTENDED)
private EntityManager em;

Dans cette configuration, le PersistenceContext n’aura pas une durée de vie corrélée à la transaction mais au Stateful Session Bean dans lequel il est déclaré. Ainsi, le cache d’objets sera maintenu sur plusieurs transactions à la base de données. Ce type de contexte de persistance peut par exemple être utilisé pour alimenter en données une page web requêtant en AJAX.

Cache de deuxième niveau (L2)

En dessous de ce cache placé sous la direction des EntityManagers se trouve un autre cache dit de second niveau. Ce cache est global à la JVM ou plus exactement à l’EntityManagerFactory. Il peut aussi être configuré au niveau cluster, dans ce cas le cache sera répliqué sur chaque JVM. Le cache de second niveau est donc partagé entre les EntityManagers qui iront l’interroger, si l’entité recherché n’est pas présent dans leur propre contexte de persistance, avant de se résigner à taper dans la base de données.

Si les règles régissant le cache de premier niveau sont relativement limpides, celles gouvernant le cache de 2ème niveau le sont un peu moins et le développeur peut légitimement se poser de nombreuses questions :

  • Quelle est la durée de rétention des objets dans le cache ?
  • Que se passe t-il si la base de données est modifiée en dehors de l’application JPA ?
  • Peut-on contrôler par configuration ou par du code ce cache ?

De manière générale, les réponses à ces questions dépendent avant tout de l’implémentation JPA choisie. D’ailleurs, si EclipseLink et TopLink activent par défaut un cache de second niveau ce n’est pas le cas d’Hibernate. Ce dernier a par contre été très tôt pensé de façon modulaire et accepte divers « cache provider » comme EhCache, OS Cache ou encore JBoss Cache. Chaque système de cache vient avec ses options et ses fichiers de paramétrages, il est souvent possible de les spécifier dans le fichier persistence.xml entre la balise <properties> ou dans le code par la méthode Query.setHint(String hintName, Object value).

En ce qui concerne les changements opérés dans la base sans passer par les couches de persistance de l’ORM ; là le résultat est identique quelque soit le framework : le cache de niveau 2 se retrouve obsolète et cela tant que l’entité en question ne sera pas rechargé. La plupart des caches d’objets utilisant des SoftReferences ou des WeakReferences, l’éviction de l’objet se produira à un moment indéterminé, au gré des pérégrinations du garbage collector. Pour ceux ne pouvant se contenter du caractère aléatoire des références faibles et qui veulent expressément déclencher le rafraichissement des données la classe EntityManager propose heureusement la méthode refresh qui s’utilise habituellement ainsi :

MonEntity e = em.find(MonEntity.class, id);
try {
em.refresh(e);
} catch(EntityNotFoundException ex){
e = null;
}

Toutefois, ce n’est que depuis la version 2 de JPA que le cache L2 est officiellement mentionné dans la spécification. Dans cette API, une classe Cache accompagnée de ses méthodes « evict » a fait son apparition et certains points de configuration ont été standardisés.

En conclusion

Il y aurait encore beaucoup à dire sur les caches d’entités et plusieurs billets seraient nécessaires, c’est pourquoi je conclurai simplement en invitant tous les développeurs et architectes à regarder sous le capon de leur framework de persistance et à ne pas se laisser « endormir » par la simplicité trompeuse de l’API JPA.