DAO et ORM sont-ils compatibles ?
Les architectures modernes sont typiquement découpées en couches représentant des services concentriques de plus en plus haut niveau :
- Accès aux données (couche de persistance ou DAO, l’objet de ce billet),
- Traitements métiers,
- Présentation à l’utilisateur ou exposition à des systèmes tiers.
Les bonnes pratiques imposent que chacune de ces couches soit représentée par une interface exprimant le service qu’elle rend et masquant la façon dont elle est implémentée. Leur respect permet d’obtenir des architectures modulaires et faiblement couplées, facilitant le test et la maintenance des applications.
Pourtant, les ORMs remettent en cause ce schéma.
La couche de persistance
Le service demandé à une couche de persistance est parfaitement exprimé par l’acronyme CRUD (Create, Retrieve, Update, Delete), c’est-à-dire que l’on souhaite pouvoir sauvegarder, récupérer, mettre à jour et supprimer les données que l’application manipule.
L’interface typique d’une couche DAO ressemble donc à :
-
public interface ClientDao
-
{
-
public Client save(Client client);
-
public Client update(Client client);
-
public void delete(Client client);
-
public Client getClient(ClientId id);
-
public Collection<Client> getAllClients();
-
}
Constatez que cette interface est suffisamment générique pour ne laisser transparaître aucun détail technique, et peut ensuite être implémentée facilement de différentes façons : écriture dans un fichier plat ou XML, insertion en base de données, sauvegarde sur un système distant, sauvegarde en mémoire…
DAO et bases de données : le problème Hibernate
Les bases de données suivent depuis longtemps le modèle CRUD ; les instructions SQL INSERT
, SELECT
, UPDATE
, DELETE
en sont une transposition directe. Il est donc très simple d’implémenter une couche DAO via de simples appels JDBC basiques.
Tout était bien dans le meilleur des mondes, jusqu’à l’apparition des frameworks de mapping objet-relationnel (ORM), Hibernate en tête. Sous couvert de simplifier la vie du développeur (ce qui reste à démontrer), ils apportent une sémantique tout à fait différente et largement incompatible avec le système CRUD. En cause, la notion de cycle de vie des objets manipulés (transient / attaché / détaché), qui doit être géré manuellement par le développeur.
S’il apporte des fonctionnalités puissantes (mise à jour automatique, gestion de grappes d’objets…), ce système a une forte tendance à transpirer dans les couches supérieures – souvent même jusqu’à la couche de présentation, ce qui est une hérésie totale du point de vue architectural (cf. le pattern “open session in view“).
Exit donc le pattern CRUD. Exit aussi l’interface DAO générique indépendante de l’implémentation : les couches supérieures devant gérer finement le cycle de vie des objets, il est indispensable qu’elles puissent accéder aux APIs bas niveau du framework de persistance : persist
, merge
, delete
, saveOrUpdate
, lock
…
DAO par ORM : isolation plutôt que découplage
Si la couche DAO n’est plus découplée des couches supérieures, est-elle encore utile ? Si son interface n’est qu’un simple miroir des APIs de l’implémentation sélectionnée, à quoi sert-elle ? Ne serait-il pas plus simple de reconnaître cet état de fait et de supprimer la couche DAO lorsqu’on utilise un framework d’ORM ?
A cette question qui mérite réflexion, je préfère apporter une réponse nuancée.
Certes, le découplage est cassé ; mais la couche DAO conserve une utilisé pratique : elle permet d’isoler une partie du code technique spécifique (requêtes HQL / SQL, Criterions, Transformers…) dans des packages bien définis, et de garder l’apparence d’une architecture en couches standard.
Conclusion
Lors de la mise en place d’une pile technique, il faut savoir jeter un regard critique sur les différentes technologies disponibles sur le marché, et déterminer si leurs forces et faiblesses sont acceptables dans le cadre du projet. En particulier, il faut résister aux sirènes du “standard” (Spring/Hibernate/Struts par exemple) et ne pas hésiter à faire ses propres choix argumentés.
Cet article avait notamment pour but de vous faire prendre conscience que les ORMs ne sont pas des outils magiques, et qu’ils imposent de fortes contraintes sur l’architecture (en plus d’une certaine complexité), pouvant mener à des difficultés de testabilité ou de maintenance.
Bonsoir, il est 00h30 :^),
Je pense que ce que vous pointez dans cet article est tout à fait pertinent dans des projets petits ou moyens, un grand maximum de 20 classes d’après mon expérience, ce qui est tout à fait relatif je vous le concède. Par contre sur des projets conséquents ( +20 classes et jusqu’à des centaines) l’idée de se taper la couche DAO à la main est plus que suicidaire…à fortiori pour plusieurs cibles (mysql, oracle, xml….etc).
Si les ORM ‘transpirent’ sur les couches supérieures elles permettent en contre-partie de s’affranchir de la Base de données, ce qui loin d’être négligeable. Un client se moque de savoir que la couche vue est totalement découplée des DAO, par contre le fait d’apprendre que votre application peut s’interfacer avec sa base existante est un atout parfois majeur. Comme je l’ai dis plus haut, développer un DAO par Base de données est tout simplement infaisable ne serait-ce que sur un plan purement économique.
Pour ma part je conseil toujours de travailler avec des ORM en particulier Hibernate même sur de petits projets histoire de s’entrainer en vue des projets importants, bien entendu sans empiéter sur les délais de livraison pour les clients peu fortunés 🙂
Je ne conteste pas l’utilité des ORM – il est vrai qu’une fois bien maîtrisés, ils simplifient le développement et améliorent la productivité.
Je tentais plutôt d’attirer l’attention sur le fait que l’utilisation de leurs fonctionnalités avancées entraînait fréquemment une rupture de l’isolation entre les couches DAO et Service – par exemple, le fait que les objets soient toujours surveillés par la Session Hibernate une fois qu’ils ont quitté la couche DAO.
Une discussion similaire a déjà eu lieu dans les commentaires de cet article : http://thecodersbreakfast.net/index…
Je rebondis tardivement sur le post. Quand on pense ORM, on a naturellement tendance à penser Hibernate. Et pourtant…. il existe une solution bien plus sage qu’Hibernate. Il s’agit d’iBatis. Avec iBatis, vous conservez la main sur vos requêtes, le n+1 select et globalement tous les accès bases. Et de ce fait, vous continuez à écrire vos DAO. C’est donc dit : iBatis vous assure de la simplicité dans vos développements!