InfOsaurus

Aller au contenu | Aller au menu | Aller à la recherche

mercredi 2 juin 2010

Persistez votre domaine avec Raven DB

 

RavensIl y a peu, je vous parlais d'une possible synergie entre les bases de données NoSQL de type document et les Agrégats de Domain Driven Design.

Entretemps est sortie la base NoSQL pour la plateforme .NET d'Ayende Rahien, Raven DB, et elle confirme ces suppositions. Voici un extrait de la documentation de la base au corbeau :

« When thinking about using Raven to persist entities, we need to consider the two previous points. The suggested approach is to follow the Aggregate pattern from the Domain Driven Design book. An Aggregate Root contains several entities and value types and controls all access to the objects contained in its boundaries. External references may only refer to the Aggregate Root, never to one of its child objects.
When you apply this sort of thinking to a document database, there is a natural and easy to follow correlation between an Aggregate Root (in DDD terms) and a document in Raven. An Aggregate Root, and all the objects that it holds, is a document in Raven. »

Avec Raven, les données sont stockées dans des documents JSON qui ressemblent à ça :


Document

Contrairement à une base relationnelle dans laquelle les données résident dans des tables fragmentées qu'on peut relier par l'intégrité référentielle, il est possible de stocker dans ce type de document tout un graphe d'objets complexes et typés. Les préconisations pour la conception de documents Raven sont donc à l'inverse de celles qui président au design d'une base de données relationnelle : il s'agit de regrouper au sein d'un seul document les objets formant un tout logique. Par exemple, un Livre regroupe à la fois un titre mais aussi les objets que sont son Auteur et sa Catégorie. Il se trouve que la notion d'Agrégat et de Racine d'Agrégat de DDD recoupe en tous points ce concept.

Un des bénéfices qu'on constate immédiatement avec Raven DB est l'élimination pure et simple de la couche de mapping objet-relationnel, parfois source de bien des complications. En effet avec Raven, plus de défaut d'impédance entre les objets de l'application et leur équivalent persisté relationnel. La couche persistance s'en trouve bien allégée.


Exemple en C#

Comme un bon exemple vaut tous les discours, voici comment on peut implémenter un Entrepôt avec Raven et son client C# natif :


   1:      public class Livre
   2:      {
   3:          public string Id { get; set; }
   4:          public Auteur Auteur { get; set; }
   5:          public string Titre { get; set; }
   6:          public Categorie Categorie { get; set; }
   7:      }


   1:      public class EntrepotLivres
   2:      {
   3:          public EntrepotLivres(IDocumentSession documentSession)
   4:          {
   5:              session = documentSession;
   6:          }
   7:   
   8:          public void AjouterLivre(Livre livre)
   9:          {
  10:              session.Store(livre);
  11:              session.SaveChanges();
  12:          }
  13:   
  14:          public void SupprimerLivre(Livre livre)
  15:          {
  16:              session.Delete(livre);
  17:              session.SaveChanges();
  18:          }
  19:   
  20:          public Livre GetLivreParId(string id)
  21:          {
  22:              return session.Load<Livre>(id);
  23:          }
  24:   
  25:          private IDocumentSession session;
  26:      }


   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:          var documentStore = new DocumentStore { Url = "http://localhost:8080" };
   6:          documentStore.Initialize();
   7:   
   8:          using (var session = documentStore.OpenSession())
   9:          {
  10:              EntrepotLivres entrepotLivres = new EntrepotLivres(session);
  11:              ...
  12:          }
  13:      }
  14:  }

Pas de fichier de mapping à gérer à côté de ça, l'entité Livre et tout ce qu'elle contient se persistera de façon immédiate... Simple, non ?

On remarque qu'on manipule une Session Raven. Il s'agit d'une implémentation du pattern Unit of Work assez similaire à ce qu'on trouve dans des frameworks d'ORM comme NHibernate. En fait, bon nombre de concepts et dénominations dans l'API client C# de Raven sont directement issus de NHibernate.


Index et requêtes

Pour l'instant, notre Entrepôt ne fait qu'ajouter des entités, les supprimer et les hydrater à partir de leur ID, ce qui se fait nativement avec Raven. Pour exécuter des requêtes plus complexes sur nos documents, il faut passer par des index. Les index sont des vues stockées sur disque semblables aux vues matérialisées des SGBDR. Ils sont taillés pour un type de recherche particulier.

Voici comment on définit en C# un index qui porte sur les catégories des livres :


   1:          documentStore.DatabaseCommands.PutIndex("LivresParCategorie", 
   2:              new IndexDefinition<Livre>
   3:              {
   4:                  Map = livres => from livre in livres
   5:                                  select new { livre.Categorie }
   6:              });

Cet index ne doit être créé qu'une fois, il peut aussi être défini via l'interface d'administration de Raven en utilisant la même syntaxe.

On peut maintenant ajouter une méthode à notre entrepôt qui retourne tous les livres d'une catégorie. Elle effectue une requête LuceneQuery sur l'index précédemment défini :


   1:          public IList<Livre> GetLivresParCategorie(Categorie categorie)
   2:          {
   3:              return session.LuceneQuery<Livre>("LivresParCategorie")
   4:                  .Where(l => l.Categorie == categorie)
   5:                  .ToList();
   6:          }

Il existe une autre technique d'indexation plus sophistiquée, map/reduce, dont je parlerai peut-être dans un autre billet.


Raven DB

Impressions


Ce que j'aime dans Raven DB :

  • Simplicité de persistance des objets et affranchissement complet de la couche d'ORM.
  • Les données sont accessibles sous forme RESTful (chaque document dispose d'une URL) et lisible par un humain.
  • Sans doute bien plus facilement scalable qu'un SGBDR du fait de la nature atomique et autonome d'un document.
  • API .NET et requêtage sur les indexes en Linq.
  • Raven se marie bien avec DDD et une partie de l'effort de design de la base est déjà fait si on a découpé ses Agrégats.

Ce qui me plait moins :

  • Pour exploiter pleinement Raven en termes de performances, il faut idéalement ramener un seul document de la base et que celui-ci contienne tout ce dont on a besoin. Cela peut mener à une tendance à calquer les documents sur les IHM.
  • Raven DB a peut-être un petit impact sur la persistance ignorance de notre domaine. Il semble qu'une entité qui fait référence à la racine d'un autre agrégat (donc située dans un autre document, en termes Raven) ne peut pas avoir une référence directe à cet objet mais est obligé de contenir son ID à la place. Dans notre exemple, l'Auteur d'un Livre est un objet entièrement contenu dans l'agrégat Livre parce qu'on ne stocke que son nom et prénom. Mais si l'Auteur devait faire l'objet d'un agrégat séparé à lui (par exemple si l'auteur est un Utilisateur du système), le Livre devrait alors contenir la clé de l'Auteur et plus l'Auteur lui même. Sachant que les IDs natifs de Raven sont très typiques ("auteurs/465" par exemple), on se retrouve avec une trace de Raven dans le graphe d'objets du domaine, et aussi la nécessité de passer par un entrepôt pour réhydrater l'objet dont on n'a que la clé.

Les doutes à lever à l'avenir :

  • Les performances. Ayende a publié des mesures de perfs prometteuses mais il va falloir qu'elles soient confirmées sur des projets à plus grande échelle. En particulier, il serait intéressant de voir comment le système de concurrence optimiste de Raven se comporte dans un contexte transactionnel intensif.
  • L'adoption. Je pense que les bases NoSQL ne survivront pas sans un écosystème solide à la fois en termes de communauté et d'outillage disponible. Si on ne peut pas faire avec les bases non relationnelles tout ce qu'on fait avec les SGBDR (monitoring, tuning, reporting, analyse de données...), elles resteront une bonne idée sur le papier mais un choix pauvre sur le terrain.

vendredi 5 mars 2010

Règlement de comptes à Données Corral

Il va y avoir du sport

Derrière le jeu de mots vaseux de ce titre se cache une réalité beaucoup moins fun. Celle de la guerre que se livrent, depuis quelques temps déjà, les partisans d'une approche des bases de données novatrice et iconoclaste et les défenseurs de la tradition des SGBD relationnels.

Si vous suivez l'actualité du monde du développement, vous avez déjà compris de quoi je veux parler. Il ne vous a pas échappé que ces derniers temps, l'hégémonie des systèmes relationnels sur le marché des bases de données a été quelque peu chahutée : en 2009 sont apparues un certain nombre de bases regroupées sous le nom parapluie de "NoSQL". Ces systèmes (CouchDB, Big Table de Google, Project Voldemort...) sont des key-value stores, des document stores ou des bases fondées sur les graphes qui ont en commun de partir d'un constat de lourdeur du relationnel, du langage SQL et de ses jointures. Elles se veulent plus simples d'approche, plus scalable pour de gros volumes de données, plus adaptées au web et moins sclérosées par les schémas de données.


La rébellion gagne des voix

Il est intéressant de voir qu'en ce début d'année, il y a un regain de tension dans le débat qui oppose les deux visions et que celui-ci s'est invité chez les développeurs .NET. Plus particulièrement, deux acteurs importants de la communauté et qui n'ont pas a priori d'intérêt particulier à soutenir les bases NoSQL, ont pris position pour les défendre.

Le premier est Ayende Rahien, contributeur du projet NHibernate et créateur entre autres du framework d'isolation RhinoMocks. Dans un article nommé Slaying Relational Dragons, il remet en cause l'hégémonie des SGBDR et cite une session de support client à l'issue de laquelle il a recommandé de ne pas utiliser de base relationnelle. S'en suit un exemple de type d'application pour laquelle selon lui, il est tout à fait justifié d'opter pour un document store du style CouchDB. Plus étonnant, Ayende a également démarré un projet de base document, Rhino DivanDB, alors même qu'une grande partie de son travail des dernières années a été dévoué indirectement aux bases relationnelles par le biais d'NHibernate.

Une autre chose a piqué ma curiosité dans le billet d'Ayende : pour décrire les grappes de données pêchées dans une base NoSQL, il utilise le terme d'Agrégats. Oui, c'est à peu près la même notion d'Agrégat que dans Domain Driven Design. Visuellement, cela peut donner ceci (2 racines d'agrégat Book en l'occurrence) :

Agrégats

Si on cherche un peu, on s'aperçoit aussi que des frameworks DDD comme Jdon prévoient d'entrée l'utilisation d'une base NoSQL pour la persistance. Y aurait-il une synergie entre DDD et NoSQL dans la forme sous laquelle les entités sont appréhendées ? Intéressant, à creuser en tout cas.

Notre deuxième homme est Greg Young, très impliqué dans DDD justement, et qui a popularisé l'approche Command-Query Separation. Greg a comparé dans un billet récent l'utilisation d'un ORM au fait d'embrasser sa soeur (expression américaine désignant une action dénuée d'intérêt)... Pour lui, nous devrions plus souvent nous arrêter et nous demander si le choix d'un ORM couplé à un modèle de données relationnel pour notre projet, est bien justifié. Plutôt que de faire de l'ORM + SGBDR le choix par défaut, pourquoi ne pas envisager une base objet ou une base document à la place ? Dans certains cas, c'est beaucoup plus adapté au contexte et ça évite les problèmes de décalage d'impédance entre l'application objet et le modèle de données.


L'Empire contre-attaque

Bien sûr, les défenseurs des SGBD relationnels ont tôt fait de réagir. Un des plus virulents dans la contre-offensive a certainement été Frans Bouma, curieusement acteur de la scène ORM lui aussi (avec LLBLGen). Dans des commentaires et sur son blog, il avance trois arguments principaux pour contrer les enthousiastes du NoSQL :

  • Les cas évoqués par Ayende sont des anti-pattern, on essaie de créer un modèle de données qui est directement calqué sur la mise en forme d'un écran de l'application, ce qui est une mauvaise pratique.
  • Un modèle de données a pour vocation de représenter la réalité, et pas juste de refléter des entités utilisées dans une application (on retrouve un peu ici l'approche bottom-up vs l'approche top-down). Pourquoi ? Parce que dans un contexte d'entreprise, de multiples logiciels accèdent aux mêmes données et c'est de plus en plus vrai au fil du temps. Il faut donc un modèle qui représente parfaitement le métier et en est le garant quelle que soit l'application qui y puisera.
  • Les bases relationnelles existent depuis plusieurs dizaines d'années, elles sont fondées sur une théorie solide et ont fait l'objet d'innombrables recherches, ce qui en fait les outils incontournables et aboutis qu'on connait aujourd'hui. Toute concurrence est donc pour l'instant anecdotique.


Verdict

Conceptuellement, on voit bien le clivage entre les bases de type document store qui recèlent le strict nécessaire permettant à une application de fonctionner (le tout taillé sur mesure pour elle seule : une sorte de YAGNI de la donnée), et un modèle relationnel qui essaie de capturer la réalité de manière parfaite pour disposer d'une clé qui déverrouillera tous les situations à venir.

Sans prendre parti pour un camp ou l'autre, j'ai peur qu'à l'heure actuelle les bases NoSQL manquent de maturité face aux mastodontes relationnels. Mais elles restent une alternative à explorer et à mon avis, elles n'ont pas dit leur dernier mot. La guerre est loin d'être finie.