Speicherung
23.05.2005, 00:00 Uhr
Dauerhaft – bis zur nächsten Änderung
Viele .NET-Konzepte ähneln denen der Corba- und J2EE-Welt. Die Datenpersistenz ist eines davon: Wie speichert man Daten dauerhaft – für alle verfügbar? Der Artikel erörtert die Probleme, die mit der Speicherung einhergehen und die Vorteile von Objekt-relationalen Mappern.
Tobias Grasl ist Senior Technology Consultant bei Progress Real Time Division, USA und Eric Schaumlöffel ist Senior Technology Consultant bei Progress Real Time Division in München.
Eigentlich ist die Angelegenheit ganz banal, und bereitet Entwicklern dennoch oft große Probleme: die Datenpersistenz, sprich die zuverlässige Speicherung von Anwendungsdaten. Häufig nutzen mehrere Anwendungen solche Daten; sie bleiben so lange erhalten, bis sie explizit durch Benutzer geändert oder gelöscht werden. Die Daten selbst sind unabhängig vom Lebenszyklus der Applikation. Wie wichtig die Einhaltung der Datenkonsistenz ist, zeigt sich, wenn mehrere Benutzer auf die Datenbestände zugreifen. Dann müssen Datenbanksysteme sicherstellen, dass jedem Benutzer, trotz gleichzeitigem Zugriff, ein konsistenter Zustand der Daten zur Verfügung steht. Aktuelle Veränderungen dürfen nicht verloren gehen.
Oft ist es so, dass Probleme der Datenkonsistenz erst in einem fortgeschrittenen Projektstadium erkannt werden. Treten sie erst in der Testphase zu Tage, sind Änderungen nicht nur aufwändig, sondern auch teuer. Wird dagegen bereits in der Designphase eine einheitliche applikationsübergreifende Datenzugriffsschicht (Data Layer) eingebaut, kommt dies letztlich auch der Performance einer Anwendung während der Laufzeit zu Gute.
Anforderungen an eine persistente Datenschicht
Unabhängig von der Programmiersprache und der Ablaufumgebung haben alle unternehmensweiten Applikationen ähnliche Anforderungen an die Datenpersistenz. Recht hilfreich sind hier Erfahrungen, die Java-Entwickler einbringen können. Objekt-relationale Ansätze aus der Java-Welt wie Enterprise Java Beans, Java Data Objects oder auch Hibernate (eine Open-Source-Technologie für Objekt-relationales Mapping) haben sich als recht fruchtbar erwiesen und .NET-Entwickler können von diesen Erfahrungen profitieren.
Im Idealfall sollte der dafür benötigte Programmcode ohne großen Extra-Aufwand bereitstehen. Denn Entwickler können sich dann auf die eigentliche Geschäftslogik der Applikation konzentrieren. Das heißt jedoch nicht, dass damit die Datenpersistenz ignoriert werden könnte. Auch mit guten Tools, die eine wichtige Hilfe beim Objekt-relationalen Mapping und der Code-Generierung sind, bedarf es einiger grundsätzlicher Überlegungen und Entscheidungen.
Selbst ein zügiges Design und eine schnelle Programmentwicklung sagen noch nichts über das Verhalten zur Laufzeit aus. Erst die tatsächliche Arbeitsgeschwindigkeit und Skalierbarkeit entscheiden über Erfolg oder Scheitern. Ein schlechtes Design der Datenzugriffsschicht macht sich dann negativ bei der Verfügbarkeit und Skalierbarkeit bemerkbar. Statt hier mit leistungsstärkerer Hardware "nachzubessern", verspricht Caching eine deutlich preisgünstigere und flexiblere Lösung.
So ist beispielsweise Web-Caching sehr beliebt, um schnellere Antwortzeiten zu erzielen. Bei Engpässen in unternehmensweiten Applikationen hilft es jedoch nicht weiter. Ist der Datenzugriff direkt in die Webkomponenten eingebettet, erweisen sich Anwendungen mit komplexen Anforderungen an Daten, wie sie etwa für Finanzdienstleister oder die Telekommunikationsbranche typisch sind, schnell als kaum noch administrationsfähig. Performance- und Skalierbarkeitsprobleme entstehen, wenn das Volumen der Anfragen von der Serverschicht die (beschränkten) Datenbankressourcen übersteigt. Weit besser aufgehoben sind die Funktionen für den Datenzugriff in der Serverschicht – dort, wo sich auch die Businesslogik befindet. Caching der Datenpersistenz verbessert in diesem Fall sofort die Fähigkeiten einer Applikation, möglichst viele Requests gleichzeitig bearbeiten zu können.
Abbildung 1: Schematischer Überblick der Architektur einer typischen Unternehmens-Applikation
Quelle: ObjectStore
Vorteile eines objektorientierten Ansatzes
Eine Bankapplikation beispielsweise verarbeitet pro Stunde ohne größere Probleme tausende von Transaktionen, bestehend aus Daten wie Kunden, Konten, Bankleitzahlen, Zinssätzen, Bonitätsinformationen, Plausibilitätsüberprüfungen, Währungsumrechungen etc. Gespeichert sind diese Daten in einer Vielzahl relationaler Tabellen in unterschiedlichen Datenbanksystemen. Die Verwendung einer Datenabstraktionsschicht schirmt Entwickler von den zu Grunde liegenden Details der Datenpersistenzschicht ab – durch Vorgaben im Design oder während der Laufzeit. Damit lassen sich die persistenten so wie alle anderen Objekte behandeln. Die Arbeit mit Datenzugriffs-APIs wird damit deutlich einfacher.
Bereits mit der ersten Fassung des .NET Framework hat auch Microsoft eine Datenzugriffs-API namens ADO.NET (eine Weiterentwicklung von ADO, Advanced Data Objects) eingeführt. Besonders durch die Integration in Visual Studio .NET hat sich ADO.NET als durchaus hilfreich für Entwickler erwiesen, die mit relationalen Daten arbeiten. Allerdings lässt sich ADO.NET auch mit einer großen Zahl von Tools anderer Anbieter nutzen.
Dennoch: ADO.NET ist kein objektorientierter Ansatz. Auch wenn es den Zugriff auf eine Vielzahl von Datenquellen ermöglicht, bleibt ADO.NET eine Low-Level-API. Bei komplexen Datenmodellen müssen Entwickler zusätzlichen Code schreiben, um Datenpersistenz zu erzielen. Die API verarbeitet tabellarische Ergebnismengen (DataSet-Objekte), entstanden aus SQL-Abfragen, jedoch keine "Geschäftsobjekte" wie sie in den Applikationen benutzt werden.
Im Gegensatz dazu nutzt der Ansatz des Objekt-relationalen Mappings die Stärken der objektorientierten Programmierung und wendet sie auf die Datenzugriffsschicht an. Beispielhaft dafür ist der Ansatz, wie ihn die Real-time Data Access- und Caching-Software Progress EdgeXtend realisiert hat. Durch Konzepte wie Verkapselung, Abstraktion und Wiederverwendbarkeit lässt sich dann die Datenzugriffsschicht einfacher nutzen und anpassen. In der Konsequenz führt dies zu einer produktiveren Entwicklung.
Das Objektmodell der Datenpersistenz
Den Kern dieses objektorientierten Ansatzes bildet das Objektmodell der Datenpersistenz. Im Unterschied zu den Geschäftsobjekten der Applikation repräsentieren die persistenten Daten des Objektmodells die Daten, wie sie in Datenbanken gespeichert sind. Die benötigten Übersetzungsdetails (Mapping-Details) werden bereits während des Designs oder später während der Laufzeit behandelt. Die Applikation selbst enthält keinen datenbankspezifischen Code. So lässt sich beispielsweise die relationale Tabelle A_Table mit einer C#-Klasse namens A.cs modellieren und abbilden. Die Bezeichnungen der Tabellenspalten werden zu Property Names und die CRUD-Operationen (Create, Read, Update, Delete) werden durch Methoden ersetzt.
Abbildung 2: Zwei relationale Tabellen mit dem Fremdschlüssel in der Tabelle Employee.
Quelle: ObjectStore
Beispiel: Für zwei Tabellen (Department und Employee) existiert eine One-to-Many-Beziehung. Der Fremdschlüssel (DeptID) ist in der Employee-Tabelle enthalten. Gesucht sind nun alle Employees aus dem Sales-Department. In ADO.NET könnte der benötigte Programmcode so aussehen:
String sConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Samples\\Test.mdb";
OleDbConnection myConnection = new OleDbConnection(sConn);
string sSQLquery = "select * from Employee where DeptID = ‘Sales’";
OleDbDataAdapter theAdapter = new OleDbDataAdapter(sSQLquery, sConn);
DataSet employeeData = new DataSet();
theAdapter.Fill(employeeData);
foreach (DataTable table in employeeData.Tables)
{
foreach (DataRow row in table.Rows)
{
Console.WriteLine("Employee ID: " + row["EmpID"].ToString());
Console.WriteLine("Name: " + row["Name"].ToString());
Console.WriteLine("Address: " + row["Address"].ToString());
}
}
Ermittlung der Beschäftigten der Sales-Abteilung mit ADO.NET.
Dagegen würde die gleiche Operation unter Verwendung eines Objekt-relationalen Systems so aussehen:
// Instantiate the correct department..perform db call under the covers
Department dept = DepartmentFactory.FindByPrimaryKey("Sales");
// Get all employees associated with the sales department
IList employees = dept.employees;
for (int i = 0; i < employees.Count; i++) {
Employee emp = (Employee) employees[i];
Console.WriteLine("Employee ID: " + emp.ID);
Console.WriteLine("Name: " + emp.Name);
Console.WriteLine("Address: " + emp.Address);
}
Ermittlung der Beschäftigten der Sales-Abteilung mit einem Objekt-relationalen System.
Ganz wichtig: Die zweite Variante verwendet kein SQL und keinen datenbankspezifischen Code. Die relationalen Daten werden ebenso wie jedes andere .NET-Objekt behandelt. Ein Blick auf das zweite Beispiel demonstriert, wie der Objekt-relationale Ansatz (dept.employees) die Relationen abbildet (Object-Relational Mapping, O-R Mapping) und verwendet. Die in den relationalen Tabellen enthaltenen Informationen sind damit als Objektattribute zugänglich.
Features des objektorientierten Ansatzes
Das zweite Listing verdeutlicht damit an einem sehr einfachen Fall, wie der Ansatz des OR-Mapping die Wartbarkeit von Datenpersistenz-Code verbessern kann. Dies gilt umso mehr für komplexe Datenmodelle, wie sie für unternehmensweite Applikationen üblich sind. Angenommen, Employee wäre der Basistyp eines Inheritance-Modells, bestehend aus mehreren Ausprägungen von Employee (Full_Time, Part-Time oder Consultant).
Abbildung 3: Das Modell der Vererbung für die Tabelle Employee.
Quelle: ObjectStore
Die Vererbung ist ein sehr leistungsfähiges Feature der objektorientierten Programmierung und lässt sich daher auch im Bereich Datenpersistenz ideal nutzen.
Produkte mit O-R-Mapping-Funktionalität wie die Real-time Data Access- und Caching-Software Progress EdgeXtend verfügen in der Regel über verschiedene Tools zur Definition des Mapping und zur Generierung von Code für persistente Objekte. So gibt es zum Beispiel die Möglichkeit, Objektmodelle direkt aus vorhandenen Datenbankschemata zu erzeugen.
Integriertes Caching verbessert Performance und Skalierbarkeit
Der am häufigsten verwendete Ansatz zur Behebung von Performanceproblemen ist Caching auf der Serverschicht. Je näher sich die Daten an den Geschäftsprozessen befinden, desto stärker lässt sich dadurch die Zahl der Datenbank-Calls reduzieren.
In Form der Disconnected Datasets verfügt ADO.NET über eine sehr generische Möglichkeit des Caching. Dabei werden die DataSet-Informationen in sequentielle XML-Files umgewandelt, die sich dann "offline" in lokalen Prozessen bearbeiten lassen. Anzumerken ist jedoch, dass Caching nicht das ursprüngliche Ziel der Disconnected Datasets war. Vielmehr ging es darum, die Zahl der offenen Database Connections zu verringern. Da Disconnected Datasets die Connections unmittelbar nach dem Transfer der Daten aus der Datenbank beenden, verbessert sich damit sowohl die Performance der Datenbank als auch die der Applikation.
Welche Caching-Strategien tatsächlich zum Einsatz kommen, ist abhängig von der Komplexität der Applikationen. Je nachdem, ob statisches, dynamisches oder Relationship Caching zum Einsatz kommt, hat dies konkrete Auswirkungen auf die Performance und Skalierbarkeit der involvierten Applikationen.
Die einfachste Form der Zwischenspeicherung ist das statische Caching, auch als Read-only-Caching bekannt. Ein solcher Cache enthält Daten, die sich nie ändern. Das trifft beispielsweise auf Referenzdaten zu, die eine Applikation statt mit aufwändigen Database Calls weit schneller und einfacher aus einem Cache abrufen kann. Statisches Caching löst jedoch nur die Performanceprobleme von Read-only-Daten, die in der Regel nur einen sehr kleinen Teil des gesamten Datenmodells bilden. Weit mehr Elemente sind dynamischer Natur. Auch hier gilt die bekannte 80-20-Regel: 80 Prozent der Transaktionen einer Applikation erfolgen mit 20 Prozent der Daten. Gelingt es, möglichst viele dieser Daten im Cache zu halten, lässt sich die Performance einer Applikation schon deutlich verbessern.
Im Vergleich zum statischen Caching sieht das dynamische Caching vor, die Daten nicht nur zu lesen, sondern auch zu aktualisieren. Auch wenn die im Cache geänderten Daten letztlich in die Datenbank zu übertragen sind, verbessert sich allein schon dadurch die Arbeitsgeschwindigkeit, indem Benutzern die Daten-Updates zeitnah zumindest im Lesezugriff zur Verfügung stehen.
Ein wichtiger Aspekt beim dynamischen Caching ist die Lese- beziehungsweise Schreibsperre bei Updates. Beim Pessimistic Locking sind die Daten während eines Updates nicht zugänglich. Je häufiger nun auf den Cache zugegriffen wird, desto weniger ist diese Methode geeignet, denn dadurch geht die Performance zurück. Verfahren des Optimistic Locking verwenden daher Versionsnummern für die Daten im Cache und vermeiden damit weitgehend das eigentliche Locking.
Das Relationship Caching schließlich berücksichtigt darüber hinaus nicht nur einzelne, voneinander isolierte Objekte, sondern komplexe Netzwerke von Beziehungen zwischen Objekten und kümmert sich auch um deren Administration. Dafür muss dem Relationship Caching sowohl das Objektmodell der Applikation als auch das des Datenbankschemas bekannt sein. Je komplexer diese Datenmodelle sind, um so eher lohnt sich das Relationship Caching.
Eine weitere abschließende Methode, um in Umgebungen mit einer Zentrale und vielen Standorten die Performance zu verbessern, sind Cache Clustering-Funktionen. Sie sorgen dann eigenständig für eine Synchronisation von Caches beispielsweise zwischen mehreren virtuellen Rechenzentren. Zusätzlich lassen sich dadurch Funktionen zur Fehlertoleranz und zum Load Balancing implementieren.
Literaturhinweise
Eine detaillierte Diskussion des Themas Object-Relational Mapping findet sich auf den folgenden Webseiten:
http://www.agiledata.org/essays/mappingObjects.html
(beschreibt agile Strategien von Object-Relational Mapping)
(beschreibt agile Strategien von Object-Relational Mapping)
http://www.objectarchitects.de/ObjectArchitects/orpatterns/index.htm
(eine umfangreiche Sammlung von Informationen rund um Object-Relational Mapping)
(eine umfangreiche Sammlung von Informationen rund um Object-Relational Mapping)
http://www.objectstore.com/publications/caching_resources/index.ssp
(eine Diskussion von "make" versus "buy")
(eine Diskussion von "make" versus "buy")
B. Weitere Informationen über Caching:
http://www.research.ibm.com/AEM/pubs/ssgrr01.pdf
(ein Fachaufsatz, der unterschiedliche Ansätze miteinander vergleicht)
(ein Fachaufsatz, der unterschiedliche Ansätze miteinander vergleicht)
http://www.objectstore.com/publications/caching_resources/intell_caching/index.ssp
(eine Diskussion von "make" versus "buy")
(eine Diskussion von "make" versus "buy")