Concept: Bases de données relationnelles et orientation objet
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.
Relations
Description principale

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).

Modèle de données relationnel

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 :

Diagramme détaillé dans le contenu.

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.

Modèle d'objet

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.

Diagramme détaillé dans le contenu.

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).

Infrastructures de persistance

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.

Diagramme détaillé dans le contenu.

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.

Caractéristiques essentielles d'une infrastructure objet-relationnel

  • 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.

Services objet-relationnel courants

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.

Persistance

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.

Requête

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.

Transactions

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.

Accès concurrent

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.

Relations

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.