Introduction à ObjectSpaces

L'architecture ObjectSpaces
 

L'architecture Objectspaces a pour objectif principal de vous permettre d'exposer des données sous la forme d'objets et de liste d'objets indépendamment de leur représentation physique (tables, colonnes, lignes ou éléments XML). Ces objets sont appelés "objets persistants" et sont définis en utilisant les conventions du Framework .NET. Avec ObjectSpaces, plus aucun besoin de taper du SQL, l'API s'en charge. L'objet le plus important dans le Framework est l'usine de classe ObjectSpace  (pattern Factory) dont le rôle est le chargement, la création et la suppression d'instances d'objets persistants dans la base de données. Le terme base de données doit être pris au sens le plus large, il comprend l'ensemble des bases relationnelles mais aussi les fichiers XML à plats.

 

ObjectSpace se sert des classes ADO.NET pour implémenter son mécanisme de persistance. Un cache local est utilisé autour de l'objet DataSet permettant de charger des données à partir d'une source quelconque (XML, SGBD, ..) afin de répercuter en base toute modification du DataSet . Il est important de comprendre que le Framework ObjectSpace fonctionne dans un mode totalement déconnecté lui permettant de se re-synchroniser avec la source de données en cas de modifications. Plusieurs alternatives s'offrent à vous concernant la gestion de vos objets :

  •  Implicitement laissez ObjetSpace gérer le chargement et la persistance de vos données 

  •  Gérer vous même explicitement les interactions avec la base

La gestion implicite se fait par l'intermédiaire des objets XMLObjectSpace et SQLObjectSpace, la gestion explicite se fait par l'intermédiaire de la classe DataSpace en fournissant un objet DataSet.

Le diagramme suivant illustre les classes principales du Framework. IObjectSpace est utilisé lorsqu'il est nécessaire d'être indépendant du type de données manipulé (XML ou SQL).

 

 

Le mapping entre modèle objet et modèle physique se fait via un fichier XML. Le but affiché par les équipes d'ObjectSpace consiste à permettre toute modification du fichier XML de mapping sans avoir à recompiler le code client.

 

Voyons de plus près les différentes étapes nécessaires pour créer un objet persistant.

 

Implémenter l'objet

 

Un objet persistant est défini de la manière suivante : tous ses accesseurs sont abstraits. Le Framework se charge de générer le code nécessaire à leur implémentation en fonction du type de données manipulé. De plus, tout objet ObjectSpace est caractérisé par un identificateur unique représenté par l'attribut de Runtime UniqueId. Cet attribut est en lecture/écriture lorsque le développeur choisi de l'initialiser ou en lecture seule lorsqu'il laisse le Framework s'en charger. Enfin, certaines méthodes sont liées à l'API : OnCreate(..) représente le constructeur de la classe (rappelez vous, c'est le Framework qui a la responsabilité de gérer la création et la destruction de votre objet).

 

[C#]

public abstract class Consultant

{

      [UniqueId] protected abstract string Id { get; set; } // This 

                                      property contains a unique value.

 

      public abstract string Name { get; set; }

      public abstract string CompanyName {get;set;}

      public abstract string Phone { get; set; }

      public abstract string Fax { get; set; }

 

      public void OnCreate(string newId) // OnCreate is the ObjectSpaces

                                                          "constructor".

      {

            Id = newId;

      }

}

 

 

Il existe d'autres méthodes de ce type décrites dans la matrice suivante :

 

Méthode

Description

OnCreate()

Appelée à la création de l'objet

OnMaterialize()

Appelée la première fois après l'appel de OnCreate

OnDelete()

Appelée à la destruction de l'objet

 

L'étape suivante consiste à créer les relations entre les classes.

 

Définir les relations

Rajoutons dans la classe précédente Consultant une relation une relation bidirectionnelle 1-1 avec la classe Course : un consultant donne un cours et un cours ne peut être donné que par ce consultant (il n'a pas intérêt à tomber malade celui là). La classe modifiée nous donne :

 

[C#]

public abstract class Consultant

{

      [UniqueId] protected abstract string Id { get; set; } // This 

                                      property contains a unique value.

 

      public abstract string Name { get; set; }

      public abstract string CompanyName {get;set;}

      public abstract string Phone { get; set; }

      public abstract string Fax { get; set; }

 

      public abstract Course MyCourse { get; set; }

 

      public void OnCreate(string newId) {...}

}

 

 

La classe Course associée. Remarquez la relation bidirectionnelle :

 

public abstract class Course

{

      [UniqueId] public abstract int CourseId { get; set; }

      public abstract Consultant MyConsultant { get; set; }

 

      public void OnCreate(int Id)

      {

            CourseId = Id;

      }

}

 

Le fichier XML de mapping sera le suivant :

 

 

 

 

Il existe à l'heure actuelle deux types de relations différentes prises en charge par le Framework :

- 1,1 : OneToOne,

- 1,n : OneToMany,

 

Attribut XML

Obligatoire

Description

type

oui

OneToOne ou OneToMany

parentType

oui

Se réfère au parent de la relation

childType

oui

Se réfère au fils de la relation

 

avec un concept de lien d'association LinkedBy ou de composition ComposedBy. Ainsi, lors d'une relation de composition, toute suppression d'un objet parent entraînera la suppression des objets fils.

 

Utilisation de l'objet et synchronisation du cache

Pour utiliser un objet ObjectSpace, commencez par récupérer une référence sur la Factory avec la méthode CreateObjectSpace. Ensuite, la méthode CreateObject() renvoie des références d'objets persistants en assurant leur création en base. La mise à jour et la synchronisation se fait manuelle par l'appel aux méthodes os.Update() ou os.ReSync() pour assurer la synchronisation entière du cache.

 

// Fichier de configuration de la base de données et du mapping

IObjectSpace os = ObjectSpaceFactory.CreateObjectSpace("source.xml");

 

Consultant theConsultant = (Consultant)os.CreateObject(typeof(Consultant), 1);

theConsultant.CompanyName = "Valtech" ;

theConsultant.Phone = "123456" ;

 

// Or os.UpdateAll() for updating all objects

os.Update(theConsultant) ;

 

// Synchronise all the cache with the underlying storage

os.ReSyncAll() ;  

 

Course theCourse = (Course)os.CreateObject(typeof(Course), 1);

 

// Establish the one-to-one relationship.

theConsultant.MyCourse = theCourse;

 

Console.WriteLine("theConsultant.MyCourse.Id: {0}", theConsultant.MyCourse.Id);

 

 

5) Récupérer une liste d'objets à partir de critères : OPath

OPath est un langage de requête créé spécialement pour ObjectSpace. Il permet de récupérer un graphe d'objets persistants selon des critères spécifiés en OPath. Le code suivant nous illustre quelques exemples de requêtes :

 

 

// Renvoie la liste des consultants chez Valtech

os.GetObjects(GetType(Consultant), "CompanyName = 'Valtech'") ;

 

// Renvoie un consultant

os.GetObjects(GetType(Consultant), "") ;

 

// etc ...

os.GetObjects(GetType(Customer), "Orders.OrderDate >= '5/1/1998' and Orders.OrderDate <= '5/31/1998'");

os.GetObjects(GetType(Customer), "Orders[Freight > 10].Details.Quantity > 50");

 

// Utilisation dans le cadre d'une relation 1,n : Consultant donne n Courses

// Recherche et Parcours tous les consultants dont le nom commence par F travaillant chez Valtech

foreach( Consultant theConsultant in
                       os.GetObjects(typeof(Consultant), "CompanyName ='Valtech' and Name < 'F*'") )

{

      Console.WriteLine("\nConsultant Name: "+ theConsultant.Name + "\n--------------------");

 

      foreach(Course theCourse in theConsultant.Courses)

      {

            Console.WriteLine("Consultant: Id: " + theConsultant.Id +

            ", Company Name : " + theConsultant.CompanyName +

            ", Phone : " + theConsultant.Phone +

            ", Fax : " + theConsultant.Fax +

            ", Email: " + theConsultant.Emaim);

      }

}

 

 

Pourquoi un n-ième langage après OQL (Object Query Langage), SQL (Simple Query Langage), XPath, ... ? C'est une question que nous sommes en droit de nous poser. Mais il faut bien l'avouer, dans ce domaine, Microsoft n'est pas le seul éditeur à s'être confronté au problème. Sans aller très loin, le monde Java contient une multitude d'exemples où la standardisation du langage de requête objet est un vain mot. Ce sujet a toujours été source de moult discussions interminables, que ce soit dans la spécification EJB (EJBQL) ou dans des API plus spécifiques telles que JDO (http://www.castor.org). Certains utilisent EJBQL (WebLogic), d'autres préfèrent OQL et finalement on en revient toujours à SQL ...

 

 

Mapping Objet/Relationnel

 

Nous avons gardé le meilleur pour la fin : le mapping Objet/Relationnel. Comment configurer notre base existante (SQLServer, Oracle, ...) pour continuer à utiliser les objets précédents ?

Rappelez-vous ce qui a été dit en début d'article, le but d'ObjectSpace est de masquer au client le modèle de stockage physique. Le fait de passer d'une gestion sous forme de fichiers XML à une gestion sous la forme d'une base doit-il changer la vision que le client a de cet objet ? bien sûr que non ! C'est pourquoi, au niveau des APIs, vous ne verrez rien qui fasse intervenir une éventuelle requête SQL, ni même une table. Tout se passe à l'intérieur des fichiers de configurations : Map.xml, Connect.xml et DbSource.xml. Vous spécifiez les sources de données vers vos bases, les tables et colonnes à mapper sur les objets et le tour est joué !

L'exemple suivant illustre le principe avec le stockage dans une base SQL Serveur de nos deux objets Consultant et Course avec deux tables :

 


Pour accomplir cette tâche il suffit de créer le fichier de mapping XML suivant :

 


Ce fichier contient l'association table(colonne)=>champs de l'objet ainsi que les relations entre ces objets. A l'avenir, nous pouvons imaginer que Microsoft fournira des IHMs permettant de sélectionner des graphes d'objets et de réaliser le mapping graphiquement. Biztalk Mapper ne propose-t-il pas déjà quelque chose de semblable ?

Ensuite, il ne faut pas oublier d'associer le fichier XML (config.xml) contenant les DataSource  ADO.NET (lien vers les bases).

 


Enfin, le code source du client une fois modifié doit juste se contenter de charger les fichiers de configuration de la manière suivante :

 
IObjectSpace os = ObjectSpaceFactory.CreateObjectSpace("config.xml);

 

Conclusion

Vous aurez compris que ObjectSpace est un énorme bond en avant vers l'objet de Microsoft. La couche de persistance est la seule brique manquante à .NET pour devenir une véritable architecture 3 tiers. Les lecteurs ayant des connaissances J2EE et EJB ont dû sourire discrètement à la lecture de cet article. Et OUI, ces concepts existent dans les EJB à travers les composants Entity, OUI ces notions existent aussi dans la multitude d'outils de persistance Java allant de JDO, TopLink, CocoBase à Oracle9i (et il y en a d'autres !)... Microsoft ré-écrit un n-ième Framework de persistance et à un retard important à combler dans ce domaine, il faut bien l'avouer. D'un autre coté, est-ce vraiment le but d'ObjectSpaces de vouloir contrer Sun ? à entendre les discussions émanant du forum microsoft.public.dotnet.objectspaces, cela reste moins sûr...


Bref, au delà de discussions d'ordre stratégiques, il se pose les questions fondamentales suivantes : qu'est-ce que JDO ou les EJB Entity font-ils de plus qu'ObjectSpace et inversement ?... Réponse au prochain numéro ;-)

 

Auteur : Sami Jaber