Whitepapers & Broschüren
Den "Impedance Mismatch" auf der Datenbankebene vermeiden
Mary A. Finn
Product Marketing Manager
Einführung
Nicht zuletzt durch die große Anerkennung und weite Verbreitung von Java, C++ und COM ist objektorientierte Programmierung in den Vordergrund der Anwendungsentwicklung gerückt. Wegen ihrer reichen Datenmodelle und der Unterstützung produktivitätssteigernder Konzepte wie Kapselung, Vererbung und Polymorphismus werden solche Objekt-Technologien heute von Anwendungsentwicklern klar bevorzugt.
Allerdings sind weltweit immer noch die meisten Daten in relationalen Datenbanken abgelegt. Entwickler von Datenbankanwendungen (d.h. aller Anwendungen, die auf gespeicherte Daten zugreifen) haben häufig mit dem "Impedace Mismatch" genannten Problem zu kämpfen, dem inhärenten Bruch zwischen dem objektorientierten und dem relationalen Datenmodell. Bemühungen, die relationalen Daten auf ein brauchbares Objektformat abzubilden, gehen häufig auf Kosten sowohl der Produktivität der Programmierer als auch der Performance von Anwendungen.
Der Impedance Mismatch kann jedoch durch die richtige Wahl von Datenbanktechnologien gemildert werden. In dieser Abhandlung wird der Impedance Mismatch definiert und anhand zweier einfacher Beispiele dargelegt, wie er die Anwendungsentwicklung beeinträchtigt. Anschließend wird im Hinblick auf den Impedance Mismatch das Für und Wider dreier Arten von Datenbanken diskutiert, nämlich relationaler und objektorientierter Datenbanken sowie von Caché, der multidimensionalen Datenbank von InterSystems.
Den Impedance Mismatch verstehen
Impedance Mismatch (wörtlich: Impedanz-Fehlanpassung) ist ein aus der Elektrotechnik übernommener Begriff; auf dem Gebiet der Software bezieht er sich jedoch auf den inhärenten Unterschied zwischen dem relationalen und dem objektorientierten Datenmodell.
Sehr einfach ausgedrückt heißt das: Im relationalen Modell werden alle Daten in Zeilen und Spalten verwaltet. Jede Zeile stellt einen Datensatz und die Spalten stellen die verschiedenen Datenfelder in einem Datensatz dar. Sind die Daten für die Beschreibung durch ein zweidimensionales Raster zu komplex, werden zusätzliche Tabellen zur Erfassung der abhängigen Informationen erstellt. Jede Tabelle in einem relationalen Schema wird also einige, jedoch nicht alle Datenfelder für sehr viele Datensätze umfassen.
Das Objektmodell beschränkt sich nicht darauf, Daten lediglich in Zeilen und Spalten zu erfassen. Stattdessen erstellt der Entwickler eine Definition – also ein Produktionsmuster oder Template – zur vollständigen Beschreibung einer bestimmten Klasse von Informationen. Jeder Datensatz (Objekt) ist eine spezifische Instanz dieser Klasse. Jedes Objekt enthält somit alle Datenfelder für einen einzigen Datensatz. Doch das ist noch nicht alles. Klassendefinitionen dürfen auch Codesegmente – sogenannte Methoden – enthalten, die auf die von der Klasse beschriebenen Daten einwirken. Im relationalen Modell gibt es kein analoges Konstrukt.
Ein einfaches Beispiel
Zur Verdeutlichung der Unterschiede zwischen den beiden Datenmodellen stellen Sie sich vor, dass Sie eine Debitorenanwendung entwickeln. Ihre Anwendung wird sicher eine Anzahl von Rechnungen erfassen müssen, von denen jede eine Reihe von einleitenden Angaben (wie z.B. Rechnungsdatum), eine Rechnungsnummer sowie einen oder mehrere Posten aufweist. Jeder dieser Posten enthält unter anderem Angaben über das bestellte Produkt und die entsprechende Stückzahl.
Ein Weg, um die Rechnung in einer relationalen Datenbank nachzubilden, besteht in der Erstellung zweier Tabellen. Eine Tabelle – Invoice (Rechnung) genannt – enthält eine Reihe von Angaben, die nur einmal in jeder Rechnung vorkommen. Eine zweite Tabelle – LineItems (Posten) – umfasst Spalten für Invoice_Parent (übergeordnete Rechnung), Line_Item_Product_Code (Posten-Artikelnummer) und Line_Item_Quantity (Posten-Menge). Die erste Spalte ist besonders wichtig, da dieser Wert die Posten mit den Angaben in der Rechnungstabelle "verbindet".
Beachten Sie, dass keine der Tabellen alle Angaben über eine bestimmte Rechnung enthält. Stattdessen enthält jede einen Teil der Angaben über viele Rechnungen. Sollte Ihre Anwendung beispielsweise dafür vorgesehen sein, eine Rechnung zu drucken, muss sie sowohl auf die Tabelle für die Rechnung als auch auf die Tabelle für die Posten zugreifen, um die einleitenden Informationen bzw. die Detailinformationen abzurufen. Beachten Sie auch, dass die Tabellen keine Anweisungen über die Formatierung der Daten für den Ausdruck enthalten. Diese Anweisungen befinden sich außerhalb der Datenbank.
Im Objektmodell müssen Daten nicht in Zeilen und Spalten passen, sodass die Klassendefinition für die Rechnung wie eine Liste aller Datenfelder aussieht, die eine Rechnung ausmachen. Es gibt Eigenschaften, die die einleitenden Informationen enthalten, z.B. InvoiceDate (Rechnungsdatum), InvoiceNumber (Rechnungsnummer) usw., und eine Auflistung einer oder mehrerer Instanzen der Klasse LineItem (Posten). Die Klasse LineItem beinhaltet die Eigenschaften ProductCode (Artikelnummer) und LineItemQuantity (Posten-Menge).
Klassendefinitionen sind lediglich Produktionsmuster für das Datenformat. Jede einzelne Rechnung ist eine spezifische Instanz der Rechnungsklasse und enthält die spezifischen Instanzen der Postenklasse, die zu ihr gehören. Somit enthält jedes Rechnungsobjekt alle Angaben für eine bestimmte Rechnung, und zwar ausschließlich Angaben für diese eine Rechnung.
Doch Klassendefinitionen können auch Methoden beinhalten, die Auswirkungen auf die durch die Klasse beschriebenen Daten haben. So kann zum Beispiel Ihre Rechnungsklasse eine Print()-Methode enthalten, die angibt, wie die Rechnungsinformationen für den Ausdruck zu formatieren sind. Persistente Objekte verfügen über eine Save()-Methode, die festlegt, wie Objekte in der Datenbank gespeichert werden. Die Standardimplementierung dieser Save()-Methode wird von der Struktur der Datenbank-Engine bestimmt und vom Datenbankanbieter bereitgestellt.
Impedance Mismatch bei der Datenbankbearbeitung
Stellen Sie sich vor, dass eine neue Rechnung mit einem Posten in der Debitorenanwendung erstellt werden soll. Sollten Sie für eine relationale Datenbank programmieren, würde Ihr Code etwa so lauten wie in Beispiel 1 gezeigt. Er würde zwei Insert-Anweisungen enthalten: Mit einer werden einleitenden Informationen zur Tabelle Invoice (Rechnung), mit der anderen Detailinformationen zur Tabelle LineItems (Posten) hinzugefügt. Insert ist ein SQL-Standardbefehl, und der Anbieter der relationalen Datenbank sorgt für seine Implementierung.
| Beispiel 1 : Erstellen einer neuen Rechnung im relationalen Modell
|
Der Code zum Speichern einer Rechnung mit einem Posten unter Verwendung des Objektmodells wird in Beispiel 2 gezeigt. Abgesehen von Einzelheiten bei der Syntax kommt es dem relationalen Beispiel sehr nahe. Der Hauptunterschied besteht darin, dass die Save()-Methode nur einmal aufgerufen wird.
| Beispiel 2: Erstellen einer neuen Rechnung im Objektmodell
|
Stellen Sie sich nun vor, dass Sie die Geschäftslogik für Ihre Anwendung in einer objektorientierten Sprache wie Java oder C++ schreiben möchten, die Daten aber in einer relationalen Datenbank gespeichert werden müssen. Soll dies für Ihre Rechnung geschehen, müssen die SQL-Insert-Anweisungen in der Save()-Methode Ihrer Klassendefinition für die Rechnung programmiert werden. Dies ist eine Erscheinungsform des Impedance Mismatch: eine Objektklasse mit einer Collection, die in die verschiedenen Tabellen einer relationalen Datenbank-Engine übersetzt werden muss.
Impedance Mismatch beim Design
Eine andere Form des Impedance Mismatch kann beim Anwendungs-Design zutage treten. Denn hier bietet die Objekt-Technologie nicht nur umfangreichere und intuitivere Möglichkeiten für die Nachbildung von Daten, sondern beinhaltet auch verschiedene Konzepte, die die Produktivität von Programmierern erheblich steigern. Insbesondere unterstützt die Objekt-Technologie die Konzepte Vererbung und Polymorphismus.
Dabei bedeutet Vererbung, dass eine Klassendefinition von einer anderen abgeleitet werden kann. So haben Sie z.B. in der Debitorenanwendung die Möglichkeit, eine allgemeine Klasse InvoiceTemplate (Rechnungsvorlage) anzulegen und anzugeben, dass die spezifischeren Klassen SoftwareInvoice (Softwarerechnung) und HardwareInvoice (Hardwarerechnung) Eigenschaften und Methoden von InvoiceTemplate erben. (Natürlich können auch nicht ererbte, für jede Klasse spezifische Eigenschaften und Methoden darin enthalten sein.) Wenn bei der Entwicklung der Anwendung Änderungen an InvoiceTemplate vorgenommen werden, schreibt die Vererbung vor, dass sich diese Änderungen automatisch in den Klassendefinitionen SoftwareInvoice und HardwareInvoice widerspiegeln.
Polymorphismus bedeutet, dass unterschiedliche Implementierungen einer Methode über eine gemeinsame Schnittstelle verfügen können. Es ist z.B. möglich, dass die Print()-Methode in SoftwareInvoice und HardwareInvoice unterschiedliche Anweisungen zur Formatierung usw. enthält. Zum Drucken einer Rechnung muss Ihre Anwendung allerdings nur ein Objekt in den Speicher laden und dessen Print()-Methode aufrufen. Durch den Polymorphismus „weiß“ das Objekt, wie es sich abhängig von der Klasse, zu der es gehört, für den Druck formatieren muss.
Im relationalen Modell gibt es weder Vererbung noch Polymorphismus. Einige große Datenbankanbieter wie Oracle, Microsoft und IBM haben den Versuch unternommen, objektorientierte Designkonzepte zu realisieren, aber die Ergebnisse bieten im Allgemeinen nicht die Fähigkeiten, die Objektprogrammierer erwarten.
Ansätze zur Verringerung des Impedance Mismatch
Die beiden oben aufgeführten Beispiele zum Impedance Mismatch sind stark vereinfachend, genügen aber zur Veranschaulichung des Problems. Der erforderliche Aufwand zur „Normalisierung“ des Impedance Mismatch kann erheblich sein und wächst in dem Maße drastisch an, in dem die Komplexität der Anwendung zunimmt. Die Auswirkungen des Impedance Mismatch können jedoch durch die richtige Wahl der Datenbanktechnologie beträchtlich verringert werden.
Betrachten Sie dazu drei Optionen für die Speicherung von Daten: eine relationale Datenbank, eine "reine" Objektdatenbank und eine multidimensionale Caché-Datenbank.
Verwenden einer relationalen Datenbank
In dieser Abhandlung wurde bereits dargelegt, wie die Verwendung einer relationalen Datenbank mit einer auf Objekt-Technologie basierenden Anwendung ernsthafte Impedance-Mismatch-Probleme nach sich ziehen kann. Doch in manchen Fällen haben Entwickler keine Wahl. Sie müssen unter Umständen auf bestehende Daten zugreifen, die in einer relationalen Datenbank abgelegt sind. Eine Option wäre in diesem Fall, ein objekt-relationales Mapping-Tool zu verwenden, seien es nun ein Standalone-Tool oder die Abbildungsfunktionen, die in einige so genannte „objekt-relationale“ Datenbanken integriert sind.
Im Wesentlichen legen Mapping-Tools eine Datei an – eine Map oder Abbildung –, die den Code zur Übersetzung zwischen Objekten und relationalen Tabellen enthält. Entwickler müssen genau angeben, wie diese Übersetzung abläuft, d.h., welche Objekteigenschaften welchen Datenspalten in welchen Tabellen entsprechen, und umgekehrt. Nachdem sie angelegt wurde, wird die Abbildung gespeichert und jedes Mal wieder aufgerufen, wenn die Anwendung Daten in die oder aus der Datenbank verschiebt. Einige objekt-relationale Mapping-Tools sind mit einer Laufzeit-Caching-Komponente ausgestattet, um die Performance-Einbußen zu kompensieren, die durch die Übersetzung von Daten zwischen Objekten und relationalen Masken entstehen.
Abgesehen von Laufzeit-Performance-Problemen kann die objekt-relationale Abbildung die Anwendungsentwicklung erheblich verzögern. Ein Großteil der Mapping-Tools implementieren fortgeschrittene Konzepte des Objektmodells wie Vererbung oder Polymorphismus nicht oder nur zum Teil. Daher müssen bei der Anpassung und Modifizierung von Anwendungen neue, aktualisierte objekt-relationale Abbildungen angelegt werden.
Für Entwickler, die gegen den Impedance Mismatch zwischen objektorientierten Anwendungen und relationalen Datenbanken ankämpfen, könnte es angebracht sein, die Daten in einen objektfreundlicheren Datenspeicher zu migrieren. Sie müssen den einmal entstehenden Aufwand für die Umformatierung und die Übertragung der Daten gegen die laufende Arbeit sowie die Performance-Verluste beim Verwenden einer objekt-relationalen Abbildung abwägen.
Verwenden einer Objektdatenbank
Auf den ersten Blick sieht es so aus, als ließe sich der Impedance Mismatch durch die Speicherung von Daten in einer "reinen" Objektdatenbank gänzlich ausschalten. Dies ist teilweise richtig. Im Allgemeinen ist es für eine objektorientierte Anwendung nicht schwierig, mit einer Objektdatenbank zu interagieren. In diesem Szenario tritt jedoch ein Impedance Mismatch auf, wenn Sie eine SQL-Abfrage für die Datenbank ausführen möchten. SQL ist die weltweit mit Abstand am häufigsten verwendete Abfragesprache, und sie setzt voraus, dass die Daten in relationalen Tabellen gespeichert werden. Einige Anbieter von Objektdatenbanken bieten Datenzugriff über eine Objektabfragesprache (Object Query Language, OQL), aber diese Sprachen genießen keine breite Anerkennung. Um mit gebräuchlichen Anwendungen für Datenanalysen und Berichte kompatibel zu sein, muss eine Objektdatenbank ODBC und JDBC unterstützen und daher mit einigen Mechanismen ausgestattet sein, um Daten als relationale Tabellen zu projizieren.
Auch hier heißt die übliche Lösung wieder: Mapping. Die Nachteile des Verfahrens – Performance-Verluste und mangelnde Unterstützung für die Datenmodellentwicklung – sind nach wie vor nicht überwunden. Der Vorteil liegt darin, dass die Abbildung nur aufgerufen werden muss, wenn eine SQL-Abfrage für die Datenbank ausgeführt wird.
Verwenden einer multidimensionalen Datenbank mit "Unified Data Architecture"
Zum Speichern von Daten gibt es aber noch eine dritte Möglichkeit: Caché, die multidimensionale Datenbank von InterSystems. Während andere multidimensionale Datenbanken meist im Bereich Data Warehousing angesiedelt sind, ist Caché für den Einsatz in transaktionsverarbeitenden Anwendungen konzipiert. Und Caché implementiert einen einzigartigen Ansatz zur Verringerung des Impedance Mismatch: die "Unified Data Architecture".
Durch die Unified Data Architecture nutzen das objektorientierte und das relationale Datenmodell die multidimensionalen Daten von Caché gemeinsam. Multidimensionale Arrays sind leicht als Tabellen zu projizieren, da Tabellen im Grunde nur zweidimensionale Arrays sind. Andererseits ist eine Korrelation zwischen Objekten und multidimensionalen Arrays leicht möglich, da beide nicht auf das Zeilen-und-Spalten-Format der relationalen Technologie beschränkt sind. Die Übersetzung zwischen den Datenformaten erfolgt automatisch und wird Bestandteil der kompilierten Datendefinition. Für Entwickler ist jede Tabelle effektiv ein Objekt, und jedes Objekt besteht aus einer oder mehreren Tabellen.
Zu den weiteren Eigenschaften der
Unified Data Architecture von Caché zählen:
Umfassende Parallelität (Concurrency)
Auf Aktualisierungen der Daten, die über die relationale Schnittstelle vorgenommen werden, kann sofort über die Objektschnittstelle zugegriffen werden (und umgekehrt).
Unterstützung der Datenmodell-Evolution
Änderungen an der Datenstrukturdefinition spiegeln sich automatisch sowohl in der objektorientierten als auch in der relationalen Darstellung wider.
Vollständige SQL-Unterstützung
Alle SQL DDL-, DML- und DCL-Befehle werden unterstützt.
Vollständige Objektunterstützung
Alle Konzepte des Objektmodells wie einfache und mehrfache Vererbung, Polymorphismus, Advanced Datatypes sowie Methodengeneratoren werden durchweg unterstützt.
Objekt-Server
In der Unified Data Architecture definierte Objekte können als Java-, C++-
oder COM-Objekte bereitgestellt werden und bieten so Kompatibilität mit einer Vielzahl objektorientierter Technologien.
Die Unified Data Architecture kann den Impedance Mismatch drastisch reduzieren, ihn aber nicht gänzlich eliminieren. Es gibt einige Konzepte, z.B. Objektmethoden oder relationale Trigger, die nicht automatisch gemeinsam genutzt werden können. Caché ist dennoch eine gute Wahl für Entwickler, die den objektorientierten und relationalen Datenzugriff miteinander kombinieren möchten.
Fazit
In den letzten Jahren sind objektorientierte Programmiersprachen wie Java, C++ und COM die führenden Technologien für die Anwendungsentwicklung geworden. Daher müssen Entwickler von Datenbankanwendungen in der Lage sein, Daten als Objekte zu projizieren.
Andererseits ist SQL bei weitem die vorherrschende Technologie für die Datenanalyse und die Erstellung von Berichten. Um nutzbringend zu sein, muss eine Datenbank daher in der Lage sein, Daten als relationale Tabellen zu projizieren, auf die über ODBC und JDBC zugegriffen werden kann.
Impedance Mismatch – die inhärente Trennung zwischen dem objektorientierten und dem relationalen Datenmodell – kann zwar nicht vermieden, aber mit der Auswahl der richtigen Datenbanktechnologie spürbar abgeschwächt werden. Für die Entwicklung neuer Anwendungen ist es sinnvoll, Daten in einer multidimensionalen Datenbank wie Caché zu speichern, die aufgrund ihrer Unified Data Architecture die Möglichkeit bietet, Daten gleichzeitig als Objekte und als Tabellen zu projizieren.
Für die laufende Anwendungsentwicklung, bei der bestehende Daten z.B. bereits in einer relationalen Datenbank gespeichert sind, sollten Entwickler in Betracht ziehen, Daten in ein multidimensionales Format zu konvertieren. Durch einen einmaligen Aufwand für die Konvertierung ihrer Daten ersparen sie sich Performance-Nachteile und Kopfzerbrechen bei der Entwicklung von Datenmodellen, die die objekt-relationale Abbildung häufig mit sich bringt.

