Introduction
Ce document est une présentation des modèles d'objet et des modèles de données relationnelles. Il fournit aussi une
description résumée d'une infrastructure de persistance.
Bases de
données relationnelles et orientation objet
Les bases de données relationnelles et l'orientation objet ne sont pas entièrement compatibles. Ce sont deux vues
différentes : dans un SGBD relationnel vous ne voyez que les données ; dans un système orienté objet vous ne voyez que
le comportement. Il n'y en pas un qui soit meilleur que l'autre : le modèle orienté objet fonctionne bien pour les
systèmes avec un comportement complexe et propre à l'état, pour lesquels les données sont secondaires, ou pour les
systèmes dans lesquels on accède aux données an naviguant dans une hiérarchie naturelle (par exemple, les
nomenclatures). Quant au modèle SGBD relationnel, il convient bien aux systèmes et applications de reporting dans
lesquels les relations sont dynamiques ou ad-hoc.
Le fait est que beaucoup d'informations sont stockées dans les bases de données relationnelles, et si les applications
orientées objet veulent accéder à ces données, il faut qu'elles soient capables de lire et d'écrire dans un SGBD
relationnel. De plus, les systèmes orientés objet ont souvent besoin de partager les données avec des systèmes
non-orientés objet. Il est donc normal d'utiliser un SGBD relationnel en tant que mécanisme de partage.
Même si la conception orientée objet et la conception relationnelle ont certaines caractéristiques en commun (au niveau
conceptuel, un attribut d'objet est similaire à une colonne d'entités), certaines différences fondamentales font de
l'intégration transparente un défi. La différence fondamentale est que les modèles de données exposent les données (via
les valeurs de colonnes) alors que les modèles d'objet les cachent (en les encapsulant derrière des interfaces
publiques).
Le modèle relationnel se compose d'entités et de relations. Une entité peut être une table physique ou une projection
logique de plusieurs tables, également appelée vue. La figure ci-dessous est une illustration des tables LINEITEM,
ORDER et PRODUCT et de leurs diverses relations. Un modèle relationnel est composé des éléments suivants :
Un modèle relationnel
Une entité contient des colonnes. Chaque colonne est définie par un nom et un type. Dans la figure ci-dessus, l'entité
LINEITEM a les colonnes suivantes : LineItem_Id (la clé primaire), Description, Prix, Quantité, Product_Id et Order_Id
(les deux dernières sont des clés externes qui lient l'entité LINEITEM aux entités ORDER et PRODUCT).
Une entité contient des enregistrements ou des lignes. Chaque ligne représente un ensemble unique d'informations qui
représentent généralement les données persistantes d'un objet.
Chaque entité a une ou plusieurs clés primaires. LineItem_Id est la clé primaire de LINEITEM.
La prise en charge des relations est spécifique à chaque fournisseur. Cet exemple illustre le modèle logique et la
relation entre les tables PRODUCT et LINEITEM. Dans le modèle physique, les relations sont généralement implémentées à
l'aide des références clé externe/clé primaire. Si une entité a des relations avec une autre, elle aura des colonnes
qui sont des clés externes. Ces colonnes contiennent des données qui peuvent relier des enregistrements spécifiques au
sein de l'entité à l'entité connexe.
Les relations ont une multiplicité (également appelée cardinalité). Les cardinalités les plus courantes sont des
relations de un à un (1:1), de un à plusieurs (1:p), de plusieurs à un (p:1) et de plusieurs à plusieurs (p:p). Dans
notre exemple, LINEITEM a une relation 1:1 avec PRODUCT et PRODUCT a une relation 0:p avec LINEITEM.
Un modèle d'objet contient entre autres des classes (voir [UML01] pour une
définition complète d'un modèle d'objet). Les classes définissent la structure et le comportement d'un ensemble
d'objets parfois appelés instances d'objets. La structure est représentée sous forme d'attributs
(valeurs de données) et d'associations (relations entre les classes). La figure suivante est une illustration d'un
diagramme de classes simple faisant uniquement apparaître les attributs (données) des classes.
Un modèle d'objet (diagramme de classes)
Une commande est numérotés (numéro de commande) et est associée à 1 ou plusieurs (1..*) lignes article. Chaque ligne
article est associée à une quantité (la quantité commandée).
Le modèle d'objet prend en charge le principe d'héritage. Une classe peut hériter des données et du comportement d'une
autre classe (par exemple, les produits SoftwareProduct et HardwareProduct héritent des attributs et des méthodes de la
classe Product).
La plupart des applications métier utilisent une technologie relationnelle en tant que magasin de données physiques. Le
défi des développeurs d'applications orientées objet est de séparer et d'encapsuler suffisamment les bases de données,
pour que les changements dans le modèle de données n'entraînent pas de "rupture" du modèle d'objet et inversement. Il
existe beaucoup de solutions qui laissent les applications accéder directement aux données relationnelles ; le
challenge est de réussir une intégration transparente entre le modèle d'objet et le modèle de données.
Les interfaces de programmation d'application (API) de bases de données sont livrées sous des formes standard (par
exemple l'API Open Data Base Connectivity, ou ODBC, de Microsoft) et sont propriétaires (liens natifs à des bases de
données spécifiques). Les API fournissent des services à travers le langage de manipulation de données (DML) qui
permettent aux applications d'accéder aux données relationnelles brutes. Dans les applications orientées objet, les
données doivent subir une conversion objet-relationnel avant de pouvoir être utilisées par l'application. Cela
nécessite d'utiliser beaucoup de code d'application pour convertir les résultats d'API de bases de données bruts en
objets d'application. L'objectif de l'infrastructure objet-relationnel est d'encapsuler de manière générique le magasin
de données physiques et de fournir les services de conversion d'objets appropriés.
Objectif de l'infrastructure de persistance
Les développeurs d'applications passent plus de 30 % de leur temps à implémenter l'accès aux bases de données
relationnelles dans les applications orientées objet. Si l'interface objet-relationnel n'est pas implémentée
correctement, cet investissement est perdu. L'implémentation d'une infrastructure objet-relationnel enregistre cet
investissement. L'infrastructure objet-relationnel peut être réutilisée dans des applications ultérieures, le coût de
son implémentation représentant alors moins de 10 % des coûts totaux d'implémentation. Le coût le plus important à
prendre en considération lors de l'implémentation d'un système est celui de la maintenance. Plus de 60 % des coûts
totaux d'un système sur tout son cycle de vie peuvent être attribués à la maintenance. Un système objet-relationnel mal
implémenté représente un véritable problème de maintenance sur le plan technique et financier.
-
Performance. Il faut porter une attention particulière à la décomposition d'objets en données et à
la composition d'objets à partir de données. Dans les systèmes où le rendement des données est élevé et critique,
c'est souvent le talon d'Achille d'une couche d'accès aux données conçue de façon inadéquate.
-
Minimiser les compromis relatifs à la conception. Pour les spécialistes objet qui construisent des
systèmes utilisant des bases de données relationnelles, il est habituel d'ajuster le modèle d'objet afin de
faciliter le stockage dans les systèmes relationnels, et de modifier le modèle relationnel pour un stockage plus
facile des objets. Si des ajustements mineurs sont souvent nécessaires, une couche d'accès aux données bien conçue
minimise la dégradation de la conception du modèle d'objet et du modèle relationnel.
-
Capacité d'extension. La couche d'accès est une infrastructure structurelle qui permet aux
développeurs d'applications d'étendre l'infrastructure si celle-ci a besoin d'une certaine fonctionnalité. Une
couche d'accès va généralement supporter, sans extension, 65 à 85 % des besoins de stockage de données d'une
application. Si la couche d'accès n'a pas été conçue en tant qu'infrastructure extensible, il sera très difficile
et coûteux de répondre aux 15 à 35 % de besoins restants en termes de stockage de données.
-
Documentation. La couche d'accès est à la fois un composant fonctionnel et une infrastructure
structurelle. L'API du composant fonctionnel doit être clairement définie, bien documentée et facile à comprendre.
Comme mentionné ci-dessus, la couche d'accès est conçue pour pouvoir être étendue. Une infrastructure extensible
doit avoir une documentation complète. Les classes que l'on prévoit de diviser en sous-classes doivent être
identifiées. Les caractéristiques de chaque protocole de classe pertinent doivent être spécifiées (par exemple,
public, privé, protégé, final, etc.). De plus, une partie importante de la conception de l'infrastructure de la
couche d'accès doit être exposée et documentée pour en faciliter l'extension.
-
Prise en charge des mappages objet-relationnel courants. Une couche d'accès doit fournir la prise
en charge de certains mappages objet-relationnel de base sans avoir recours à l'extension. Ces mappages font
l'objet d'une discussion plus loin dans le document.
-
Interfaces de persistance : dans une application orientée objet, le modèle métier d'une
application objet enregistre la connaissance sémantique du domaine du problème. Les développeurs doivent manipuler
les objets et interagir avec sans avoir à se préoccuper du stockage des données et des détails d'extraction. Un
sous-ensemble bien défini d'interfaces persistantes (sauvegarder, effacer, rechercher) doit être fourni aux
développeurs d'applications.
Les patterns courants proviennent d'applications objet-relationnel. Les professionnels de l'informatique qui se sont
souvent trouvés face au problème commencent à comprendre et à reconnaître certaines structures et certains
comportements que présentent les applications objet-relationnel réussies. Ces structures et comportements ont été
formalisés par les spécifications de haut niveau de services CORBA (qui s'appliquent également aux systèmes basés sur
COM/DCOM).
Les spécifications de services CORBA qui sont applicables et qu'il est utile de prendre en considération pour le
mappage objet-relationnel sont :
Ces catégories structurent la discussion suivante sur les services objet-relationnel courants. Le lecteur est encouragé
à se référencer aux spécifications CORBA appropriées pour plus de détails.
Le terme persistance est utilisé pour décrire comment les objets utilisent un support de stockage secondaire afin de
conserver leur état à travers des sessions discrètes. La persistance donne à l'utilisateur la capacité de sauvegarder
des objets dans une session et d'y accéder plus tard dans une autre session. Lorsqu'on accède finalement à ces objets,
leur état (attribut, par exemple) n'a pas changé depuis la session précédente. Cela n'est pas toujours le cas pour les
systèmes multi-utilisateur, car d'autres utilisateurs peuvent accéder aux mêmes objets et les modifier. La persistance
est étroitement liée à d'autres services abordés dans cette section. Les relations, l'accès concurrent et d'autres
spécifications sont intentionnellement prises en considération (et sont cohérentes avec la décomposition des services
CORBA).
Exemples de services spécifiques fournis par la persistance :
-
Gestion de la connexion à une source de données : les applications objet-relationnel doivent
initier la connexion à la source de données physique. Les systèmes de bases de données relationnelle requièrent
généralement une identification du serveur et de la base de données. Les spécificités de la gestion de la connexion
relèvent du fournisseur de base de données et l'infrastructure préfabriquée doit être conçue en conséquence de
manière flexible.
-
Extraction d'objet : lorsque les objets sont restaurés depuis la base de données, les données sont
extraites et converties en objets. Ce processus implique d'extraire des données à partir de structures spécifiques
de base de données (structures elles-mêmes extraites de la source de données), de convertir les données depuis les
types de bases de données vers les types d'objets et/ou classes appropriés, de créer l'objet approprié et de
configurer les attributs d'objet spécifiques.
-
Stockage d'objet : ce processus est le processus inverse de l'extraction d'objet. Les valeurs des
attributs appropriés sont extraites de l'objet, une structure spécifique de base de données est créée avec ces
valeurs d'attributs (ce peut être une chaîne SQL, une procédure stockée ou un appel de procédure éloignée spécial),
puis la structure est soumise à la base de données.
-
Suppression d'objet : pour les objets qui sont supprimés d'un système, les données associées
doivent être supprimées de la base de données relationnelle. La suppression d'objet nécessite que les informations
appropriées soient extraites de l'objet, qu'une requête de suppression soit construite (ce peut être une chaîne SQL
ou un appel de procédure éloignée spécial), puis que la requête soit soumise à la base de données. Il faut noter
que dans certains langages (Smalltalk et Java, par exemple), la suppression explicite n'est pas prise en charge ;
une stratégie appelée récupération de place la remplace. Les infrastructures de persistance qui
prennent en charge ces langages doivent fournir une méthode d'extraction alternative des données depuis la base de
données, une fois que les applications ne font plus référence aux données. Une manière courante d'y parvenir est
que la base de données maintienne un comptage de références du nombre de fois où d'autres objets
font référence à un objet. Lorsque ce comptage tombe à zéro, aucun autre objet n'y fait référence et il est
peut-être possible de le supprimer. Supprimer des objets avec un comptage à zéro peut être
acceptable, puisqu'un objet peut toujours faire l'objet d'une requête même s'il n'est plus référencé. Des
règles à l'échelle de la base de données, établissant quand la suppression d'objet est permise, sont toujours
nécessaires.
Le stockage d'objets persistants a peu d'utilité sans un mécanisme de recherche et d'extraction d'objets spécifiques.
Les fonctions de requête permettent aux applications d'interroger et d'extraire des objets en se basant sur divers
critères. Les opérations de requête basiques fournies par une infrastructure de mappage objet-relationnel sont les
opérations find et find unique. L'opération find unique extrait un objet spécifique et l'opération find renvoie une
collection d'objets en se basant sur des critères de requête.
Les fonctions de requête de magasin de données varient considérablement. Les magasins de données simples basés sur des
fichiers peuvent implémenter des requêtes 'maison' rigides, alors que les systèmes relationnels fournissent un langage
de manipulation de données flexible. Les infrastructures de mappage objet-relationnel étendent le modèle de requête
relationnel pour qu'il soit centré sur l'objet plutôt que sur les données. Des mécanismes passe-système sont aussi
implémentés pour utiliser les extensions de flexibilité de requête relationnelle et les extensions propres au
fournisseur (les procédures stockées, par exemple).
Notez qu'il peut y avoir un conflit entre les mécanismes de requête basés sur les bases de données et le paradigme
objet : les mécanismes de requête de base de données sont basés sur les valeurs des attributs
(colonnes) d'une table. Dans les objets correspondants, le principe d'encapsulation nous empêche de voir les valeurs
des attributs ; elles sont encapsulées par les opérations de la classe. On utilise l'encapsulation car
elle permet de changer les applications plus facilement : on peut modifier la structure interne d'une classe sans se
préoccuper des classes dépendantes, tant que les opérations publiques visibles de la classe ne changent pas. Un
mécanisme de requête basé sur la base de données dépend de la représentation interne d'une classe,
brisant ainsi l'encapsulation. Le défi de l'infrastructure est d'empêcher les requêtes de rendre les
applications fragiles en cas de changement.
La prise en charge transactionnelle permet au développeur d'applications de définir une unité de travail atomique. Dans
la terminologie relative aux bases de données, cela veut dire que le système doit pouvoir appliquer un ensemble de
changements à la base de données, ou qu'il doit s'assurer qu'aucun changement n'est appliqué. Soit les opérations d'une
transaction s'exécutent toutes avec succès, soit l'ensemble de la transaction échoue. Les infrastructures
objet-relationnel doivent fournir au minimum une fonction de transaction de validation/annulation, semblable à celle
offerte par les bases de données relationnelles. La conception d'infrastructures objet-relationnel dans un
environnement multi-utilisateur peut présenter de nombreux défis. Il faut donc y apporter une attention particulière.
En complément des fonctions fournies par l'infrastructure de persistance, les applications doivent comprendre comment
gérer les erreurs. Lorsqu'une transaction échoue ou est abandonnée, le système doit pouvoir retrouver un état antérieur
stable, en lisant les informations relatives à l'état antérieur dans la base de données. Il y a ainsi une interaction
étroite entre l'infrastructure de persistance et l'infrastructure gérant les erreurs.
Les systèmes orientés objet multi-utilisateur doivent contrôler l'accès concurrent aux objets. Lorsque beaucoup
d'utilisateurs accèdent simultanément à un même objet, le système doit fournir un mécanisme pour assurer que les
changements dans le magasin persistant se font de façon prévisible et contrôlée. Les infrastructures objet-relationnel
peuvent implémenter des contrôles d'accès concurrent pessimistes et/ou optimistes.
-
Le contrôle d'accès concurrent pessimiste nécessite que le développeur d'applications précise
quelles sont ses intentions lorsque l'objet est extrait du magasin de données (lecture seule, verrou en écriture,
etc.). Si les objets sont verrouillés, d'autres utilisateurs peuvent être bloqués au moment d'y accéder, et
contraints d'attendre que le verrou soit levé. L'accès concurrent pessimiste doit être utilisé et implémenté avec
précaution car on peut créer des situations de blocage.
-
Le contrôle d'accès concurrent optimiste suppose qu'il y a peu de chances qu'on accède
simultanément au même objet. Les conflits d'accès concurrent sont repérés lorsque les changements sont sauvegardés
dans la base de données. Généralement, si un objet a été modifié par un autre utilisateur depuis son extraction, un
message d'erreur indiquant l'échec de l'opération de changement sera envoyé à l'application. C'est l'application
qui doit repérer et gérer les erreurs. Cela implique que l'infrastructure mette en cache les valeurs concurrentes
et les compare à la base de données. L'accès concurrent optimiste coûte moins cher s'il y a peu de conflits d'accès
concurrent, et plus cher si le nombre de conflits est assez important (car il faut recommencer le travail lorsqu'il
y a des conflits).
Toutes les applications utilisant des données partagées doivent utiliser la même stratégie d'accès concurrent ; vous ne
pouvez pas mélanger le contrôle d'accès concurrent optimiste et le contrôle d'accès concurrent pessimiste dans les
mêmes données partagées, car vous risqueriez de corrompre les données. La nécessité d'avoir une stratégie d'accès
concurrent cohérente se gère mieux par le biais d'une infrastructure persistante.
Un objet a des relations avec les autres objets. Un objet Commande a beaucoup d'objets Ligne article. Un objet Livre a
beaucoup d'objets Chapitre. Un objet Employé appartient a un seul objet Entreprise. Dans les systèmes relationnels, les
relations entre entités sont implémentées à l'aide de références clés externes/clés primaires. Dans les systèmes
orientés objet, les relations sont généralement implémentées de façon explicite via les attributs. Si un objet Commande
a des objets Ligne article, il aura alors un attribut appelé Ligne article. L'attribut Ligne article de l'objet
Commande aura beaucoup d'objets Ligne article.
Les aspects relationnels d'une infrastructure objet-relationnel sont interdépendants avec les services de persistance,
de transaction et de requête. Lorsqu'un objet est stocké, extrait, ou soumis à une transaction ou à une requête, il
faut aussi considérer les objets associés :
-
Lorsqu'un objet est extrait, faut-il aussi extraire les objets associés ? Pour faire simple, oui, mais extraire les
objets associés lorsqu'ils ne sont pas nécessaires coûte très cher. Une bonne infrastructure doit permettre de
mêler les stratégies.
-
Lorsqu'un objet est stocké, faut-il aussi stocker les objets associés s'ils ont été modifiés ? Ici aussi, la
réponse dépend du contexte.
Alors qu'il est avantageux au niveau conceptuel de considérer les services objet-relationnel courants séparément, leurs
implémentations dans l'infrastructure objet-relationnel seront codépendantes. Ces services doivent être implémentés de
manière cohérente, non seulement pour toutes les organisations mais aussi pour toutes les applications qui partagent
les mêmes données. Une infrastructure est le seul moyen économique d'y parvenir.
|