Pourquoi utiliser un ORM et sur quels critères le choisir ?

Publié le 19 mai 2008 par Olivier Duval

Introduction

Dans un environnement 100 % objet, il convient de ne manipuler que des objets, que cela soit des classes métiers, ou toutes autres classes de type traitements (contrôleurs), pour plusieurs raisons :

  • cela facilite le développement en apportant une sémantique aux classes (par exemple on manipulera un objet Client qui représente un...client, et non un tableau contenant des champs issus de la base),
  • apporte des fonctionnalités en termes de collections d'objets (itérations, manipulation de type tris, ...),
  • l'accès aux propriétés (ie: champ d'une table) est fortement typé

Un ORM permet tout cela avec en plus une abstraction à l'accès aux données, ainsi que des fonctions que le développeur n'a plus ou pas à gérer et qui lui facilitent la vie : connexion à la base, instanciations des objets (ie : mapping avec prise en compte de l'héritage ou des collections pour les relations 1-n ou n-m), lazy loading, CRUD, moindre utilisation du langage SQL...on s'affranchit de beaucoup de traitements et c'est appréciable !

Une définition de Wikipédia :

L'object-relational mapping (ORM), que l'on pourrait traduire par « correspondance entre monde objet et monde relationnel » est une technique de programmation informatique qui crée l'illusion d'une base de données orientée objet à partir d'une base de données relationnelle en définissant des correspondances entre cette base de données et les objets du langage utilisé.

Quel choix ?

Le choix est souvent difficile parmi la pléthore de framework ORM sur le marché.

Après plusieurs expériences et d'implémentations de plusieurs architectures sur des projets Web, un peu de retour d'expérience : ci-après quelques critères que l'on pourrait énumérer selon des besoins qui seront spécifiques aux projets développés et à l'architecture mise en place :

  • non polluant : personnellement, afin d'éviter d'utiliser des objets DTO qui relient les différentes couches, j'aime que les classes utilisées par l'ORM soient directement sérialisables par celui-ci et utilisables directement par les différentes couches (présentation notamment) : cela évite d'avoir une couche service par laquelle on devra mapper les objets de l'ORM vers des objets entités (objets métiers), ceci afin de conserver une astraction entre les couches supérieure et la DAO. Beaucoup d'ORM utilisent une combinaison d' attributs et d'héritage de classe pour gérer leur mapping (sur les champs, sur les tables), en cas de changement de framework, on risque de se faire coincer si on n'a pas suffisamment abstrait les classes entités.
  • SGBDR, CRUD, lazy-loading, collections, cache, héritage, transactionnel, langage d'interrogation objet
  • communauté existante : pour la correction de bugs ou tout simplement pouvoir se débloquer d'un problème d'implémentation, une communauté reste primordiale, comme tout logiciel Opensource
  • simple (si ce terme est réaliste) à mettre en oeuvre et qui s'intégre de façon transparente à la plateforme

SGBDR & CRUD

L'ORM devra fournir des mécanismes simples pour le CRUD (Create, Read, Update, Delete) des objets manipulés ainsi qu'une compatibilité avec plusieurs bases de données (MySQL, MSSQL, Oracle, PostgreSQL, ...) et aussi ne pas se soucier des identifiants (l'ID système dans nos tables).

Lors d'un développement, certaines opérations de mise à jour des données nécessitent des contraintes d'intégrités afin de ne pas avoir d'incohérences dans les données, les contraintes sont faites pour ça. L'ORM doit alors mettre en place les transactions afin d'actionner le rollback le cas échéant en cas de soucis d'insertion, de suppression ou de mise à jour d'une entité.

Lazy-loading & cache

Egalement, la gestion des collections d'objets (typiquement les relations 1-n ou n-m) devra dans la mesure du possible utiliser le lazy-loading, cela permettra d'optimiser les accès à la base, notamment pour les relations, ou les gros champs (type text / blob) : ne charger les objets uniquement lors de leur accès, économie de ressource mémoire et de charge serveur côté SGBDR, et surtout ce qui induit une réduction de temps lors de l'initialisation d'une application. Avec le même objectif d'amélioration des performances, au lieu de gérer un cache de nos objets côté présentation, l'ORM peut offrir des mécanismes évolués pour le gérer.

Mapping entités, héritage

Souvent, 1 table = 1 classe, mais on peut avoir des objets non contenus dans la même table (une adresse par exemple), mais d'une représentation strictement objet, l'information sera insérée dans un objet plus globale (un individu ayant une adresse), cela facilite les manipulations. Egalement, l'héritage peut s'avèrer très utile, l'ORM devra alors prendre en compte (ie : instancier, souvent grâce à un discriminant) les différents types qui dérivent d'une entité mère, ceux-là étant stockées dans des tables différentes que celle représentant les informations communes.

Divers

Si on veut totalement s'abstraire de la base, il convient que l'ORM implémente son propre langage d'interrogation : soit par une constructions de critères ou filtres, soit grâce à un véritable langage d'interrogation objet. Dans ce cas, on s'affranchit totalement du SQL, ce dernier peut avoir des instructions spécifiques selon le serveur de bases de données, l'ORM traduira selon la cible les bonnes instructions.

And the winner is...

Après avoir testé plusieurs types d'architectures et d'ORMs, celui qui répond le plus à nos besoins précédemment cités est : NHibernate. Je ne suis pas très attiré par les ORMs qui génére toute une couche DAO, dès qu'il faut coder du spécifique, cela peut devenir très contraignant, sans compter les milliers de lignes de code plus ou moins compréhensibles. NHibernate a une forte communauté, très active, et on trouve bon nombre de blogs spécialisés pour décortiquer la bête.

Quelques ressources

  • Optimisation et de l'intêret du lazy-loading avec NHibernate
  • Un livre blanc sur les ORM
  • NHibernate en environnement ASP.NET, les bonnes pratiques
  • La problèmatique du chargement N+1
  • FAQ NHibernate