NHibernate(encore lui) et identité
Par Jean-Baptiste le lundi 17 mars 2008, 17:11 - Code - Lien permanent
J'en parlais donc il y a quelques instants, mais me revoici pour un autre petit billet sur ce bien bel outil qu'est NHibernate. Ce coup-ci, laissez moi vous entretenir du principe d'identité des entités.
Pour résumer, il existe deux manières de récupérer des entités depuis une session nhibernate:
- le "get" sur l'identité de l'entité
- les requêtes (que ce soit par sql, hql, ou criteria)
Dans le cadre ou nous voulons récupérer une seule entité, normalement le get est la solution la plus performante, d'autant plus qu'il est capable de chercher dans le cache de premier niveau pour voire si nous n'avons pas déjà une référence (ce qui arrive bien plus souvent qu'on pense). La requête pour aller chercher une résultat unique n'est pas capable de chercher dans le cache, et va en plus émettre un flush pour être sûr d'être consistante.
Pourquoi est-ce que je vous dis tout ça? Et bien parce qu'une veille croyance de l'informatique à laquelle je m'accrochais moi-même consistait à dire que toute entité à un identifiant interne gérée par la base (le bon vieux int auto-increment), et que tout ce qui est idendité "fonctionnelle", comme par exemple un numéro de sécurité sociale, une plaque d'immatriculation, un numéro de compte etc etc etc, n'est pas considérée comme clef primaire par besoin de "simplification".
Le problème de cette approche, vous l'avez peut être deviné, est que nous allons la grande majorité du temps aller chercher nos entités via ces identifiants fonctionnelles plutôt que par l'identifiant auto générée. Nous passons ainsi à côté de toute la puissance du "get" standard de nhibernate, et passons notre temps à faire des auto-flush plus ou moins coûteux pour aller chercher des entités que nous avons en plus déjà en cache de premier niveau là plupart du temps. Le cache de second niveau dans notre cas nous a permis de résoudre en partie ce problème, mais ce n'est qu'une astuce qui camoufle un problème de fond.
Donc, ma conclusion serait de ne pas hésiter à déclarer comme identifiant des notions plus fonctionnelles. Si elles nous garantissent l'unicité et l'identité d'une entité, pourquoi utiliser autre chose qui plus est complètement dépourvu de sens?



Commentaires
Je ne suis pas tout à fait d'accord, il y a au moins 2 raisons pour lesquelles utiliser un identifiant fonctionnel comme clé primaire n'est pas souhaitable, du moins au sens SGBD :
- Un identifiant fonctionnel est susceptible de changer. Or une clé doit être la plus stable possible : idéalement elle ne devrait pas bouger, sous peine de devoir effectuer fréquemment des modifications en cascade et de voir les erreurs se multiplier.
- Les SGBDR sont beaucoup plus à l'aise avec des identifiants numériques. Ils tiennent infiniment moins de place d'où des index plus compacts et des performances accrues.
De plus les index cluster, couramment utilisés pour les clés primaires, tirent beaucoup mieux parti d'un identifiant chronologiquement séquentiel (comme un int auto incrémenté) car ils sont nativement triés par cet identifiant et les données les plus demandées sont souvent les plus récentes chronologiquement.
Ceci dit cela peut relever d'un arbitrage entre les performances propres de la base et celles de nhibernate.
@Max :

Je vais tenter de te répondre puisque je connais un peu le contexte dans lequel ce billet a été écrit
1. Le changement : c'est à proprement parler le but de la méthode XP que d'accepter les changements. La peur du changement ne doit pas conditionner les choix que nous faisons. Si un jour l'identité change alors un refactoring s'imposera. Les tests unitaires seront là pour vérifier qu'on n'a pas tout cassé.
2. Les performances : là encore, je pense que c'est une erreur de modéliser une application autour d'hypothétiques problèmes de performances. Je mesure ce qu'il y a de choquant dans ma phrase quand je dis "hypothétiques problèmes" mais c'est volontaire. S'il faut tordre le modèle ou la façon dont sont persistées les données, cela doit être fait à la suite d'un constat et d'une mesure. Dans ce cas, et au moment opportun seulement, on pourra faire le choix entre la puissance du cache de premier niveau de NHibernate et l'amélioration obtenue par un identifiant numérique; tout ceci sera fait dans le contexte d'exécution de l'application.
Pour aller plus loin, je précise que l'idée d'identité permettant de distinguer deux entités vient d'Eric Evans et de Domain Driven Design. Je t'invite à chercher des informations dans cette direction pour bien comprendre notre position.
Je note le "nous" de ta réponse fabien
Nous n'avons plus de volonté propre je vois.
Sinon désolé, je n'ai rien d'intéressant à ajouter, Fabien a bien résumé le contexte.
Certes, XP débride les réticences au changement et le facilite, en matière de génie logiciel et de gestion de projet informatique.
Je parlais plutôt de la modification de valeurs d'identifiants au cours de la vie d'une base de données de production. La modification de la valeur d'une clé primaire (changement de plaque d'immatriculation ou de toute autre donnée non statique utilisée comme identifiant) engendre une cascade de mises à jour dont le coût est lourd. Le risque de laisser la base dans un état non cohérent est aussi beaucoup plus grand que s'il s'était agi d'un update sur toute autre colonne.
Tu parles d'un "moment opportun" pour éventuellement effectuer des remaniements sur ce point central de la structure d'une base de données que sont les clés primaires. Le problème, c'est que ce moment ne devrait jamais arriver dans un environnement de production pour des raisons évidentes ("refactoring" des tables => rupture de service, recalcul des index, réorganisation des sauvegardes...). A moins de disposer de tests préalables de montée en charge reflétant parfaitement ce que sera la charge future de l'application, ce qui est rarement le cas, la mesure de mauvaise performance dont tu parles ne ressortira qu'une fois que le mal est fait, en prod. Il me parait donc sage de prévoir à l'avance le modèle de données qui sera potentiellement le plus efficace.
Ce que tu dis est correcte Max, le seul soucis que je vois, c'est le "si". "Si la clef primaire change". Est-ce que je dois trainer un boulet en allourdissant mon design parce qu'hypothétiquement je vais avoir un changement de clef primaire?
Ma réponse a tendance à être non. Pourtant je croise un peu trop souvent à mon goût des applications lourdes et compliquées à reprendre car elles ont voulu gérer les "si".
L'identité d'une entité doit rester la même pendant la durée de vie du système. Si trouver un attribut ou un groupe d'attribut d'une entité permettant de garantir cet invariant s'avère impossible alors une solution d'un identifiant généré automatiquement, voire invisible pour l'utilisateur, est tout à fait correcte. L'idée est ici est d'avantage la suivante : si j'ai un attribut qui identifie clairement mon entité, pourquoi ne pas l'utiliser ? La difficulté est souvent d'identifier cet ou ces attributs.
Tu dis qu'il faut "prévoir à l'avance le modèle de données qui sera potentiellement le plus efficace". Cela ne correspond pas à ma vision des choses. Une base de données peut être remaniée, même en production. Je préfère dire qu'il faut réagir pour améliorer l'efficacité. Encore une fois, à mon avis, l'ajout d'un identifiant numérique dans ce contexte sera exceptionnel.
@BodySplash : Loin de moi l'idée de vouloir prévoir la variabilité d'une clé primaire, je dis exactement le contraire ! Je préconise, et c'est un des BA-ba de la théorie relationnelle, que la clé primaire ne change pas ! Sa valeur doit rester stable dans le temps. Or la seule clé satisfaisant à cette exigence est une clé dénuée de toute sémantique métier : un identifiant numérique créé ex nihilo. La plaque d'immatriculation d'un véhicule changera. Le numéro de téléphone ou l'adresse mail d'une personne changera. Même un numéro de sécurité sociale peut changer, en cas d'acquisition de nationalité ou de changement de pays...
@Fabien Bezagu : entièrement d'accord, à l'intérieur des frontières du monde objet. Lorsqu'on entre dans le monde relationnel, les contingences sont toutes autres. On raisonne avant tout en termes de ressources matérielles et logiques à notre disposition : en termes de RAM, de disque, de fichiers de données, de pages, de blocs d'index, de journaux de transactions... C'est peut-être regrettable, mais l'optimisation des SGBDR est encore intimement liée au support physique et système sous-jacent. Cela fait l'objet de cette discipline à part entière qu'est le tuning de base de données, et de 30 ans de R&D de la part des industriels du secteur.
On peut vouloir faire abstraction d'une partie de ces règles en vigueur dans le monde des bases de données, mais c'est subir à coup sûr une dégradation significative des performances de l'application.
La solution pourrait être de s'affranchir du SGBD tout court, mais cela relève encore actuellement plus du domaine de la science-fiction que de la réalité...
Bonjour,
Je relance le sujet un peu tard mais une question me travaille actuellement. Dans mon boulot, il y a un grand débat par rapport au mapping avec la base de données. Il s'agit d' "NHibernate contre les dataset typés". On me soutien qu'avec ces fichus dataset, ce sera plus simple (à mettre en place et plus performant) mais je n'y crois pas.
Nous traitons beaucoup de données et il nous semble important de pas nous tromper sur notre choix.
D'après toi quelle est la meilleure solution, et pourquoi?
Bonjour Bary,
Pour aller directement à la conclusion, les DataSet ne sont jamais la bonne solution. Maintenant, je développe :)
Premier point, il faut comparer ce qui est comparable, NHibernate est un mapper objet/relationnel, les DataSets types sont une petite surcouche par dessus le driver sql pour typer les colonnes de résultat : on est très loin de jouer dans la même cours. Ce qui m'amène à bien souligner l'intérêt d'un mapper : c'est une infrastructure aidant à persister des objets métiers, mais en aucun cas le centre d'une architecture. Cela me fait penser que votre équipe se pose les mauvaises questions : ce n'est pas nhibernate vs DataSet, mas plutôt domaine riche de sens vs Smart UI. Dans le cadre d'une architecture tournant autour d'un modèle objet riche de sens, sans duplication et cohérent, les DataSets sont un très mauvais choix : ils vont demander une énorme quantité de travail de mapping manuel pour pouvoir hydrater ces objets : NHibernate est juste fait pour ça, et fait le travail sans doute beaucoup mieux que ce que n'importe quelle équipe peut espérer produire en même temps que son logiciel. Il a un coût d'apprentissage bien entendu supérieur à un outil beaucoup plus bas niveau comme les DataSets, mais encore une fois, dans le cadre du développement d'un modèle l'objet, il est juste le meilleur outil et les gains de productivité à court/moyen terme compensent sans commune mesure le temps d'apprentissage initial. La déclaration du mapping était sans doute le point le plus embêtant de NHibernate, mais il se trouve que Fluent NHIbernate est passé par là et permet de décrire le mapping de bien belle manière.
Il faut bien se rendre compte par contre que NHibernate est conçu pour manipuler un faible nombre d'entité pendant une transaction donnée. Il n'est pas fait pour faire des batchs sur plusieurs milliers d'éléments ou garder en mémoire la quasi totalité de la base. Pour ces besoins qui s'apparentent plus à du décisionnel, d'autres outils sont plus adaptés (iBatis, voir Talend Open Studio). Même là, les DataSets typés sont surclassés par ces frameworks.
Si NHibernate peut faire peur, il existe d'autres patterns d'hydratation de données, comme Active Record. Cette approche mime beaucoup plus le schéma de la base que ne le fait NHibernate, qui lui permet de modéliser vraiment des relations beaucoup plus complexes. Pour une implémentation de référence, on peut aller voir par là. Pas mal de frameworks web à la mode comme Ruby On rails et Grails s'appuient sur ce pattern d'ailleurs. Il a l'avantage d'être simple à mettre en place et facile à comprendre. Pour faire une application purement CRUD, il est peut être même difficile de trouver plus productif. Le défaut bien sûr est que c'est une approche assez invasive souvent antinomique avec une approche plus orientée domaine.
De tous les outils cités, tous permettent d'être bien plus productifs que les DataSets. Niveau performance, je donnerai la réponse classique : il faut mesurer avant d'optimiser. NHibernate fournit en plus tout ce qu'il faut pour optimiser ses requêtes, et il existe même un profiler surpuissant (Hibernate profiler) pour nous aider dans cette tâche. La seule exception bien sûr et comme je disais plus haut qu'il faut choisir l'outil adapté au besoin : si on fait du décisionnel on ne sort pas NHIbernate.
Bref, même Microsoft a plus ou mois laissé tomber les DataSets, en développant sa réponse à NHibernate via Entity Framework. La véritable question derrière est de connaître ses besoins, et avoir choisi ses grands principes d'architecture : la méthode d'accès aux données ne devient alors qu'un corolaire.