INSTITUT FÜR ANGEWANDTE INFORMATIK UND FORMALE BESCHREIBUNGSVERFAHREN (AIFB) Universität Karlsruhe (TH) Persistenzmodelle in EJB-Architekturen Diplomarbeit Tilman Seifert 25. Januar 2002 Aufgabensteller: Betreuer: Prof. Dr. Wolffried Stucky Dr. Roland Schätzle Prof. Dr. Johannes Siedersleben, sd&m Research GmbH Gerd Beneken, sd&m Research GmbH Erklärung Ich versichere hiermit wahrheitsgemäß, die Arbeit bis auf die dem Aufgabensteller bereits bekannte Hilfe selbständig angefertigt, alle benutzten Hilfsmittel vollständig und genau angegeben und alles kenntlich gemacht zu haben, was aus Arbeiten anderer unverändert oder mit Abänderung entnommen wurde. München, den 25. Januar 2002 Danksagung Denen, die zum Gelingen dieser Arbeit beigetragen haben, danke ich herzlich: Herr Siedersleben und das gesamte sd&m Research-Team haben ein sehr gutes Umfeld für diese Arbeit geschaffen. Die Betreuung durch Gerd Beneken war exzellent; er hat mit seinen vielen Ideen, Hinweisen und Hilfestellungen einen wesentlichen Teil zum Erfolg beigetragen. Roland Schätzle hat mich mit seinen wertvollen kritischen Anmerkungen auf Kurs gehalten und hat in der hektischen Abgabephase Geduld und Flexibilität bewiesen. Außerdem gilt mein Dank meinen Geschwistern und den Hardtwald-Läufern“ für ihre ” Hilfe bei schwierigen Entscheidungen und nicht zuletzt meinen Eltern sowie meiner Tante Elke Seifert, die mich unterstützt und mir mein Studium überhaupt ermöglicht haben. ii Inhaltsverzeichnis 1 Einleitung 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Aufgabenstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Überblick über die Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 2 2 Architektur betrieblicher Informationssysteme 2.1 Charakteristika betrieblicher Informationssysteme . . . . . . . . . . . . 2.2 Komponentenorientierte Software-Entwicklung . . . . . . . . . . . . . 2.2.1 Eigenschaften einer Komponente . . . . . . . . . . . . . . . . . 2.2.2 Entwurf von Komponenten . . . . . . . . . . . . . . . . . . . . 2.2.3 Abgrenzung zur objektorientierten Software-Entwicklung . . . 2.3 Quasar als komponentenorientierte Architektur . . . . . . . . . . . . . 2.3.1 Softwarekategorien . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.2 Elemente von Quasar . . . . . . . . . . . . . . . . . . . . . . . 2.3.3 Kritik/Einordnung . . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Aufbau einer Standard-Architektur betrieblicher Informationssysteme 2.4.1 Anwendungs-Komponenten . . . . . . . . . . . . . . . . . . . . 2.4.2 Anwendungs-Fälle . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.3 Anwendungs-Entitäten . . . . . . . . . . . . . . . . . . . . . . . 2.4.4 Anwendungs-Entitäten-Verwalter . . . . . . . . . . . . . . . . . 2.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 4 5 5 6 6 7 9 9 10 10 10 11 11 12 3 Standardprobleme beim Datenbankzugriff 3.1 Objekt-relationale Abbildung . . . . . . . . . . . . . . . . . . . . . 3.1.1 Abbildung von Attributen . . . . . . . . . . . . . . . . . . . 3.1.2 Abbildung von Beziehungen zwischen Objekten . . . . . . . 3.1.3 Objektidentität . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.4 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Transaktionsstrategien . . . . . . . . . . . . . . . . . . . . . 3.2.2 Abbildung von Transaktionen auf die Benutzer-Interaktion 3.2.3 Implementierung der fachlichen Transaktionen . . . . . . . 3.3 Performanz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4 Wartung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 13 14 14 14 16 16 18 19 20 21 . . . . . . . . . . . . . . . . . . . . . . iii Inhaltsverzeichnis 4 Enterprise Java Beans 4.1 Die EJB-Spezifikation . . . . . . . . . . . . . . . . . . . . 4.2 Infrastruktur und Produkte . . . . . . . . . . . . . . . . . 4.3 Enterprise-Beans . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Aufbau einer Bean . . . . . . . . . . . . . . . . . . 4.3.2 Session-Beans . . . . . . . . . . . . . . . . . . . . . 4.3.3 Entity-Beans . . . . . . . . . . . . . . . . . . . . . 4.3.4 Lebenszyklen von Session- und Entity-Beans . . . 4.3.5 Transaktionen . . . . . . . . . . . . . . . . . . . . 4.4 Persistenz von Entity-Beans . . . . . . . . . . . . . . . . . 4.4.1 Container Managed Persistence (CMP) . . . . . . 4.4.2 Einsatz eines OR-Mapping-Werkzeugs für CMP . . 4.4.3 Bean Managed Persistence (BMP) . . . . . . . . . 4.4.4 Java Data Objects (JDO) . . . . . . . . . . . . . . 4.4.5 Weitere Aspekte . . . . . . . . . . . . . . . . . . . 4.5 EJB 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.1 Erweiterungen in EJB 2.0 bezüglich der Persistenz 4.5.2 Bewertung der Änderungen . . . . . . . . . . . . . 4.5.3 Der Wechsel von EJB 1.1 auf EJB 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 22 23 24 24 27 29 29 31 32 32 34 35 37 37 37 38 40 41 5 Persistenzoptionen von A-Entitäten 5.1 Persistenz über Entity-Beans . . . . . . . . . . . . . . 5.1.1 Kritik am naiven Einsatz von Entity-Beans . . 5.1.2 Zusammenfassung . . . . . . . . . . . . . . . . 5.2 Persistenz über native Java-Objekte . . . . . . . . . . 5.2.1 Ausprogrammieren mit JDBC . . . . . . . . . . 5.2.2 Zugriffsschichten: QDI und TopLink . . . . . . 5.2.3 Exkurs: Objektorientierte Datenbanken . . . . 5.2.4 Datenbankzugriff über Entity-Beans mit CMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 42 43 48 48 48 49 53 55 . . . . . . . . . . . . . . . . 6 Persistenz über ein neutrales Interface 6.1 Entwurf wartbarer Systeme mit EJB . . . . . . . . . . . . . . . . . . . 6.1.1 Innovationszyklen betrieblicher Informationssysteme . . . . . . 6.1.2 Entkoppelung des Anwendungskerns vom Datenbankzugriff über ein neutrales Interface . . . . . . . . . . . . . . . . . . . . . . . 6.2 Anforderungen an das neutrale Interface . . . . . . . . . . . . . . . . . 6.3 Design des neutralen Interfaces . . . . . . . . . . . . . . . . . . . . . . 6.4 Implementierung des neutralen Interfaces . . . . . . . . . . . . . . . . 6.4.1 Spezifische JDBC-Lösung . . . . . . . . . . . . . . . . . . . . . 6.4.2 Einsatz einer Datenbankzugriffsschicht . . . . . . . . . . . . . . 6.4.3 Umsetzung mit CMP . . . . . . . . . . . . . . . . . . . . . . . 6.5 Beurteilung des Entwurfs . . . . . . . . . . . . . . . . . . . . . . . . . 6.5.1 Wartbarkeit des Codes . . . . . . . . . . . . . . . . . . . . . . . 6.5.2 Unabhängigkeit von der verwendeten Basistechnologie . . . . . iv 57 57 57 57 60 61 62 62 63 65 66 66 67 Inhaltsverzeichnis 6.5.3 6.5.4 Effizienz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . 67 7 Fallstudie: Telekom-Billing-System 7.1 Überblick: Die Komponenten des TBS . . . . . . . . . . . . . . 7.1.1 Kundenverwaltung . . . . . . . . . . . . . . . . . . . . . 7.1.2 Rechnungsverwaltung . . . . . . . . . . . . . . . . . . . 7.1.3 Tarif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.1 Überblick über die Klassen des Anwendungskerns . . . . 7.2.2 Das neutrale Interface . . . . . . . . . . . . . . . . . . . 7.2.3 Implementierung 1: Datenbankzugriff über Entity-Beans 7.2.4 Implementierung 2: Datenbankzugriff über das QDI . . 7.2.5 Vergleich der zwei Implementierungen . . . . . . . . . . 7.2.6 Praxiserfahrungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 70 70 73 75 75 75 78 79 79 80 80 8 Zusammenfassung 84 8.1 Erkenntnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 8.2 Vorteile des Ansatzes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 8.3 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Index 89 Literaturverzeichnis 90 v Inhaltsverzeichnis vi 1 Einleitung 1.1 Motivation Die Entwicklung großer betrieblicher Informationssysteme ist nicht nur gekennzeichnet durch fachliche und technische Komplexität, sondern auch durch eine hohe Lebensdauer der Systeme. Deshalb kommen der Wartbarkeit und der Erweiterbarkeit solcher Systeme eine hohe Bedeutung zu. Diesbezüglich werden zur Zeit hohe Erwartungen an die komponentenorientierte Software-Entwicklung geknüpft. Einen Schritt in diese Richtung unternimmt die Spezifikation der Enterprise Java Beans (EJB) der Firma Sun Microsystems im Rahmen der J2EE-Gesamtspezifikation (Java 2 Enterprise Edition). Sie verspricht, durch Komponentenorientierung einen hohen Grad an Wiederverwendbarkeit zu erreichen. Sie verspricht weiterhin, durch den Einsatz von Applikationsservern für alle technischen Aspekte, wie z. B. Datenbankzugriff, Ressourcenverwaltung und Client-Server-Verbindungen, transparente Mechanismen anzubieten, welche es den Entwicklern ermöglichen sollen, sich voll auf die fachliche Komplexität zu konzentrieren. Die J2EE-Architektur ist zwar noch recht jung (erste Produkte erschienen 1997), aber da die Verbreitung von Java als Programmiersprache und Plattform auch im Serverbereich deutlich zugenommen hat, lohnt es sich auf jeden Fall, die Praxistauglichkeit auch für den Einsatz in großen Software-Projekten zu untersuchen. Diese Diplomarbeit wurde erstellt in Zusammenarbeit mit der sd&m Research GmbH. Die sd&m AG erstellt individuelle betriebliche Informationssysteme und verfügt über Erfahrung mit großen Projekten für geschäftskritische Anwendungen mit den verschiedensten Hard- und Software-Plattformen, Programmiersprachen, Datenbanken, Middleware, Applikationsservern etc. Die sd&m Research GmbH ist die F&E-Einrichtung von sd&m. Hier werden neue Technologien daraufhin untersucht, wie sie in sd&m-Projekten eingesetzt werden können. Wissen und Erfahrungen aus den Projekten wird gesammelt und so aufbereitet, dass es für weitere Projekte verfügbar wird. So konnte diese Diplomarbeit von den vorhandenen Projekterfahrungen mit EJBApplikationsservern profitieren. Vorhandene Lösungen konnten im Detail betrachtet und kritisch bewertet werden. Die auf diese Weise gesammelten Erfahrungen wurden in ein beispielhaftes Projekt übernommen; dieses Beispielprojekt wird Gegenstand der praktischen Betrachtungen sein. 1 1 Einleitung 1.2 Aufgabenstellung Ein spezieller Aspekt, der in jedem betrieblichen Informationssystem zu lösen ist, ist die Persistenz von Komponenten (und nicht nur von Klassen) in typischerweise relationalen Datenbanken. In dieser Arbeit sollen die Möglichkeiten untersucht werden, die in EJB-Anwendungen zur Verfügung stehen. EJB bringt einen PersistenzMechanismus mit, der aber genau geprüft werden muss und nicht die einzige Möglichkeit darstellt. Insbesondere bei komplexen Datenstrukturen stößt er schnell an seine Grenzen. Es müssen auch Aspekte wie das Zusammenspiel mit vorhandenen Nachbar- oder Altsystemen, die Arbeit auf einem vorgegebenen Datenbankschema oder das Verhalten bei hochgradig nebenläufigem Zugriff in die Überlegungen mit einbezogen werden. Diese Diplomarbeit untersucht verschiedene Möglichkeiten, das Problem der Persistenz von Komponenten auf der Architekturebene zu behandeln. Ziel ist es, belastbare Designvorschläge zu erarbeiten, die es ermöglichen, • effizient auf eine Datenbank zuzugreifen und komplexe Daten einer Komponente zu speichern, • wartbare Systeme zu erhalten und • von der aktuellen EJB-Spezifikation und den Eigenschaften eines Produktes weitgehend unabhängig zu sein. 1.3 Überblick über die Arbeit Kapitel 2 setzt sich mit den Charakteristika betrieblicher Informationssysteme auseinander, stellt Ideen zum komponentenorientierten Softwareentwurf vor und beschreibt eine allgemeine Architektur für Informationssysteme. In Kapitel 3 werden die Probleme aufgezeigt, die in jedem objektorientierten Informationssystem auftreten, das auf eine relationale Datenbank zugreift. Kapitel 4 beschreibt die EJB-Architektur in der Version 1.1 und gibt einen Ausblick auf die kommende Version 2.0. Kapitel 5 stellt den naiven Einsatz der EJB-Mechanismen zur Persistenz vor und setzt sich kritisch mit ihnen auseinander. Es zeigt weitere Möglichkeiten, aus einer objektorientierten Anwendung auf eine relationale Datenbank zuzugreifen. Kapitel 6 stellt eine Architektur vor, welche alle gewünschten Dienste von EJB in Anspruch nimmt, gleichzeitig aber durch den Einsatz eines neutralen Interfaces möglichst unabhängig von der aktuellen Spezifikation und von bestimmten Produkten bleibt. Anhand eines Praxisbeispiels wird in Kapitel 7 diese Architektur mit zwei unabhängigen Implementierungen vorgestellt. Diese Implementierungen haben ihren Ursprung in realen Projekten der sd&m-AG und haben sich im produktiven Einsatz bereits bewährt. 2 1.3 Überblick über die Arbeit Sie werden hinsichtlich ihrer Entwurfs-Qualität und ihrer Performanz verglichen. Der Ausblick nennt einerseits weitere mögliche Varianten der Implementierung und der Architektur. Andererseits soll die Entwicklung der Applikationsserver-Technologie insgesamt gewürdigt werden, um nicht den Blick auf J2EE zu verengen, sondern auch zukünftige Entwicklungen realistisch einschätzen zu können. 3 2 Architektur betrieblicher Informationssysteme 2.1 Charakteristika betrieblicher Informationssysteme Betriebliche Informationssysteme zeichnen sich aus durch folgende Eigenschaften aus: • Hohe Datenmengen • Hohe Client-Lasten • Anforderungen an Ausfallsicherheit: Sonst liegt der ganze Betrieb lahm. • Anforderungen an Wartbarkeit, Änderungsfreundlichkeit und Erweiterbarkeit: Typischerweise haben betriebliche Informationssysteme eine sehr hohe Lebensdauer.1 • Verteilte Systeme: Kopplung von heterogenen Systemen. Häufig wird die Verarbeitung verteilt auf Host-Rechner, Applikationsserver und Clients, die jeweils mit unterschiedlichen Systemen arbeiten. Diese Eigenschaften stellen besondere technische Herausforderungen an den Entwurf eines solchen Systems. 2.2 Komponentenorientierte Software-Entwicklung Ziel des Software Engineering ist es, die Übersichtlichkeit, Wartbarkeit und Wiederverwendbarkeit von (Teil-) Systemen zu erhalten, auch wenn die Funktionalität immer komplexer wird. Große Hoffnungen hat hier die Objektorientierung mit der Einführung von Geheimnisprinzip, Schnittstellenkonzept und Vererbung geweckt. Es hat sich aber gezeigt, dass Klassen zu klein sind zur Strukturierung großer Systeme. Anforderungen an ein System ändern sich immer wieder und erfordern kontinuierliche Pflege; zunehmende Vernetzung erfordert Wiederverwendbarkeit auch über technische Grenzen wie Betriebssysteme und Programmiersprachen hinweg. Aus diesen Forderungen ist der Begriff der Komponente entstanden. 1 4 Projekte der sd&m AG sind z. T. 10 Jahre und länger im Einsatz. 2.2 Komponentenorientierte Software-Entwicklung 2.2.1 Eigenschaften einer Komponente Was genau ist unter einer Komponente zu verstehen? Es existiert keine einheitliche Definition für Komponenten; es gibt Erklärungen dafür, was man von einer Komponente erwarten darf. Eine häufig zitierte Definition stammt von Szyperski [Szy99, S. 34]: • A software component is a unit of composition with contractually specified ” interfaces and explicit dependencies only. • A software component can be deployed independently and • is subject to composition by third parties.“ Eine weitere Definition findet sich in [Si01] von Siedersleben et al.: • Sie ist eine sinnvolle Einheit des Entwurfs, der Implementierung und damit ” der Planung. • Sie implementiert eine oder mehrere Schnittstellen. (Hintergrund ist hier z. B. eine Komponente, die dem Administrator eine andere Sicht zeigt als dem nor” malen“ Anwender, Anm. d. Autors) • Die Implementierung ist austauschbar.“ 2.2.2 Entwurf von Komponenten Szyperski stellt sich Komponenten vor, die binär ausgeliefert werden und sofort verwendet werden können. Sie müssen konfigurierbar sein, um das Zusammenspiel mit anderen Komponenten festzulegen. Das setzt einen sogenannten deployment-Vorgang voraus, der entweder auf einem Applikationsserver stattfindet oder auch auf Betriebssystemebene geschehen kann. Siedersleben et al. hingegen setzen auf Quelltextebene an, um Softwaresysteme sauber zu strukturieren; sie zielen damit auf die Wartbarkeit von Systemen ab. In beiden Fällen sind die zentralen Fragen des komponentenorienterten Designs: • Welche Teile des Systems können geeignet in einer Komponente zusammengefasst werden? Was ist eine passende Größe (häufig findet sich hier der Begriff Granularität) für eine Komponente? Die Forderung an Komponenten, nur sehr schmale Schnittstellen und nur minimale Abhängigkeiten zu anderen Komponenten zu haben, führt – ins Extrem getrieben – entweder zu trivialen Komponenten oder zu Monolithen. 5 2 Architektur betrieblicher Informationssysteme • Wie sieht eine gute Schnittstelle für eine Komponente aus? Welche Beziehungen bestehen zu anderen Komponenten? Das beschreibt die Außensicht auf die Komponente. • Wie wird die Komponente intern aufgebaut? Das ist die Innensicht der Komponente. Während die Außensicht konstant bleibt, kann die Innensicht einer Komponente geändert oder die Implementierung ausgetauscht werden. Siedersleben et al. bringen noch einen weiteren wichtigen Aspekt mit ein: Die Variabilitätsanalyse. Sie beschreibt, für welche künftigen Änderungen die Komponente vorbereitet ist und für welche nicht. Sie belegt die immer postulierte und selten untermauerte Änderungsfreundlichkeit von Softwaresystemen. 2.2.3 Abgrenzung zur objektorientierten Software-Entwicklung Doch was unterscheidet den komponentenorientierten Ansatz vom objektorientierten Ansatz? Auch beim Entwurf von Klassen steht die Frage nach der Schnittstelle im Mittelpunkt; es geht um das Geheimnisprinzip und die Unterscheidung von Innenund Außensicht. Objektorientierung strebt Wiederverwendbarkeit über Vererbung an. Das Paradigma lautet: Nimm, was schon da ist und erbe davon, so dass es ganz genau für deine ” Zwecke einsetzbar ist.“ Dieser Ansatz führt zu relativ komplexen Frameworks und großen Klassenbibliotheken, die zwar einerseits sehr mächtig sind, andererseits aber nur schwer zu beherrschen sind, da zu viel Wissen über die internen Strukturen und Abhängigkeiten verlangt wird. Der Einarbeitungsaufwand ist in der Regel hoch. Der komponentenorientierte Ansatz stellt fest, dass Wiederverwendung deshalb zu teuer werden kann. Daher kommt zu der nicht ganz neuen Forderung nach schmalen Schnittstellen noch die nach geringen und genau definierten Abhängigkeiten von anderen Komponenten hinzu. Wie im vorherigen Abschnitt erläutert, kann diese Forderung auf binär auslieferbare oder auch auf quelltextorientierte Komponenten angewandt werden. Erst dann können Komponenten nach dem Lego-Prinzip“ zu” sammengebaut werden. Komponentenorientierter und objektorientierter Ansatz sind keine gegensätzlichen Ideen, sondern betrachten Wiederverwendung und die Zerlegung von Programmen auf unterschiedlichen Ebenen und ergänzen sich: Objektorientierung ist der Weg, Komponenten zu programmieren. 2.3 Quasar als komponentenorientierte Architektur Eines der Forschungsprojekte bei sd&m Research ist die Qualitäts-Software-Archi” tektur“ ( Quasar“). Quasar setzt dabei auf drei Ebenen an: ” 6 2.3 Quasar als komponentenorientierte Architektur 1. Quasar formuliert eine Reihe von Prinzipien, die sich bei der Architektur bewährt haben und die immer wieder eingesetzt werden können. Man könnte hier auch von Architektur-Patterns“ sprechen. ” 2. Quasar spricht von Komponenten. Der Begriff mag in mancherlei Hinsicht unscharf definiert sein – sicher ist aber, dass Komponenten über schmale Schnittstellen verfügen. Quasar definiert solche Schnittstellen für Komponenten. Wichtig ist hierbei, dass die Schnittstellen auf Langlebigkeit hin entworfen wurden und die Implementierung austauschbar bleibt. 3. Für diese Schnittstellen existieren konkrete Implementierungen, die in Projekten direkt verwendet werden können. Aus dieser Herangehensweise wird die Herkunft von Quasar deutlich: Die Ideen stammen aus der Entwicklung von großen Softwaresystemen mit langer Lebensdauer. Es fließen daher neben dem Software Engineering auch Erfahrungen aus dem Projektmanagement mit ein. 2.3.1 Softwarekategorien Dijkstra stellt die Forderung nach Separation of concerns auf [Dij76]; Parnas formuliert in [Par72] Kriterien zur Modularisierung von Programmen: Software, die sich unterschiedlich schnell ändert, wird in unterschiedliche Module aufgeteilt. In [Si01] werden diese Ideen aufgegriffen, um eine Klassifikation von Softwarebausteinen zu entwickeln. Vorteil dieser Einteilung ist: Die Prinzipien sind zwar altbekannt, aber diese sehr griffige Formulierung ist doch neu und hilfreich: Bestehender bzw. geplanter Programmcode lässt sich ganz einfach anhand dieser Kriterien überprüfen, ob er leicht oder schwer wartbar sein wird. Informationsysteme befassen sich einerseits mit der fachlichen Anwendung und bauen andererseits auf einer technischen Basis auf, denn sie können im luftleeren Raum nicht laufen. Jeder Baustein lässt sich daher einer der vier folgenden Kategorien zuordnen. Er kann sein: • unabhängig von Anwendung und Technik, • bestimmt durch die Anwendung, aber unabhängig von der Technik, • unabhängig von der Anwendung, aber bestimmt durch die Technik oder • bestimmt sowohl durch Anwendung als auch durch Technik. Anwendungsbestimmter Code kennt Begrife wie Kunde“, Auftrag“ oder Rech” ” ” nung“. Technikbestimmter Code kennt (mindestens) ein technisches API wie z. B. 7 2 Architektur betrieblicher Informationssysteme JDBC oder OCI. Abkürzend wird anwendungsbestimmter Code mit A“ bezeichnet, ” technikgetriebener Code mit T“, neutraler Code mit 0“ und Bausteine, die sowohl ” ” von Anwendung als auch von Technik bestimmt sind, mit AT“. Zusätzlich erhält man ” die Kategorie R“, die sich mit der externen Repräsentation von Objekten befasst. ” 0-Software ist ideal wiederverwendbar, für sich alleine aber ohne Nutzen. Klassenbibliotheken, die sich mit Strings und Behältern befassen (z. B. in Java die Klassen im Paket java.util), sind Beispiele für 0-Software. A-Software kann immer dann wiederverwendet werden, wenn vorhandene Anwendungslogik ganz oder teilweise benötigt wird. T-Software kann immer dann wiederverwendet werden, wenn ein neues System dieselbe technische Komponente einsetzt (JDBC, ODBC, MFC, CICS). AT-Software befasst sich mit Technik und Anwendung zugleich. Sie ist schwer zu warten, widersetzt sich Änderungen, kann kaum wiederverwendet werden und ist daher zu vermeiden, es sei denn, es handelt sich um R-Software. R-Software ist eine milde Art von AT-Software. Sie befasst sich mit der Transformation zwischen fachlichen Objekten und externen Repräsentationen (Zeilen einer Datenbank-Tabelle, ein Bildschirmformat, XML). R-Software kann z. B. mit Hilfe von awk - oder perl -Skripts generiert werden. Bei der Kombination von Software-Bausteinen gelten einfache Regeln: 0-Software kann ohne Einfluss auf die Kategorie zu A oder T hinzugefügt werden. Kombination von A mit A liefert A, und T mit T liefert T; A mit T liefert AT oder im günstigsten Fall R, wenn die Software nur zur Repräsentation dient und stereotypen Charakter hat. Die Entscheidung, welcher Code als T-Software und welcher – ähnlich der gewählten Programmiersprache – als Basisfunktionalität und damit 0-Software behandelt werden sollte, ist häufig nur im praktischen Kontext zu entscheiden. So kann z. B. eine Schnittstelle, die einen allgemeinen Zugang zu einer Datenbankverwaltung ermöglicht, als T-Software betrachtet werden, weil sie von datenbanktechnischen Begriffen wie Transaktionen und Operationen wie Insert/Delete/Update spricht. Sie kann aber auch als 0-Software bezeichnet werden, da sie die spezielle Datenbank-Technik verbirgt; wichtig ist dabei die Austauschbarkeit der Implementierung. In der Praxis gilt: Jede Basis-Software, die potentiell bei einer späteren Portierung oder Migration nicht mehr zur Verfügung steht oder ersetzt werden soll, sollte gekapselt werden. Ein weiteres Argument für eine Kapselung technischer APIs ist eine Vereinfachung der Nutzungsschnittstelle. Eine Kapselung hinter einer abgespeckten Schnittstelle ermöglicht ggf. eine normierte Nutzung und eine geringere Einarbeitungszeit als die direkte Nutzung einer komplexen T-Software. 8 2.3 Quasar als komponentenorientierte Architektur 2.3.2 Elemente von Quasar Wie zu Beginn dieses Abschnittes erwähnt, zeigt Quasar, wie eine Komponentenbildung mit den Zielen schmale, aber aussagekräftige Schnittstellen und saubere Kapselung von technischen Abhängigkeiten aussehen kann. Nach diesen Prinzipien wurde das Quasar Database Interface, QDI entwickelt. Es existieren einerseits die Schnittstelle und andererseits zwei Implementierungen (eine Dummy-Implementierung und eine auf JDBC aufbauende, für Oracle-, für MySQLund für ODBC-Datenbanken einsetzbare). Die Schnittstelle beschreibt ein sogenanntes Workspace-Konzept, mit dem transaktionsorientiert jeweils mit einer Menge von Objekten gearbeitet werden kann, und das zu jedem Zeitpunkt eine konsistente Sicht auf die verwalteten Objekte ermöglicht. Das QDI wird in Abschnitt 5.2.2 genauer beschrieben. Daneben existieren noch Ideen und Implementierungen für GUI-Darstellungen (QUI, Quasar User Interface) und eine Sammlung nützlicher Datentypen (QDT, Quasar Data Types). 2.3.3 Kritik/Einordnung Um die Quasar-Ideen nutzbringend einsetzen zu können, ist eine kritische Betrachtung notwendig: Quasar ist kein Framework – diese Feststellung ist wertfrei, es ist weder ein Vornoch ein Nachteil. Gamma2 kommentiert zum Thema Frameworks: Frameworks sind ” gut, solange man nicht gegen sie programmiert. Wenn man gegen ein Framework programmiert, wird man diesen Kampf verlieren.“ Der in Quasar verwendete Komponentenbegriff arbeitet auf Quelltextebene und ist nützlich, um eine wohlstrukturierte Implementierung zu erstellen, die den Forderungen nach Wartbarkeit durch A/T-Trennung und durch schmale Schnittstellen sowie durch Austauschbarkeit von Implementierungen gerecht wird. Er entspricht aber nicht den Ideen, die z. B. Szyperski in [Szy99] zugrunde legt (s. Abschnitt 2.2): Quasar-Komponenten sind nicht binär auslieferbar und nicht konfigurierbar. Sie haben (bewusst) keinen Produkt-Charakter; vielmehr zielen sie darauf ab, für Projekte die richtigen Komponentenschnitte vorzuschlagen. Die Implementierung des QDI ist zur Zeit am weitesten gediehen und wird auch in produktiven Projekten eingesetzt. Dennoch wird es nicht im Sinne eines Produktes oder vollständigen Werkzeuges gepflegt, sondern als ausgereifter proof of concept betrachtet. 2 auf der sd&m-Konferenz 2001 in Bonn 9 2 Architektur betrieblicher Informationssysteme 2.4 Aufbau einer Standard-Architektur betrieblicher Informationssysteme Abbildung 2.1 stellt die Komponenten eines betrieblichen Informationssystems dar, die von der Spezifikation motiviert werden. Sie sind also nur durch die fachlichen Anforderungen bestimmt und unabhängig von der verwendeten Technik, sei es in Bezug auf die Darstellung in der GUI, die zugrundeliegende Netztopologie oder die eingesetzte Datenbank. Abbildung 2.1: Grobarchitektur eines betrieblichen Informationssystems 2.4.1 Anwendungs-Komponenten In Abbildung 2.1 sind drei Anwendungs-Komponenten (A-Komponenten) dargestellt: Das Bestellwesen, die Kundenverwaltung und die Auftragsverwaltung. Sie bestehen jeweils aus Anwendungs-Fällen, Anwendungs-Entitäten und Anwendungs-EntitätenVerwaltern, die im Folgenden beschrieben werden. 2.4.2 Anwendungs-Fälle Anwendungs-Fälle (A-Fälle, AWF) sind die Use Cases von UML. Sie legen fest, welche Operationen der Anwendungskern zur Verfügung stellt. A-Fälle werden im Dialog, im Batch und von Nachbarsystemen gerufen. In Abbildung 2.1 sind das die A-Fälle Bestellung bearbeiten, Bestellung aufnehmen, Kundenliste bearbeiten, Kunde suchen und bearbeiten, Auftrag anlegen und bearbeiten. 10 2.4 Aufbau einer Standard-Architektur betrieblicher Informationssysteme A-Fälle als Schnittstelle der A-Komponente Die A-Fälle sind der Zugang zur Anwendung. Die Gesamtmenge der A-Fälle einer A-Komponente definieren, was die Komponente leistet, und zwar ohne Bezug auf den konkreten Nutzer: Dem A-Fall ist es egal, ob seine Daten am Bildschirm angezeigt oder in ein Nachbarsystem übertragen werden. Nur so ist gewährleistet, dass ein und derselbe A-Fall im Batch und in verschiedenen Dialogen verwendbar ist. A-Fälle garantieren den sachgemäßen Zugriff auf A-Entitäten und A-Verwalter. So können z. B. Vorbedingungen geprüft werden, bevor bestimmte Operationen auf AEntitäten durchgeführt werden. Wenn solche Prüfungen nichttrivial sind, ist es wichtig, sie an genau einer Stelle auszuprogrammieren, und dafür sind A-Fälle der richtige Ort. 2.4.3 Anwendungs-Entitäten Anwendungs-Entitäten (A-Entitäten, AE) sind die Pfeiler, an denen eine Anwendung hochgezogen wird: Im Beispiel sind das Kunde und Auftrag (vgl. [BrSi00, S. 137ff]). A-Entitäten werden beschrieben durch Attribute, wobei jedes Attribut entweder elementar ist (String, Integer, ..) oder zu einem fachlichen Datentyp (A-Datentyp) gehört. A-Entitäten besitzen in der Regel die üblichen get- und set-Operationen sowie Instanz-Operationen wie z. B. kunde.addiereUmsätze oder auftrag.stornieren. Sie haben einen Lebenszyklus, der in der Regel durch ein Zustandsmodell beschrieben wird. Die UML-Begriffe Assoziation und Aggregation gelten für A-Entitäten genauso. A-Entitäten sind persistent. Sie können in einer oder mehreren Datenbanken, Nachbarsystemen oder auch flachen Dateien liegen. Der Entwurf von A-Entitäten wird weder von Klassenstrukturen noch von Datenbankmodellen geleitet, sondern ist rein fachlich motiviert. Sie entstehen mit der fachlichen Spezifikation, noch bevor das technische Konzept festgelegt wird. Verwandte Begriffe Die Begriffe Business Object und Geschäftsobjekt bedeuten je nach Autor mehr oder weniger dasselbe wie A-Entität. Im Interesse einer einheitlichen Begriffsführung wird in diesem Dokument der Begriff A-Entität verwendet. 2.4.4 Anwendungs-Entitäten-Verwalter Operationen, die man keiner einzelnen Instanz zuordnen kann, werden VerwalterOperationen genannt. Die Anwendungs-Entitäten-Verwalter (A-Verwalter, AV) implementieren diese Operationen. Jeder verwaltet mindestens eine A-Entität, aber oft 11 2 Architektur betrieblicher Informationssysteme macht es Sinn, einen A-Verwalter für die ganze A-Komponente vorzusehen. Beispiele für Verwalter-Operationen sind z. B.: • Operationen zum Erzeugen von Instanzen, • Operationen, die alle Instanzen einer A-Entität betreffen, • Suche nach unterschiedlichen Kriterien (Abfragen, Queries) Dazu braucht man in der Regel Verwalter-Daten, z. B.: • Anzahl der vorhandenen Instanzen • Summen von Attributwerten • Konstanten, die für alle Instanzen gelten (z. B. Default-Werte). A-Verwalter haben die Aufgabe, Abfragen an die Datenbank anzustoßen. Die Umsetzung der Anfragen erfolgt in der Regel in einer Zugriffsschicht. Datenbankabfragen stellen oft einen Flaschenhals bezüglich der Performance einer Anwendung dar; AVerwalter übernehmen den Teil der Abfrage-Optimierung, der nur unter Kenntnis der fachlichen Anforderung und der fachlichen Semantik durchgeführt werden kann. Hier ist als Erstes die Entscheidung zwischen lazy und eager loading anzuführen. 2.5 Zusammenfassung Dieses Kapitel hat auf einer sehr allgemeinem Ebene betriebliche Informationssysteme charakterisiert und eine Architektur vorgestellt, die sich an den fachlichen Anforderungen orientiert. Es hat einen Komponentenbegriff eingeführt, der in den folgenden Kapiteln verwendet wird. Ein betriebliches Informationssystem benötigt neben den anwendungsorientierten Elementen eine technische Grundlage, um Datenbankzugriff, Anbindung an Nachbarsysteme und Implementierung der GUI zu realisieren. Mit der Umsetzung dieser allgemeinen Architektur mit Hilfe eines EJB-Applikationsservers beschäftigen sich die folgenden Kapitel. 12 3 Standardprobleme beim Datenbankzugriff Betriebliche Informationssysteme, auch wenn sie individuell für die speziellen Anforderungen eines Kunden entworfen werden, müssen sich mit Problemen beschäftigen, die immer wieder in ähnlicher Form auftreten. Der Zugriff auf Datenbanken ist ein solches Problem. Bei der Verbindung einer objektorientiert programmierten Anwendung mit einer relationalen Datenbank ist der sogenannte objekt-relationale Paradigmenbruch (impedence mismatch) zu überwinden: Unter anderem müssen folgende Fragen beantwortet werden: Wie werden die Daten von der objektorientierten Darstellung ins relationale Datenbankschema abgebildet? Und wie werden Änderungen im Objektgeflecht (Transaktionen auf Ebene der Anwendungslogik) auf Datenbanktransaktionen abgebildet? 3.1 Objekt-relationale Abbildung Der sogenannten objekt-relationalen Abbildung (OR-Mapping) kommt eine größere Bedeutung zu als die bloße Abbildung von Klassen auf Tabellen und von Attributen auf Spalten. Die Objektidentität, die korrekte Abbildung von Beziehungen zwischen Objekten und die Vererbung seien hier als wichtigste Schwierigkeiten genannt. 3.1.1 Abbildung von Attributen Attribute mit Elementardatentypen (Zahlen, Strings, Datum- oder Zeitangaben) werden direkt oder mit einfacher Datentyp-Umwandlung auf Spalten abgebildet. Doch für abhängige Objekte mit komplexeren Attribut-Datentypen gibt es schon mehrere Möglichkeiten. Meist werden sie, mit Fremdschlüssel versehen, in eine eigene Tabelle geschrieben. Man könnte sie auch en bloc als BLOB“ (binary large object) in ei” ne Tabellenspalte schreiben oder – bei bekannter Anzahl – die vorhandene Tabelle erweitern; solche Lösungen sind aber eher als Notnagel“ zu bezeichnen. Sie werden ” hier dennoch erwähnt, weil sie in der Praxis überraschend häufig angetroffen werden. 13 3 Standardprobleme beim Datenbankzugriff 3.1.2 Abbildung von Beziehungen zwischen Objekten Es ist zu unterscheiden zwischen 1:1, 1:n und n:m-Beziehungen. Bei 1:n und m:nBeziehungen kann die Kardinalität entweder variabel oder fest sein. Weiterhin ist zwischen Komposition und Assoziation zu unterscheiden. Bei Komposition sind die enthaltenen Objekte abhängig; z. B. zieht das Löschen eines Objektes das Löschen der abhängigen Objekte nach sich. Assoziierte Objekte können unabhängig voneinander existieren. Typischer Fall für eine 1:n-Beziehung ist ein Objekt, das eine Reihe von abhängigen Objekten enthält, z. B. in Form eines Containers. Die Abbildung auf die Datenbank wird meist mit zwei Tabellen arbeiten, wobei die Tabelle für die abhängigen Objekte einen Fremdschlüssel enthält, um auf den entsprechenden Datensatz in der ersten Tabelle zu verweisen. Man beachte, dass in diesem Fall die Verweisrichtung“ umgekehrt ” zum OO-Modell ist. 3.1.3 Objektidentität Es wird auf drei Ebenen von Identität gesprochen: Die fachliche Ebene, die Sicht des OO-Modells sowie die Darstellung im relationalen Datenmodell. Die Wahrung der Identität muss konsistent durch alle drei Ebenen erfolgen. Die fachliche Identität ist in der Regel durch fachliche Attribute gegeben. Ein Objekt hat als eindeutige Identität die Referenz. Die Identität ist von den Attributwerten unabhängig; sie bleibt erhalten, auch wenn sich die Attributwerte ändern. In einer relationalen Datenbank wird die Identität durch das Konzept der Primärschlüssel gesichert. Eine Tabellenzeile wird durch den Primärschlüssel, der aus einer oder mehreren Spalten gebildet wird, eindeutig gekennzeichnet. Für die Wahl des Primärschlüssels in der Datenbank und die Objekt-Identität im OOModell gibt es die Möglichkeit, entweder die Anwendungsdaten oder einen künstlich erzeugten Primärschlüssel zu verwenden. Die erstgenannte Variante hat den Vorteil, dass keine zusätzlichen Daten eingeführt werden. Die zweite ist jedoch meist einfacher zu handhaben, z. B. wenn sich der fachliche Schlüssel über mehrere Spalten erstreckt oder sich im Laufe der Zeit womöglich die Anforderungen an das System ändern. 3.1.4 Vererbung Relationale Datenbanken kennen das Konzept der Vererbung nicht. Betrachten wir das Beispiel in Abbildung 3.1: Eine Person kann in den Ausprägungen Angestellter oder Kunde auftreten. Die Klasse für Personen enthält einige Attribute, die Angestellten und Kunden gemein sind, und die abgeleiteten Klassen für Angestellte und Kunden enthalten weitere, spezielle Attribute. Für die Abbildung auf eine Tabellenstruktur gibt es drei Möglichkeiten: 14 3.1 Objekt-relationale Abbildung 1. Ein-Vererbungsbaum-eine-Tabelle: Eine Tabelle für Personen enthält die Attribute für Personen, die zusätzlichen Attribute für Angestellte und für Kunden sowie ein Typfeld zur Unterscheidung zwischen Angestellten und Kunden. 2. Ein-Vererbungspfad-eine-Tabelle: Für jede konkrete Klasse existiert eine Tabelle. Die Attribute der abstrakten Klasse Person werden sowohl in der Tabelle für Angestellte als auch in der für Kunden gespeichert. 3. Eine-Klasse-eine-Tabelle: Für jede Klasse existiert eine Tabelle, die jeweils die dort vereinbarten Attribute aufnimmt. Die Tabellen für die Oberklasse enthalten außerdem ein Typfeld, die Tabellen für die Subklassen enthalten jeweils den Primärschlüssel und die zusätzlichen Attribute. Abbildung 3.1: Abbildung von Vererbung Beim Entwurf einer Tabellenstruktur müssen die zum Teil gegensätzlichen Ziele • Speicherplatzoptimierung, • Geschwindigkeit der Datenbankabfragen, • Zahl der benötigten Datenbankzugriffe zum Aufbau eines Objektes, • Einfachheit der Tabellenstruktur bzw. möglichst klare Abbildung des modellierten Realweltausschnitts und • Einfachheit der Abfragen 15 3 Standardprobleme beim Datenbankzugriff gegeneinander abgewägt werden. Jede dieser drei Varianten hat Vor- und Nachteile bezüglich dieser Ziele. Mit diesem Handwerkszeug“ können auch komplexe Strukturen bzw. Objektgeflechte ” abgebildet werden. 3.2 Transaktionen Um Inkonsistenzen beim parallelen Zugriff zweier Clients auf A-Entitäten zu vermeiden, werden Zugriffe in Transaktionen durchgeführt. Ziel ist die Isolation eines Vorganges mit definiertem Anfangs- und Endpunkt, der entweder komplett oder gar nicht ausgeführt wird, um jeweils einen konsistenten Datenstand zu erhalten. Man nennt dies die ACID-Eigenschaft einer Transaktion: • Atomicity: Sie ist Atomar, also nicht mehr teilbar. Entweder alle Aktionen oder keine werden ausgeführt. • Consistency: Vor Beginn und nach Abschluss sind die Daten in einem konsistenten Zustand. • Isolation: Transaktionen laufen unabhängig oder isoliert voneinander ab. • Durability: Nach erfolgreichem Abschluss einer Transaktion sind die Änderungen im Datenbestand dauerhaft. Eine Transaktion wird entweder mit rollback abgebrochen, d. h. dass alle Änderungen, die innerhalb der Transaktion angestoßen wurde, verworfen werden, oder sie wird mit commit abgeschlossen mit dem Ziel, alle Änderungen dauerhaft zu übernehmen. Je nach Transaktionsstrategie kann dies immer durchgesetzt werden oder auch wegen einer schneller abgeschlossenen Änderung einer Transaktion eines anderen Clients scheitern. 3.2.1 Transaktionsstrategien Zur Implementierung von Transaktionen werden Transaktionsstrategien verwendet. Diese verwenden Sperren, sie werden daher auch als Sperrstrategien bezeichnet. Transaktionsstrategien können auf zwei Wegen implementiert werden: pessimistisch oder optimistisch [Sla99, Kapitel 13]. 16 3.2 Transaktionen Pessimistische Transaktionsstrategie Die pessimistische Transaktionsstrategie sperrt während einer Transaktion alle Daten, auf die lesend oder schreibend zugegriffen wird. Die Sperren bestehen bis zum Transaktionsende. Will eine zweite Transaktion auf die selben Daten zugreifen, wird sie blockiert. Erst wenn die erste Transaktion ihre Daten geschrieben hat, kann die zweite Transaktion die Daten lesen. Die pessimistische Transaktionsstrategie wird in der Regel mit Sperren auf Datenbankebene implementiert. Optimistische Transaktionsstrategie Die optimistische Strategie arbeitet ohne Sperren. Sie erkennt Konflikte über eine Validierungsphase. Die erste Transaktion, die Daten schreibt, gewinnt. Transaktionen, die danach die selben Daten schreiben wollen, werden abgebrochen (sie müssen dann die Daten erneut lesen und ändern). Die optimistische Transaktionsstrategie arbeitet in drei Phasen. Die letzten beiden Phasen fallen häufig zusammen: 1. Lesephase: Daten werden geladen, ohne sie zu sperren. Der Zugriff erfolgt nur lesend. Diese Daten liegen im Applikationsserver oder Client vor und können dort geändert werden. 2. Validierungsphase: Vor dem Schreiben der Daten werden potentielle Konflikte mit anderen Transaktionen festgestellt. Dazu wird eine darunter liegende Transaktion gestartet, und die Daten werden erneut geladen. Über einen Vergleich der Änderungen mit den geladenen Daten werden Konflikte erkannt. Bei einem Konflikt wird die Transaktion mit einem Rollback abgebrochen. 3. Schreibphase: In der Validierungsphase wurden keine Konflikte erkannt, die darunter liegende Transaktion ist noch offen. Alle Daten stehen im aktuellen Zustand zur Verfügung, die Änderungen werden geschrieben und die darunter liegende Transaktion wird mit commit bestätigt. Die darunter liegende Transaktion kann ihrerseits wieder optimistisch oder pessimistisch arbeiten. Auswirkungen auf den Entwurf der Anwendung Transaktionssstrategien sind ein weiteres Beispiel dafür, dass zwar die Eigenschaften der Datenbank verborgen bleiben, nicht jedoch das Problem des parallelen Zugriffs an sich: Bei Verwendung der pessimistischen Strategie muss der Anwendungsprogrammierer damit rechnen, dass seine Anwendung langsamere Antwortzeiten bietet oder sogar Verklemmungen (Deadlocks) verursachen kann. Setzt er hingegen die optimistische Strategie ein, kann es sein, dass eine Transaktion nicht mit commit abgeschlossen 17 3 Standardprobleme beim Datenbankzugriff werden kann. Damit muss die Anwendung umgehen können. Der Anwendungsprogrammierer wird zwar von der Programmierung der Transaktionsproblematik entlastet, er kommt aber nicht umhin, mit den Begriffen optimistische und pessimistische Sperrstrategie umgehen zu können. 3.2.2 Abbildung von Transaktionen auf die Benutzer-Interaktion Transaktionen tauchen auf jeder Ebene eines Informationssystem auf und müssen aufeinander abgebildet werden: Für den Benutzer ist es beispielsweise eine Transaktion, ein Dialogfenster am Bildschirm zu öffnen und die eingetragenen Daten mit dem OK-Knopf zu bestätigen. Auf den verschiedenen Ebenen werden unterschiedliche Transaktionsbegriffe verwendet. Für die Gestaltung der Schnittstelle eines A-Falles können vier Fälle unterschieden werden: Die Einschritt-Transaktion Ein Dialog kann eine A-Transaktion mit genau einem Aufruf an den A-Fall durchführen. Siehe Abbildung 3.2. Abbildung 3.2: Einschritt-Transaktion Die Mehrschritt-Transaktion ohne Benutzereingabe Ein Dialog kann eine A-Transaktionen nur über mehrere Aufrufe des A-Falls durchführen. Zwischen den Aufrufen findet keine Benutzerinteraktion statt. Die Aufrufe erfolgen also kurz hintereinander. Abbildung 3.3 stellt dies dar. Die Mehrschritt-Transaktion mit Benutzereingabe In komplexeren Dialogen wird, um eine A-Transaktion auszuführen, der A-Fall mehrfach aufgerufen (siehe Abbildung 3.4). Zwischen den Aufrufen findet eine Benut- 18 3.2 Transaktionen Abbildung 3.3: Mehrschritt-Transaktion ohne Benutzereingabe zereingabe statt. Diese kann beliebig lange dauern. Diese Transaktionen werden Mehrschritt-Transaktionen mit Benutzereingabe genannt. Der Benutzer bleibt aber am Client angemeldet und lässt den selben Dialog offen. Abbildung 3.4: Mehrschritt-Transaktion mit Benutzereingabe Die Lange Transaktion A-Transaktionen, die sich über mehrere Sitzungen des Benutzers erstrecken, werden lange Transaktionen genannt. Sie können beliebig lange dauern und überdauern auch den Absturz eines Clients und eines Servers. Sie sind in der Regel persistent. Siehe Abbildung 3.5. 3.2.3 Implementierung der fachlichen Transaktionen Für die Umsetzung der fachlichen Transaktion (A-Transaktion) auf die Datenbanktransaktion kann die optimistische oder die pessimistische Strategie gewählt werden. 19 3 Standardprobleme beim Datenbankzugriff Abbildung 3.5: Mehrschritt-Transaktion mit Benutzereingabe Prinzipiell sind beide Strategien für alle vier vorgestellten Fälle möglich. Allerdings ist – je nach Anwendung – nicht jede Kombination sinnvoll, denn der Einsatz der pessimistischen Strategie führt durch das restriktive Sperrverhalten leicht zu Performanzengpässen, wenn nicht sogar zu Verklemmungen. Andererseits kann sie bei kurzen Transaktionen sogar performanter sein, da commit-Aufrufe nicht scheitern können und Transaktionen serialisiert werden. Die Abbildung der fachlichen Transaktion einerseits zum Benutzer hin und andererseits zur Datenbank hin ist zwar techniscch unabhängig, dennoch empfiehlt sich für die Praxis, die Wahl der Transaktionsstrategie auf die Abbildung zum Client hin abzustimmen. 3.3 Performanz Die Performanz einer Anwendung hängt von der Zahl der Datenbankzugriffe und von der Menge der gelesenen Daten ab. Es macht einen großen Unterschied, ob viele Objekte auf einmal gelesen werden können oder ob sie mit vielen separaten Leseoperationen geladen werden. Optimierungsziel ist, die Zahl der Datenbanaufrufe zu minimieren sowie die pro Aufruf transportierten Daten zu verringern. Falls sich diese Ziele widersprechen, ist ein geeigneter Kompromiss zu finden oder ein Mechanismus zu entwickeln, mit dem im Einzelfall die Entscheidung getroffen werden kann. Eine Auswahl von wichtigen Problemstellungen ist: • Beim Zugriff auf Objektgeflechte ist zwischen lazy loading“ und eager loading“ ” ” zu entscheiden. • Datenbank-Updates sollen nur dann ausgeführt werden, wenn die Daten tatsächlich geändert wurden. 20 3.4 Wartung • Die Entscheidung zwischen optimistischer und pessimistischer Sperrstrategie ist vor allem bei hoher Clientlast für die Performanz und das Verklemmungsverhalten von großer Bedeutung. • Die Entscheidung, ob der Applikationsserver zustandslos oder zustandsbehaftet arbeitet, hat auch Einfluss auf die Abbildung der Transaktionen, da ein zustandsloser Applikationsserver nach jedem Aufruf durch einen Client die Transaktion abschließt. Ein zustandsbehafteter Server kann eine Transaktion bis zum nächsten Aufruf durch den selben Client offen halten. • In die selbe Richtung zielt auch die Frage, was mit Anfragen mit einer hohen Trefferzahl geschehen soll: Werden alle Treffer an den Client übermittelt? Wenn die Ergebnisse hingegen portionsweise abgefragt werden, gibt es zur Realisierung auf Serverseite mehrere Möglichkeiten zur Realisierung, die je nach Einsatzgebiet abzuwägen sind. • Gleiches trifft auf Client-Anfragen zu, die einen umfangreichen Vorgang anstoßen und asynchron zurückkehren sollen, wie z. B. die Erstellung von großen Reports. 3.4 Wartung Ein Informationssystem mit einer langen Lebensdauer wird immer wieder Änderungen ausgesetzt sein. Mal ändern sich fachliche Anforderungen, mal ändern sich technische Gegebenheiten wie z. B. die Anbindung an Nachbarsysteme. Auch wenn versucht wird, das Datenbankschema möglichst selten zu ändern, ist es manchmal unumgänglich. Ebenso können sich notwendige Änderungen an der Klassenstruktur des Anwendungskerns ergeben. Die Frage ist, was nach einer solchen Änderung passiert: Muss nach einer Änderung des Datenbankschemas der gesamte Anwendungskern neu geschrieben werden, oder reicht eine Anpassung in der sauber gekapselten Zugriffsschicht? Auch die andere Richtung muss geprüft werden: Ist das Datenbankschema unabhängig genug vom verwendeten Klassenmodell? Parnas et al. [PCW83] stellen bezüglich der Änderungsfreundlichkeit von Software die Forderung: It should be possible to make likely changes without changing any ” module interfaces; less likely changes may involve interface changes, but only for modules that are small and not widely used. Only very unlikely changes should require changes in the interfaces of widely used modules.“ Die Erfüllung dieser Forderung kann nur durch die Architektur des Systems sichergestellt werden. Parnas et al. zeigen, dass das Geheimnisprinzip in diesem Sinne der Wartbarkeit von Software dient. 21 4 Enterprise Java Beans Die Programmiersprache Java wurde 1995 von Sun Microsystems vorgestellt und hat seitdem große Verbreitung erlangt. Sie verdankt ihre Popularität einerseits der leichten Erlernbarkeit und andererseits den zahlreichen verfügbaren Programmierschnittstellen (Application Programmer Interfaces, APIs), die aus der reinen Programmiersprache einen regelrechte Programmierplattform machen. Durch den Einsatz der sogenannten virtuellen Maschine (VM) bleiben Java-Programme unabhängig von der eingesetzten Hardware, und mit Hilfe der APIs bleiben sie auch unabhängig vom verwendeten Betriebssystem. Ursprünglich wurde Java bekannt durch die Programmierung von Applets, die in einem Web-Browser ablaufen können, sich also auf der Client-Seite befinden. Mittlerweile hat aber Java auch auf der Server-Seite verteilter Anwendungen Einzug gehalten. Hier spielen vor allem Servlets (bzw. Java Server Pages, JSP) und Enterprise Java Beans (EJB) eine Schlüsselrolle und setzen sich gegenüber beispielsweise C++Anwendungen immer mehr durch. Dieses Kapitel beschreibt zunächst die EJB-Spezifikation und die Systemarchitektur, die sich daraus ergibt. Daran schließt sich eine kritische Bewertung unter den im vorigen Kapitel eingeführten Kriterien für die Architektur von Informationssystemen an. Das Kapitel schließt mit einem Ausblick auf die kommende EJB-Version. Vergleiche zur Darstellung in diesem Kapitel auch [Sch01] und [DePe00]. 4.1 Die EJB-Spezifikation Enterprise Java Beans (EJB) ist ein Bestandteil der Java-2-Plattform, Enterprise Edition (J2EE), welche die Grundlage für serverseitige Anwendungslogik darstellt. Es handelt sich dabei nicht um ein Produkt, sondern um eine Spezifikation [Sun99], die frei verfügbar ist. Sie beschreibt den Aufbau einer 3-Schichten-Architektur (3-tier architecture) (siehe Abbildung 4.1.) Für die mittlere Schicht spezifiziert sie einen Applikationsserver, welcher eine Ablaufumgebung für Anwendungs-Komponenten bietet. Die Spezifikation legt fest, wie die Schnittstellen zwischen den Komponenten untereinander und zwischen Komponenten und dem Applikationsserver aussehen sowie die Leistungen und Dienste, die der Applikationsserver zur Verfügung stellen muss. 22 4.2 Infrastruktur und Produkte Abbildung 4.1: 3-Schicht-Architektur mit Applikations-Server So wird ein Komponentenmodell definiert, das sich Folgendes zum Ziel setzt: • Anwendungsentwickler benötigen weniger Know-How bezüglich systemnaher Dienste (z. B. Multithreading, Transaktionssteuerung etc.) • Unabhängigkeit von der Datenbank • Damit höhere Produktivität der Anwendungsentwickler Die EJB-Spezifikation greift ein weiteres Thema auf, das mit der Produktivitiät im Entwicklungsprozess zusammenhängt. Für die verschiedenen Aufgaben wie Entwicklung der fachlichen Logik, Zusammensetzen der erstellten Komponenten, Wartung der Basissysteme usw. werden verschiedene Rollen definiert, z. B. der Bean-Provider für die Erstellung der Enterprise Beans und der Server-/Container-Provider, der den Applikationsserver herstellt, in dieser Arbeit oft mit Hersteller“ bezeichnet. Für eine ” vollständige Auflistung siehe [Sun99] oder [DePe00, Kapitel 4]. 4.2 Infrastruktur und Produkte Zur EJB-Architektur gehören der J2EE-Server, der EJB-Container sowie die Beans. Der Container stellt dabei die Laufzeitumgebung und eine Reihe von Diensten für die Enterprise Beans dar; der Server wiederum bietet eine Laufzeitumgebung für Container. In der Regel läuft in jedem Server neben dem EJB-Container noch ein Container für Servlets/JSPs. Die Spezifikation unterscheidet zwar zwischen Server und Container, definiert aber keine Schnittstelle zwischen ihnen. Es wird vermutlich angenommen, dass Server und Container immer vom selben Hersteller geliefert werden. Dies stimmt nur zum Teil (s. Abschnitt 4.4.2). Dienste des Servers umfassen u. a.: 23 4 Enterprise Java Beans • das Thread- und Prozessmanagement • Unterstützung für Lastverteilung und Ausfallsicherheit • Namens- und Verzeichnisdienst (JNDI-Service) • Pooling von Betriebssystemressourcen Der EJB-Container ist verantwortlich für: • die Kontrolle des Lebenszyklus’ einer Bean • Instanzen-Pooling und Aktivierung bzw. Passivierung von Beans • Verteilung • Persistenz • Transaktionen • Sicherheit 4.3 Enterprise-Beans Während Server und Container sämtliche technischen“ Aufgaben übernehmen sol” len, sind die Enterprise Beans dazu gedacht, sich vollständig auf die Anwendungslogik zu konzentrieren. Diese fachliche Logik wird unterteilt in Anwendungsfälle und intelligente Anwendungsobjekte. (In der UML-Sprechweise werden häufig die Begriffe Geschäftsvorfälle bzw. Geschäftsobjekte verwendet.) Es gibt zwei Typen von Enterprise Java Beans1 : Session-Beans modellieren die Anwendungsfälle, und Entity-Beans modellieren die Anwendungsobjekte. Daraus ergeben sich entscheidende Unterschiede zwischen den beiden Bean-Arten in Bezug auf Einsatzgebiete und auf Eigenschaften. Sie werden in Tabelle 4.1 kurz dargestellt und nachfolgend genauer erklärt. 4.3.1 Aufbau einer Bean Eine einzelne Enterprise-Bean setzt sich aus mehreren Elementen zusammen, die zu einem Teil vom Bean-Entwickler geschrieben und zum anderen Teil beim Installations-Vorgang (Deployment) vom Container (oder Container-eigenen Tools) generiert werden. Die Erstellung der folgenden Bestandteile muss dabei der Entwickler übernehmen: 1 ab Version 2.0 gibt es auch noch Message Driven Beans. Siehe dazu Abschnitt 4.5. 24 4.3 Enterprise-Beans Manipuliert Daten Repräsentiert Daten / ist persistent Zustandsbehaftet Zugriff von x Clients Kennt Transaktionen Lebensdauer Überlebt Servercrash Was tun nach Servercrash? Session-Bean Ja Nein Entity-Bean Ja Ja Ja / Nein 1 Ja Eher kurz Ja n Ja Kann lang sein (wie Daten in DB) Ja + impliziter Rollback Client erhält Exception beim nächsten Aufruf Nein Client baut neue Verbindug auf Tabelle 4.1: Wichtige Charakteristika der Enterprise-Beans • ein Remote-Interface 2 , • ein Home-Interface, • eine Implementierungsklasse (Bean-Klasse) und • ein Deployment-Deskriptor ; • eine Primärschlüsselklasse für Entity-Beans ist optional möglich. Die ersten vier Teile sind für Session- und für Entity-Beans erforderlich. Eine Primärschlüsselklasse muss nur für Entity-Beans erstellt werden, deren Primärschlüssel aus mehreren Attributen zusammengesetzt ist. Zum Deployment werden die Bestandteile einer Enterprise-Bean in einer jar-Archivdatei zusammengefasst. Diese Aufgabe kann auch mit Hilfe von Deployment-Tools erledigt werden, die auch bei der Erstellung des Deployment-Deskriptors helfen können. Remote-Interface Im Remote-Interface werden alle Methoden der Anwendungslogik aufgeführt, die von der Enterprise-Bean nach außen hin angeboten werden. Das Remote-Interface wird von einer vom Container (bzw. einem Tool) generierten Klasse implementiert, welche über eine RMI-Verbindung3 mit dem J2EE-Server kommuniziert und die Methodenaufrufe weitergibt. Auf Serverseite werden die RMI-Aufrufe von einer ebenfalls generierten Klasse entgegengenommen, die die entsprechenden Methoden der BeanImplementierung aufruft. 2 3 Ab Version 2.0 allgemein Komponenten-Interface Remote Method Invocation, s. die einschlägige Literatur, z. B. [Öb01] 25 4 Enterprise Java Beans Home-Interface Das Home-Interface definiert Methoden, um Zugriff auf Beans zu erlangen. Für Session-Beans sind das nur create-Methoden. Zustandslose Session-Beans enthalten im Home-Interface genau eine parameterlose create-Methode; zustandsbehaftete Session-Beans können auch mit Parametern erzeugt werden und daher auch mehrere create-Methoden enthalten. Entity-Beans hingegen haben einen persistenten Zustand und repräsentieren typischerweise Objekte, die in einer Datenbank abgelegt sind. Sie enthalten daher noch Methoden, die der Client zum Erzeugen, Suchen und Löschen eines Beans verwendet. Es werden create-Methoden definiert, deren Signaturen mit jeweils einer ejbCreate- und einer ejbPostCreate-Methode in der Bean-Klasse übereinstimmen müssen (siehe (B) in Abbildung 4.2). Zum Suchen nach Objekten werden find-Methoden definiert. Die meisten Container bieten die Möglichkeit, für diese find-Methoden die entsprechenden Suchanfragen an die Datenbank im Deployment-Deskriptor zu hinterlegen; daraus werden die Abfragen konstruiert. Das Home-Interface wird ebenfalls von einer vom Container generierten Klasse implementiert. Bean-Klasse und Primärschlüssel Die Bean-Klasse implementiert eines der beiden Java-Interfaces javax.ejb.SessionBean oder javax.ejb.EntityBean. Damit werden Methoden vorgegeben, die hier implementiert werden müssen. Es handelt sich um sog. Callback-Methoden (siehe (C) in Abbildung 4.2), die vom Container aufgerufen werden, um die Bean über Zustandswechsel im Lebenszyklus zu informieren. Daneben implementiert die Bean-Klasse die Methoden, welche im Remote-Interface für die Anwendungslogik deklariert wurden (A). Hier kann der Compiler die Konsistenz nicht sicherstellen, aber die Tools zur Code-Generierung setzen konsistente Deklarationen voraus. Die Implementierung einer eigenen Primärschlüsselklasse ist optional. Zwingend notwendig ist sie nur bei Einsatz eines zusammengesetzten Schlüssels. Namen und Datentypen der Schlüsselattribute müssen zwischen Bean- und Primärschlüsselklasse übereinstimmen (D). Deployment-Deskriptor Zusätzlich zum Java-Code muss der Deployment-Deskriptor, eine Beschreibungsdatei im XML-Format, erstellt werden. Hier werden die vollständigen Namen der beteiligten Klassen und Informationen zum Primärschlüssel verzeichnet und benötigte Ressourcen wie Datenbankverbindungen oder auch andere Enterprise-Beans eingetragen. 26 4.3 Enterprise-Beans Außerdem werden hier in deklarativer Form Transaktionsverhalten, Zugriffskontrolle und persistente Attribute (bei Entity-Beans) festgelegt. Code-Generierung Bei der Installation eines Enterprise-Beans in einem EJB-Container (Deployment) erzeugen die Container-Tools zusätzliche Klassen. Mit Informationen aus dem Deployment-Deskriptor werden Implementierungen für die Home- und Remote-Interfaces generiert (E). Da diese Klassen zur Laufzeit als Remote-Objekte via RMI angesprochen werden, sind für beide jeweils Stub- und Skeleton-Klassen zu erzeugen (F). Eine Enterprise-Bean besteht also aus zwei Interfaces und sieben bis acht Klassen. Abbildung 4.2 zeigt die Abhängigkeiten zwischen den Klassen und Interfaces und zeigt, was vom Bean-Provider geleistet werden muss und was von Tools des ContainerHerstellers generiert wird. 4.3.2 Session-Beans Session-Beans modellieren Anwendungsfälle (oder Geschäftsvorfälle) und sind genau einem Client zugeordnet. Sie können also als Erweiterung des Clients auf dem Server angesehen werden. Session-Beans werden unterschieden in stateful Session-Beans, welche zwischen zwei Methodenaufrufen erhalten bleiben, genau dem aufrufenden Client zugeordnet bleiben und insbesondere einen Zustand behalten können, und stateless Session-Beans, die nur für einen Methodenaufruf einem Client zugeordnet sind. Sie können also zwischen zwei Aufrufen keinen Zustand behalten. Für die Entscheidung, ob Session-Beans zustandslos oder zustandsbehaftet eingesetzt werden, sind Überlegungen auf zwei Ebenen wichtig: Einerseits die Übersichtlichkeit und Klarheit des Clients und der Bean, und andererseits die Fähigkeiten des eingesetzten EJB-Containers hinsichtlich Lastverteilung und Fail-over-Sicherheit. Bei interaktiven Anwendungen ziehen sich Anwendungsfälle in der Regel über mehrere Aufrufe an den Server hin; hier bieten sich stateful Session-Beans an, da der Zustand behalten werden kann und die Aufrufe an sich einfacher sein können. Allerdings kann das bei hohen Clientlasten dazu führen, dass sehr viele Session-Beans instanziiert werden müssen, obwohl nur eine geringe Zahl von Clients tatsächlich Anfragen an den Server schickt. Dann müssen die zur Zeit nicht aktiven Beans ausgelagert werden. Der Verwaltungsaufwand für den Container steigt also. Für viele Projekte wird aber zum entscheidenen Argument, ausschließlich stateless Session-Beans einzusetzen, dass Fail-over-Sicherheit mit den gängigen Produkten zur Zeit nur mit stateless Session-Beans gewährleistet werden kann, und dass auch Lastverteilung nur bedingt mit stateful Session-Beans möglich ist. 27 4 Enterprise Java Beans Abbildung 4.2: Überblick über alle Klassen und Interfaces einer Bean 28 4.3 Enterprise-Beans 4.3.3 Entity-Beans Entity-Beans dagegen repräsentieren Anwendungsentitäten (oder Geschäftsobjekte). Verschiedene Clients können auf ein und dieselbe Entity-Bean-Instanz parallel zugreifen. Entity-Beans werden in der Regel in Datenbanken persistent gespeichert; der Container bietet hierfür Dienste an. Dieser Aspekt ist Schwerpunkt dieser Arbeit und wird in den folgenden Kapiteln ausführlich erörtert. Um dabei die Konsistenz beim konkurrierenden Zugriff mehrere Clients zu gewährleisten, stellt der Container Mechanismen zum Transaktionsschutz bereit (s. Abschnitt 4.3.5). 4.3.4 Lebenszyklen von Session- und Entity-Beans Der EJB-Container ist für die Steuerung der Lebenszyklen aller Bean-Instanzen zuständig. Er kümmert sich um deren Instanziierung, das Management unterschiedlicher Zustände während ihrer Lebensdauer, wie auch um die Vernichtung von Instanzen. Zur Benachrichtigung der Beans über ihren jeweiligen Status werden auf ihnen vom Container bei den Zustandsübergängen entsprechende Callback-Methoden aufgerufen (siehe (C) in Abbildung 4.2). Da betriebliche Informationssysteme häufig mit einer großen Anzahl von Clients und infolge dessen auch einer großen Anzahl benötigter Anwendungsentitäten umzugehen haben, muss der Container in der Lage sein, ohne negative Beeinflussung der Performanz auch große Zahlen von Bean-Instanzen zu verwalten. Dazu kann er Enterprise Beans in sogenannten Pools verwalten (Pooling). Eine bestimmte Anzahl von Beans wird in einem Pool auf Vorrat gehalten, bei Notwendigkeit dem Pool entnommen und gegebenenfalls mit den entsprechenden Werten belegt. Zusätzlich besteht für SessionBeans die Möglichkeit der Aktivierung und Passivierung einzelner Instanzen. Hierzu werden die Beans in der Regel serialisiert im Dateisystem abgelegt. Stateless Session-Bean Die einfachste Lebenszyklusstruktur haben die stateless Session-Beans. Die beiden einzigen Möglichkeiten nicht existent“ und bereit“ stellt Abbildung 4.3 dar. Im ” ” Bereit-Zustand kann der Container diese Art der Beans im Pool verwalten, da sie jeweils nur für die Dauer eines Methodenaufrufs einem Client zugeordnet werden und ansonsten für alle Aufrufer gleich sind. Stateful Session-Bean Die Verwaltung von stateful Session-Beans gestaltet sich etwas schwieriger. Da diese zustandsbehaftet und jeweils nur einem Client zugeordnet sind, gibt es von einer Session-Bean-Klasse viele Instanzen. Da deren Anzahl mitunter sehr hoch werden 29 4 Enterprise Java Beans Abbildung 4.3: Lebenszyklus eines stateless Session-Beans kann, besteht hier für den Container zur Reduzierung der Resourcenauslastung die Möglichkeit, eine Überführung in den Zustand passiviert“ vorzunehmen und die ” Instanz dabei auszulagern (s. Abbildung 4.4). Abbildung 4.4: Lebenszyklus eines stateful Session-Beans Entity-Bean Grundsätzlich sind Entity-Instanzen entweder nicht existent“ oder in einem Pool ” bereit“. Da Entity-Beans Daten aus einer Datenbank repräsentieren, gibt es pro ” Entität in der Datenbank nur eine Bean-Instanz. Diese wird durch die Belegung einer Instanz aus dem Pool erzeugt und zeichnet sich dann, wie in Abbildung 4.5 zu sehen, vor allem durch ihren aktuellen Status in Bezug auf die Datenbank aus. Betrachtet man die Lebensdauer eines Entity-Beans, so muss man sauber unterscheiden zwischen der Lebensdauer der Entität, welche durch das Entity-Bean repräsentiert wird, und der aktuellen Lebensdauer der Bean-Instanz. Ein Entity-Bean kann 30 4.3 Enterprise-Beans Abbildung 4.5: Lebenszyklus eines Entity-Beans als Objekt im Pool bereit liegen und wird für einige Zugriffe mit den Werten einer bestimmten Instanz A aus der Datenbank belegt, wird wieder in den Pool zurückgelegt und anschließend für die Repräsentation einer anderen Instanz B verwendet. Interessant ist es, die Zeit zu betrachten, während der ein Entity-Bean genau eine bestimmte Instanz aus der Datenbank repräsentiert. In dieser Zeit existiert kein anderes Entity-Bean, das die selbe Instanz repräsentiert. Möchten nun zwei Clients gleichzeitig mit diesem Bean arbeiten, so müssen die Zugriffe transaktionsgeschützt erfolgen. Die EJB-Spezifikation sieht hierfür ein Transaktionskonzept vor, das im nächsten Abschnitt besprochen wird. 4.3.5 Transaktionen Die Handhabung der Transaktionen kann vom Bean selber übernommen werden oder dem Container überlassen werden. EJB sieht ein deklaratives Transaktionskonzept vor, d. h. dass der Transaktionsschutz im Deskriptor festgelegt wird. Vorteil ist, dass der Beanprogrammierer kein Transaktions-Handling ausprogrammieren muss. Nachteil ist, dass er doch bei der Implementierung des Beans implizite Annahmen treffen muss und die hoffentlich mit den Angaben im Deskriptor übereinstimmen. EJB-Transaktionen auf Datenbank-Transaktionen abzubilden ist nicht unbedingt trivial, zumal ja nicht festgelegt ist, wie mächtig das Transaktionskonzept der betreffenden Datenbank ist. Im Deployment-Deskriptor wird zunächst festgelegt, ob der Transaktionsschutz deklarativ im Deployment-Deskriptor oder programmatisch in 31 4 Enterprise Java Beans der Bean-Implementierung erfolgt. Im ersten Fall wird für jede Methode des RemoteInterfaces angegeben, mit welchem Isolationslevel sie aufgerufen werden muss. Der Container verwendet einen Transaktionsservice4 , um den geforderten Transaktionsschutz bereitzustellen. Dies entlastet den Bean-Programmierer davon, sich um Details der Transaktions-Implementierung zu kümmern. Allerdings muss er durchaus wissen, dass ein zu laxer Transaktionsschutz u. U. zu fehlerhaftem Verhalten führen kann, und dass zu strenger Transaktionsschutz einen Performance-Engpass bedeutet. 4.4 Persistenz von Entity-Beans Ein besonderes Problem beim Design einer EJB-Anwendung ist die effiziente Speicherung der Daten in einer Datenbank. EJB bietet für Entity-Beans transparente Mechanismen. Sie können entweder vom Container angestoßen und vom Entity-Bean ausgeführt werden (Bean Managed Persistence, BMP), oder der Container kann dies komplett übernehmen (Container Managed Persistence, CMP). Die CMP der Version 1.1 zur Abbildung der Entity-Beans in die Datenbank erlaubt nur sehr einfache Datenstrukturen. Es ist z. B. nicht möglich, abhängige Objekte abzubilden (klassisches Beispiel: ein Auftrag mit beliebig vielen Auftragspositionen). Beziehungen zwischen Entity-Beans werden von Hand ausprogrammiert oder über proprietäre Funktionalität des Containers gelöst. 4.4.1 Container Managed Persistence (CMP) Vor- und Nachteil zugleich ist bei CMP die Tatsache, dass die Klasse selbst nichts über die Persistenz weiß; das OR-Mapping wird im Deskriptor beschrieben; der Container erledigt die Arbeit. Im Entity-Bean wird die fachliche Logik implementiert, und die technischen Abhängigkeiten, in diesem Fall die Abbildung in die Datenbank, wird deskriptiv gelöst. So erhält man eine A/T-Trennung in Bean (A) und Deskriptor (T). Was auf den ersten Blick so einfach aussieht, stellt bei näherer Betrachtung eine Reihe von Problemen: Die Spezifikation legt nur fest, wie persistente Attribute im Deployment-Deskriptor benannt werden. Die genaue Abbildung in die Datenbank wird nicht spezifiziert. Das ist unter dem Gesichtspunkt der Portabilität ein Nachteil: Werden Entity-Beans auf einen anderen Container portiert, muss der OR-Mapping-Deskriptor neu geschrieben werden. Dabei sind unter Umständen nicht nur anderslautende XML-Tags erforderlich, sondern womöglich sind die OR-Mapping-Mechanismen unterschiedlich mächtig. Daraus folgt, dass auch das Bean anders entworfen werden muss, um die 4 die Dienste JTA und JTS 32 4.4 Persistenz von Entity-Beans Möglichkeiten des Containers auszunutzen. Unterschiedlich mächtige OR-MappingMechanismen verhindern die Portabilität von Entity-Beans. Die Standard-Mechanismen der gängigen Container sind relativ trivial: Sie bilden jedes Bean auf eine Tabelle, jedes Attribut auf eine Spalte und jede Entität auf eine Zeile ab. Komplexere Objektgeflechte, die komplexere Abbildungen erfordern, gehen über die Spezifikation hinaus und sind nicht mehr portabel. Einige der gängigen Container können abhängige Objekte von nicht-trivialen Datentypen höchstens als BLOB (binary large object) abspeichern. Hier taucht dann das Problem auf, dass Datenbanken wie z. B. Oracle nur eine BLOB-Spalte pro Tabelle unterstützen; außerdem kann über diese Spalten nicht gesucht werden, und die Definition der abgespeicherten Klassenstruktur darf sich nie wieder ändern. Erst aufwändige Werkzeuge, wie im nächsten Abschnitt beschrieben, leisten hier mehr. Die Listings 4.1 und 4.2 zeigen Teile der Bean-Implementierung und den zugehörigen Deployment-Deskriptor für ein Bean mit CMP. Listing 4.1: Beispiel einer Bean-Implementierung mit CMP import javax.ejb.EntityBean; public class CustomerBean implements EntityBean { public String customerId; // public nur wegen CMP-Zugriff public String firstName; public String lastName; // ... } Listing 4.2: Zugehöriger Deployment-Deskriptor <ejb-jar> <enterprise-beans> <entity> <ejb-name>CustomerBean</ejb-name> <home>com.sdm.tbs.ac.customer.impl.CustomerHome</home> <remote>com.sdm.tbs.ac.customer.impl.Customer</remote> <ejb-class>com.sdm.tbs.ac.customer.impl.CustomerBean</ejbclass> <persistence-type>Container</persistence-type> <prim-key-class>com.sdm.tbs.ac.customer.impl.CustomerPK</ prim-key-class> <reentrant>False</reentrant> 33 4 Enterprise Java Beans <cmp-field> <field-name>customerId</field-name> </cmp-field> <cmp-field> <field-name>firstName</field-name> </cmp-field> <cmp-field> <field-name>lastName</field-name> </cmp-field> </entity> </enterprise-beans> </ejb-jar> Die mit CMP persistent gehaltenen Felder müssen als public vereinbart werden, damit der Container darauf zugreifen kann. Unter dem Gesichtspunkt der Kapselung ist das kritisch zu bewerten. 4.4.2 Einsatz eines OR-Mapping-Werkzeugs für CMP Es ist auch möglich, den Persistenz-Mechanismus eines Containers zu ersetzen durch ein Produkt wie z. B. TopLink [Top01] oder Versant [Ver01]. Auf diese Weise wird CMP wesentlich mächtiger; Standardfälle wie die Abbildung von abhängigen Objekten werden deutlich besser unterstützt. Dies ist möglich, weil die Hersteller solcher Werkzeuge sehr spezifisches Know-How über die OR-Abbildung haben, über das die Applikationsserver-Hersteller nicht verfügen. Deshalb ist es auch sinnvoll, dass im EJB-Standard nicht so ein ausgefeiltes OR-Mapping verlangt wird. Allerdings weicht man durch den Einsatz eines solchen Werkzeuges vom Standard ab und erhöht so die Abhängigkeit von diesem Tool und vermutlich auch vom verwendeten Applikationsserver, da die Schnittstelle zwischen Server und Container nicht spezifiziert ist. TopLink Die mächtigen OR-Mapping-Fähigkeiten von TopLink für native Anwendungen sind auch für EJB einsetzbar. Entity-Beans können mit abhängigen Objekten und ganzen Geflechten mit Hilfe der bekannten grafischen Oberfläche und des damit erzeugten Zugriffscodes bzw. XML-Deskriptors auf die Datenbank abgebildet werden. Diese Möglichkeiten gehen deutlich über die üblichen Container-Fähigkeiten hinaus. Deshalb macht man sich schon mit dem Entwurf, der auf die Nutzung dieser Möglichkeiten abzielt, von dieser Produktkombination abhängig. 34 4.4 Persistenz von Entity-Beans Versant enJin Versant enJin ist eine objektorientierte Datenbank. Versant vertreibt einen Container, der direkt mit WebSphere oder WebLogic zusammenarbeiten kann, und Entity-Beans direkt in der Objektdatenbank ablegt. So unterliegt man beim Entwurf der Beans nicht mehr den oben angeführten Einschränkungen, macht sich aber von der eingesetzten Datenbank abhängig. Siehe Abschnitt 5.2.3 für eine kurze Einordnung von objektorientierten Datenbanken. 4.4.3 Bean Managed Persistence (BMP) Die Spezifikation sieht vor, dass jedes Entity-Bean auch selbst die Speicherung in der Datenbank übernehmen kann. Dafür implementiert es die Methoden ejbLoad und ejbStore, die vom Container aufgerufen werden und in denen die Speicherung des Objektes in der Datenbank durch das EntityBean selbst angestoßen oder vorgenommen wird. Der naive Ansatz ist, hier direkt JDBC-Aufrufe einzubauen. Dies ist aber ineffizient, da das Bean die Zahl der Aufrufe dieser Methoden nicht in der Hand hat, und es ist auch kein gutes Design, da Anwendungslogik mit datenbanktechnischen Dingen vermischt wird. In der Literatur (s. z. B. [DePe00], [Ro99] oder [Mon99]) wird BMP auf diese Weise erklärt; gleichzeitig erfolgen in der Regel Warnungen, BMP wegen der drohenden Unübersichtlichkeit und der nicht unbedingt höheren Effizienz nur in ausgewählten Fällen einzusetzen. Dem ist uneingeschränkt zuzustimmen: Selber JDBC-Aufrufe in ein Entity-Bean zu schreiben, ist sicher keine gute Idee. Listing 4.3 zeigt das Grobgerüst eines Beans, das BMP verwendet. Listing 4.3: Beispiel eine Bean-Implementierung mit BMP import javax.ejb.EntityBean; public class CustomerBean implements EntityBean { private String customerId; private String firstName; private String lastName; private boolean dataLoaded; // ... public CustomerPK ejbCreate (String customerId) { this.customerId = customerId; dataLoaded = false; 35 4 Enterprise Java Beans return new CustomerPK (customerId); } public void ejbLoad () { customerId = context.getPrimaryKey (); // verwende den Primaerschluessel, um die Daten zu laden // z. B. ueber JDBC-Aufrufe dataLoaded = false; } public void ejbStore () { // schreibe alle geaenderten Daten zurueck in die Datenbank. } public String getLastName () { if (!dataLoaded) doLoadData (); return lastName; } private void doLoadData () { // Der Primaerschluessel customerId ist bekannt. // Nun die Daten aus der Datenbank laden, z. B. per JDBC. dataLoaded = true; } } Allerdings sind damit die Möglichkeiten von BMP noch nicht erschöpft – und das wird in den genannten Standardwerken nicht erwähnt. Eine Alternative ist es, in diesen Methoden ein Framework aufzurufen, das die Datenbankaufrufe durchführt. Dies kann ein eigenes Konstrukt sein, das als einfacher Zwischenspeicher arbeitet und nur notwendige Updates ausführt, eine eigene Zugriffsschicht wie das QDI, oder auch ein vorhandenes Produkt wie TopLink. In Listing 4.3 werden dann nicht mehr in der Methode ejbLoad die Daten aus der Datenbank gelesen, sondern erst beim ersten Zugriff auf das Bean werden die erforderlichen Daten nachgeladen. 36 4.5 EJB 2.0 4.4.4 Java Data Objects (JDO) In diesem Zusammenhang taucht immer wieder die Frage nach Java Data Objects (JDO) auf. Hierbei handelt es sich ebenfalls lediglich um eine Spezifikation, nicht um ein fertiges und benutzbar vorliegendes API. An dieser Stelle sei nur auf die einschlägigen Internet-Seiten verwiesen, zu finden unter http://jcp.org/jsr/detail/ 12.jsp oder http://java.sun.com/products/jdbc/related.html Dort heißt es einerseits, JDO definiere keine eigenen Persistenzmechanismen, sondern beziehe sich auf die entsprechenden APIs von J2EE. Andererseits sei JDO eine von EJB unabhängige, parallele Spezifikation. Es handelt sich – grob gesagt – um das objektorientierte Pendant zum relationalen JDBC. JDO ist kein von EJB ausdrücklich motivierter Weg für die Persistenz. Deshalb soll an dieser Stelle nicht zuviel Aufmerksamkeit darauf gelenkt werden. 4.4.5 Weitere Aspekte Das Problem der Persistenz kann nicht losgelöst von einem Transaktionsmodell betrachtet werden. Zur Abbildung von Anwendungs-Transaktionen auf Applikationsserver-Transaktionen und von diesen auf Datenbank-Transaktionen siehe Abschnitt 3.2.1. Der Applikationsserver stellt einen Transaktionsservice zur Verfügung. Ähnlich wie bei der Persistenz können auch Transaktionen entweder deklarativ (im DeploymentDeskriptor) oder programmatisch (in der Bean-Implementierung) festgelegt werden. Eine gute Einführung in das Thema Transaktionen mit EJB ist u. a. in [Ro99] zu finden; für eine detaillierte Betrachtung von Transaktionen sei auf [GrRe92] verwiesen. 4.5 EJB 2.0 Die Spezifikation 2.0 [Sun01a] wurde im Sommer 2001 verabschiedet. Im Moment sind noch keine ausreichend gute Produkte erhältlich, die die Spezifikation 2.0 implementieren5 ; daher sind bislang noch kaum aussagegräftige Versuche möglich. Wichtige Neuerungen umfassen u. a.: • Message Driven Beans: JMS jetzt auch für EJB Ähnlich den Session-Beans enthalten sie Logik für Anwendungsfälle. Im Gegensatz zu Session- oder Entity-Beans werden sie aber nicht über Home- und Remote-Interfaces angesprochen, sondern ihnen wird eine JMS-Nachricht (Java Messaging Service, s. [Wrox01]) zugesandt; sie arbeiten also asynchron. Der 5 einzig der Bea WebLogic 6.1, der zur Zeit in einer Beta-Version vorliegt. Stand: November 2001 37 4 Enterprise Java Beans Nachrichtendienst JMS ist nicht neu; neu ist lediglich die Möglichkeit, in einem EJB-System JMS-Nachrichten empfangen zu können. • Local interfaces Bislang war jeder Aufruf an ein Enterprise Bean ein Remote-Aufruf; d. h. dass alle Argumente serialisiert werden und der Aufruf über RMI erfolgt, selbst dann, wenn das gerufene Objekt sich in der selben virtuellen Maschine befindet. Nun ist es aber oft gar nicht sinnvoll, andere Objekte entfernt anzusprechen. Beispielsweise hat sich das Fassade-Pattern durchgesetzt, das bedeutet, dass Entity-Beans nur von Session-Beans verwendet werden und Clients nur mit Session-Beans kommunizieren. Dann braucht kein Aufruf an ein Entity-Bean mehr entfernt zu sein. Dies erhöht neben der Performanz auch die Sicherheit. Der entscheidende Vorteil, der sich aus diesen Performanzüberlegungen ergibt, ist die Tatsache, dass nun mehrere Beans gemeinsam entwickelt werden können, die zusammen gehören. Nur eines von ihnen bekommt ein Remote-Interface, der Rest arbeitet über local interfaces zusammen; so ist es eher möglich, fachlich motivierte Komponentengrenzen mit Hilfe der EJB-Konstrukte auszudrücken. Im Prinzip handelt es sich um eine konsequente Weiterentwicklung der Spezifikation, die jetzt das mit einschließt, was einzelne Container-Hersteller schon als proprietäre Lösungen eingebaut hatten. Allerdings geht die Spezifikation nicht so weit wie von manchen erwartet wurde. Es steht vielmehr zu erwarten, dass sich die EJBTechnologie weiter entwickeln wird. 4.5.1 Erweiterungen in EJB 2.0 bezüglich der Persistenz Mit der Version 2.0 kommen Erweiterungen, die in [Wrox01, S. 149] enthusiastisch angekündigt werden: This new EJB 2.0 persistence model is not just a fine tuning of features ” that were available in the EJB 1.1 specification. Instead, EJB 2.0 persistence is a revolutionary addition of a standards-based object-relational mapping framework to Enterprise JavaBeans technology.“ Die Vorteile, die sich tatsächlich aus den Änderungen ergeben, sind nach Meinung des Autors enttäuschend gering: Eine Revolution hat hier nicht stattgefunden. Trotzdem müssen alle Entity Beans umgeschrieben werden, da die Änderungen, z. B. der Zugriff auf CMP-Attribute, nicht abwärtskompatibel sind. Dennoch: Die Beschreibung der objektrelationalen Abbildung im Deployment-Deskriptor wurde immer noch nicht normiert. 38 4.5 EJB 2.0 Version 2.0 bringt interessante Neuerungen im Hinblick auf die Beziehung zwischen Entity-Beans. Solche Beziehungen können mit dem neu eingeführten Konzept Con” tainer Managed Relationsships (CMR)“ formuliert werden; elegant ist die transparente Eingliederung in den neuen CMP-Mechanismus. Erstmals wird auch eine Abfragesprache spezifiziert: EJB-QL ist neu, orientiert sich aber an den bisher verfügbaren proprietären Lösungen der Produkthersteller. Auch lässt die Spezifikation 2.0 noch zentrale Design-Fragen offen, bzw. Funktionalität fehlt. Beispielsweise fehlt eine Festlegung auf ein optimistisches oder pessimistisches Transaktionskonzept. Dies bleibt nach wie vor den Container-Herstellern überlassen. Im Einzelne sind für die Persistenz besonders folgende Änderungen interessant: Geänderte CMP Der Zugriff auf die persistenten Attribute läuft nun über (abstrakte) Zugriffsmethoden und nicht mehr direkt auf die Attribute. Im Hinblick auf die Verwendung von abhängigen Objekten und ihrer Persistenz enttäuscht die Spezifikation 2.0. Weitere Änderungen sind zu erwarten (oder zu erhoffen). Container Managed Relationships (CMR) CMR geht einen wichtigen Schritt in die richtige Richtung. Die objekt-relationale Abbildung wird erweitert um die Abbildung von Beziehungen zwischen A-Entitäten. Die Einführung von CMR ist auch der Grund für die Änderung des Zugriffs auf CMPAttribute. In Verbindung mit den neu eingeführten local interfaces sind interessante Konstruktionen möglich. Allerdings müsste dann aus jedem kleinen abhängigen Objekt (wie z. B. einer Auftragsposition, einer Adresse o. ä.) ein Bean gemacht werden. Eine Query Language (EJB-QL) Die meisten Container der Spezifikation 1.1 haben ihre eigenen, proprietären Abfragesprachen mitgebracht, mit deren Hilfe Abfragen an die Datenbank abgesetzt werden können. Diese Abfragesprachen waren alle an SQL angelehnt; nach dem Muster dieser Abfragesprachen ist die EJB-QL neu in die Spezifikation aufgenommen. Sie bringt also nicht mehr Funktionalität, sondern nur mehr Portabilität zwischen verschiedenen Containern. Es bleibt das Problem, dass die Abfragen im Deployment-Deskriptor formuliert werden. Sie sind aber durchaus auch Teil der Anwendungslogik; es kann also nicht von einer A/T-Trennung in Implementierung (A) und Deployment-Deskriptor (T) gesprochen werden. 39 4 Enterprise Java Beans 4.5.2 Bewertung der Änderungen Abhängigkeit vom Hersteller Die Neuerungen in der EJB-Spezifikation sind entstanden aus den Praxis-Erfahrungen mit EJB 1.1-Anwendungen. Daher verwundert es umso mehr, wieviel Freiheiten den Container-Herstellern gelassen werden, bzw. wieviel Arbeit ihnen noch gemacht wird. Bei der Spezifikation der Abfragesprache EJB-QL sind die Erfahrungen mit den jeweiligen proprietären Abfragesprachen eingeflossen. EJB-QL kann nicht mehr und nicht weniger als die bisherigen Methoden, aber nun ist sie immerhin spezifiziert und damit portabel. Allerdings ist nicht spezifiziert, wie das OR-Mapping genau umzusetzen ist – die Portabilität zwischen zwei Applikationsservern scheitert nach wie vor spätestens am Deployment-Deskriptor. Das Problem hierbei sind aber wie bisher nicht einfach anderslautende XML-Tags, sondern die unterschiedlichen Möglichkeiten bei der Abbildung auf die Datenbank bestimmen den Entwurf der Entity-Beans. Für einen anderen Container genügt also nicht ein neuer Deployment-Deskriptor, sondern womöglich ist eine Portierung gar nicht möglich, da die Fähigkeiten der Container zu unterschiedlich sind. Schwächen, die trotzdem erhalten geblieben sind Eine große Schwäche von EJB ist erhalten geblieben: Die versprochene Trennung von A-Software in der Bean-Implementierung und T-Aspekten im Deployment-Deskriptor ist immer noch nicht erfüllt. Im Gegenteil: Die Relationen zwischen A-Entitäten sind ein fachlicher Aspekt, der aber im Deployment-Deskriptor formuliert wird. Es ist durchaus ein gutes Konzept, solche Dinge in deklarativer Form umzusetzen, allerdings muss man sich dabei der Bedeutung des Deployment-Deskriptors als wichtigem Teil der fachlichen Implementierung bewusst sein. Der Performanz-Engpass, der durch die Remote-Interfaces entstanden war, wurde erkannt und beseitigt. Beim Umgang mit A-Entitäten ist ein wichtiges Instrument zur Performanz-Optimierung die Entscheidung zwischen lazy-loading und eager-loading. Dazu hat der Programmierer aber mit CMR keine Möglichkeit. So ist ein neuer potenzieller Flaschenhals entstanden, weil die an sich gute Idee nicht konsequent zuende gedacht wurde. Sowohl CMR als auch der neue CMP-Mechanismus laufen über abstrakte Zugriffsmethoden, welche in einer vom Container generierten Subklassee implementiert werden. Denkbar wäre, im Deployment-Deskriptor deklarativ den Ladezeitpunkt zu bestimmen, also früh“, um viele Daten en bloc zu laden, oder spät“, um nur die benötigten ” ” Attribute zu lesen. Der Container könnte dann die zugehörige Zugriffsmethode entsprechend ausgestalten. Weiterhin fehlt die Unterscheidung zwischen vollwertigen“ ” 40 4.5 EJB 2.0 A-Entitäten und kleinen, abhängigen Objekten. Erst mit einer solchen Unterscheidung wäre der Entwurf von sauber geschnittenen Komponenten elegant möglich. Da auch bisher Erfahrungen mit Schwächen der Spezifikation und der Produkte eingearbeitet wurden, ist damit zu rechnen, dass diese ausgebessert werden. Dann dürfen wir uns auf einen weiteren Versionwechsel freuen. 4.5.3 Der Wechsel von EJB 1.1 auf EJB 2.0 Für die Durchführung von Projekten, die im Moment die Version 1.1 zur Verfügung haben, aber irgendwann auf 2.0 umsteigen werden bzw. müssen, sind folgende Aspekte von Bedeutung: • Ist es möglich oder sinnvoll, EJB 1.1 und EJB 2.0 gleichzeitig zu verwenden, oder müssen vorhandene Anwendungen jetzt umgeschrieben werden? Beachte: EJB 1.1-Anwendungen sind noch kein Jahr alt und dennoch Altsysteme! • Welche Konstruktionen, die mit EJB 1.1 gebaut werden, sind mit 2.0 nicht mehr nötig, weil die Probleme behoben sind? Welche müssen beibehalten werden? • Welche müssen über Bord geworfen werden, weil sie nicht mehr funktionieren? Die EJB-Spezifikation 2.0 legt ausdrücklich fest, dass alle EJBs der Version 1.1 noch unterstützt werden müssen. Ein Entity-Bean kann entweder der Version 1.1 oder der Version 2.0 entsprechen, muss dies auch explizit machen und kann auf dem Applikationsserver installiert werden. Allerdings ist die Pflege eines Systems, das teils aus alten und teils aus neuen Beans besteht, im Projektverlauf problematisch. Es wird eher vom Pflegeaufwand als von den technischen Gegebenheiten der Druck entstehen, die alten Beans umzuschreiben, um ein einheitliches System zu erhalten. Die Spezifikation 1.1 hatte Schwächen, welche zu Hilfskonstruktionen geführt haben, die nun nicht mehr in jedem Fall nötig sind. Ein Beispiel ist das Value Object-Pattern [Alu01], dessen Verwendung angeraten wird, da Aufrufe über das Remote-Interface zu teuer sind. Werden bestimmte Entity-Beans nur von Session-Beans aus aufgerufen, so können local interfaces verwendet werden, und es ist zu überlegen, ob nicht völlig auf das Value Object verzichtet werden kann. Um Beans konsequent von 1.1 auf 2.0 umzuschreiben, reicht eine Änderung des Zugriffsmechanismus auf die CMPAttribute nicht aus, sondern auch derartige Konstruktionen müssen neu überdacht werden. 41 5 Persistenzoptionen von A-Entitäten Für die Implementierung von A-Entitäten gibt es verschiedene Möglichkeiten. Abbildung 5.1 stellt sie als Baum dar. Abbildung 5.1: Persistenz-Optionen für A-Entitäten Der linke Ast führt die Persistenzoptionen beim Einsatz von Entity-Beans auf; sie werden im ersten Teil dieses Kapitels beschrieben. Der rechte Ast sieht die Implementierung von A-Entitäten als nativen Java-Klassen vor und wird im zweiten Teil untersucht. 5.1 Persistenz über Entity-Beans Der Entwurf eines betrieblichen Informationssystems beginnt mit der fachlichen Anforderungsanalyse und der fachlichen Spezifikation. Der erste Schritt ist also, nach fachlichen Gesichtspunkten eine Komponentenbildung zu identifizieren, um Anwendungsfälle, Anwendungsentitäten und Anwendungsentitätenverwalter zu modellieren, kurz: um festzustellen, wo welche fachliche Logik untergebracht werden muss. Die EJB-Spezifikation schlägt vor, Anwendungsfälle als Session-Beans zu formulieren und Anwendungs-Entitäten als Entity-Beans zu modellieren. Sie verspricht auch die Trennung von fachlicher Logik von technischen Aspekten, indem die Anwendungslogik in den Beans ausprogrammiert wird, während die technischen Gegebenheiten 42 5.1 Persistenz über Entity-Beans wie beispielsweise die Abbildung auf die Datenbank oder der Transaktionsschutz im Deployment-Deskriptor deklarativ festgehalten wird. Die Unterscheidung in A-Entität und A-Verwalter ist durch Home- und RemoteInterface gegeben. Die Kombination von Home- und Remote-Interface, der eigentlichen Implementierung in der Bean-Klasse und der Deployment-Deskriptor ergeben zusammen ein Enterprise Bean. Sun nennt das eine Komponente. Damit scheinen alle Forderungen erfüllt, um wartbare und effiziente Systeme zu bauen. 5.1.1 Kritik am naiven Einsatz von Entity-Beans Macht man sich aber genauere Gedanken über die Bildung von Komponenten und die Definition ihrer Schnittstellen, so stößt man bald auf Widersprüche zwischen den Möglichkeiten, die EJB bietet und den in Kapitel 2 genannten Forderungen an den Komponentenschnitt und die A/T-Trennung. Sun stellt gewisse Versprechungen bezüglich der Leistungsfähigkeit der EJB-Architektur auf: EJB stellt den Anspruch Write once, run anywhere“ ” In der Spezifikation steht [Sun99, S. 19]: These applications may be written once, and then deployed on any server ” platform that supports the Enterprise JavaBeans specification.“ Es folgt die Einschränkung, dass dies nur funktioniert, wenn keine herstellerspezifischen Erweiterungen ausgenutzt werden. Gleichzeitig bleibt aber die Spezifikation so ungenau, dass die Hersteller eigene Lösungen entwickeln müssen. Dies passiert an genau den Stellen, wo es schwierig wird, gute Lösungen zu entwickeln: Persistenz, lokale oder entfernte Aufrufe, Transaktionsverwaltung, Sperrstrategien. Als Beispiel sei das Sperrverhalten genannt: Entity-Beans repräsentieren jeweils genau eine Entität der Datenbank. Der konkurrierende Zugriff von mehreren Clients auf ein Entity-Bean muss also transaktionsgeschützt geschehen. Dafür sind prinzipiell zwei Strategien denkbar: Das Bean kann entweder optimistisch oder pessimistisch gesperrt werden.1 In der pessimistischen Variante werden alle Zugriffe auf ein Entity-Bean serialisiert, so dass der erste Client Zugriff auf das Entity-Bean bekommt und bis zum Ende der Transaktion behält; alle weiteren Clients, die auf das selbe Entity-Bean zugreifen wollen, bleiben gesperrt – selbst wenn sie nur lesend zugreifen möchten. Diese Strategie wird von den meisten Applikationsservern verfolgt. Es gibt auch die optimistische Strategie, welche für eine Datenbank-Identität bei konkurrierendem Zugriff mehrere 1 siehe hierzu Abschnitt 3.2.1. 43 5 Persistenzoptionen von A-Entitäten Entity-Bean-Instanzen erzeugt und am Transaktionsende prüft, ob die Daten beim Zurückschreiben in die Datenbank einen optimistischen Sperrkonflikt auslösen. Diese Strategie beherrscht zur Zeit nur der Borland Application Server. Eine dritte Variante ist es, das Sperren von Entity-Beans auf das Sperrverhalten der darunterliegenden Datenbank zu verlagern. Damit kommen die Fähigkeiten der Datenbank und nicht mehr ausschließlich die des Containers zum Tragen. Diese Möglichkeit bietet z. B. der Bea WebLogic Server in der Version 6.1. Die Durchführung des Sperrmechanismus’ ist technischer Natur und soll für den Anwendungsprogrammierer nicht sichtbar sein. Die Entscheidung jedoch, welche Strategie verfolgt werden soll, ist für den Anwendungsprogrammierer wichtig, denn sie bestimmt, wie sich die Anwendung unter hoher Clientlast verhalten wird. Die EJB-Spezifikation schweigt sich jedoch über diesen Punkt aus und zwingt die Hersteller dazu, sich entweder festzulegen auf eine Strategie oder über proprietäre Wege eine Wahlmöglichkeit einzuführen. So wird sich die selbe Anwendung, auf zwei unterschiedlichen Servern installiert, unterschiedlich verhalten. Das führt sogar dazu, dass die Fähigkeiten des verwendeten Applikationsservers das Design der Anwendung beeinflussen. Die Spezifikation bleibt hinter dem Anspruch der Write once, run ” anywhere“ weit zurück. Abschnitt 4.4.1 führte diesen Kritikpunkt für die Persistenz mit CMP aus, weil auch dort die Spezifikation zu ungenau bleibt. EJB und die Trennung in A/T/0-Software EJB beansprucht zwar für sich, dass der Entwickler sich ganz auf die fachliche Logik konzentrieren kann und die technischen Probleme vom Container gelöst werden. Um dies zu erreichen, soll die ganze Anwendungslogik im Bean implementiert werden, während die technischen Abhängigkeiten im Deployment-Deskriptor festgehalten werden. Beim Design von Beans werden aber technische Aspekte (nämlich die Abhängigkeit von der EJB-Technologie) mit solchen der Anwendungslogik vermischt. Beispielsweise kann im Falle aufwändiger konstruierter Beans die Persistenz nicht mehr deklarativ gelöst werden, sondern muss im Bean manuell programmiert werden — oder andersherum bestimmen die Möglichkeiten der deklarativen Lösung die Gestaltung der Bean. Java-Code, der ein fachliches Problem beschreibt, kann nicht ohne Weiteres in einem EJB-Container installiert werden. Ebenso kann ein Bean nicht ohne Container, also ohne EJB-Umgebung, wiederverwendet oder getestet werden. Performanz Zumindest bis zur Version 1.1 diktieren Performanz-Überlegungen die Schnittstelle der Beans. Jeder Aufruf einer fachlichen Methode ist ein Remote-Aufruf über den RMI-Mechanismus. Das bedeutet, dass alle Parameter serialisiert werden und der 44 5.1 Persistenz über Entity-Beans Aufruf über das Netzwerk geleitet wird, selbst wenn das gerufene Objekt sich auf dem selben Rechner in der selben Virtuellen Maschine befindet. Das bedeutet einen enormen Performanz-Engpass für jeden Aufruf einer Methode des Remote-Interfaces. Vergleichsmessungen haben ergeben: Remote-Aufrufe sind ca. 1.000-mal langsamer als lokale Methodenaufrufe.2 Aus diesem Grund schlägt Sun selbst das Value Object-Pattern vor [Alu01], um die Schnittstelle des Entity-Beans möglichst schmal zu halten und möglichst wenige Aufrufe über die Remote-Schnittstelle zu tätigen. Nach diesem Entwurfsmuster enthält das Entity-Bean im Remote-Interface nur noch zwei Methoden: getData(), welche ein Transfer-Objekt zurückliefert, auf dem der Client, in der Regel ein Session-Bean, die fachlichen Operation ausführt, und mittels der zweiten Methode setData (MyTransferObject obj) die Daten im Entity-Bean wieder setzen kann. Der Einsatz des Value-Object-Musters führt also dazu, dass das Entity-Bean nach technischen Gesichtspunkten entworfen wird, und dass es nur noch als Schnittstelle zur Datenbank existiert. Sun tritt mit diesem Vorschlag von dem Versprechen zurück, Entity-Beans seien intelligente Anwendungsobjekte mit transparenter Persistenz. Ab der Spezifikation EJB 2.0 ist dieser Engpass durch die Einführung von local interfaces abgeschafft. Die Performanz allgemein zu beurteilen, ist schwierig. Es spielen verschiedene Fakten zusammen: 1. Java ist langsamer als beispielsweise C++. Es handelt sich hier aber nur“ ” um etwa einen linearen Faktor; unterschiedliche Vergleiche in Fachzeitschriften haben unterschiedliche Ergebnisse gebracht. Interessant bei PerformanzVergleichen sind aber nicht solche Faktoren, sondern Größenordnungen. Manche Implementierungen von virtuellen Maschinen können vielleicht ein wenig mehr herauskitzeln, und für die Zukunft ist noch mehr zu erwarten. 2. Der Flaschenhals bei typischen Informationssystemen liegt nicht in der Ablaufgeschwindigkeit in einem Teilsystem, sondern vielmehr im Zusammenspiel zwischen verschiedenen Systemen. Wenn ein großer Teil der Laufzeit innerhalb der Datenbank liegt, macht die Geschwindigkeit der darauf aufbauenden Anwendung eben nur einen kleinen Teil aus. 3. Was zählt, ist nicht etwa ein Vergleich Java vs. C++“, sondern ein Vergleich ” des gesamten Systems, also beispielsweise: Java mit Applikationsserver vs. ” C++ mit ORB und Transaktionsmonitor“. Hier haben Applikationsserver die Chance, durch geschickte Integration der Dienste für Transaktionssteuerung, Persistenz, Datenbankverbindungs-Pooling, Caching und zentrale Ressourcenverwaltung einen deutlichen Effizienzvorteil zu erreichen. 2 Quelle: DV-Konzept eines erfolgreichen sd&m-Projektes, Karl-Heinz Wichert, Manuel Fehlhammer 45 5 Persistenzoptionen von A-Entitäten Vergleiche sind also im Einzelfall anzustellen. Ein produktives Projekt bei sd&m konnte mit einem EJB-Applikationsserver einen messbaren Effizienzvorteil (ca. Faktor vier) gegenüber der abgelösten C++-Anwendung erreichen. Es lassen sich sicherlich auch Praxisbeispiele mit umgekehrtem Ergebnis finden. Technischer Komponentenbegriff in EJB EJB versteht sich als Komponentenmodell“ (s. Abschnitt 2.2 zur Abgrenzung zwi” schen objektorientiertem und komponentenorientertem Entwurf). Zur Identifizierung von Komponenten sind die Wahl der Granularität der Komponenten und die Beschreibung ihrer Schnittstellen entscheidend. Die Schnittstelle beschreibt die Außensicht einer Komponente; die Größe einer Komponente zeigt sich z. B. in der Zahl und Struktur ihrer Attribute. Beides sollte sich aus inhaltlichen Erwägungen, also aus Überlegungen, die sich auf das fachliche Modell stützen, ergeben. Die EJB-Spezifikation macht sich keine Gedanken über den Unterschied zwischen einer Komponente und einem Objekt [Sun99, Kapitel 4]: The enterprise Bean architecture is flexible enough to implement com” ponents such as the following: An object that represents ...“ EJB stellt mit dem Applikationsserver eine Plattform zur Verfügung, auf der die Beans eingesetzt werden können. Die Abhängigkeiten von anderen Beans werden im Deployment-Deskriptor eingetragen, sind also explizit, wie in [Szy99] gefordert. Hier hört aber der Vergleich mit dem Komponentenbegriff aus Abschnitt 2.2 schon auf. Da Enterprise-Beans von der Größe her auf einzelne Objekte festgelegt sind, taugen sie weder zur Strukturierung großer Systeme auf Quelltextebene, noch sind sie geeignet, in binärer Form ausgeliefert und beliebig kombiniert eingesetzt zu werden. Obwohl sie aber auf Objekte festgelegt sind, ist Vererbung von Enterprise Beans nicht (oder nur über Verrenkungen) möglich. Enterprise Beans können also auch nicht mehr frei nach den Ideen der Objektorientierung entworfen werden. Sucht man nun nach Hinweisen zum Entwurf von Enterprise Beans, so finden sich erstaunliche Hilfestellungen. So z. B. Ambler in [Am01]: Good entity beans: Are coarse-grained. [They] implement important ” business behavior. [They] are optimized to simplify and minimize database access, limiting the complexity of database joins, avoiding long-runnig operations that access data, and minimize round-trips between the container and the database.“ Genau wie die EJB-Spezifikation fordert er, Entity-Beans dazu zu verwenden, die Anwendungslogik aufzunehmen, gleichzeitig aber den Entwurf sowohl im Hinblick 46 5.1 Persistenz über Entity-Beans auf die Größe als auch auf die Gestaltung der Schnittstelle nur von technischen Überlegungen her zu motivieren. Dies widerspricht genau den in Kapitel 2 genannten Prinzipien. Es werden also sowohl Außen- als auch Innensicht der Beans aufgrund technischer Überlegungen entworfen. Damit fällt es schwer, Entity-Beans noch als fachliche Komponenten anzusehen. Abhängigkeit von der EJB-Version Manche der hier genannten Probleme werden mit der Version 2.0 der EJB-Spezifikation gelöst. Allerdings bedeutet das, dass EJB-Systeme, die für Produkte der Version 1.1 entwickelt wurden, heute bereits Altsysteme sind – obwohl sie noch nicht mal ein Jahr alt sind! In der Spezifikation ist zwar vorgesehen, auch mit 2.0 noch Beans der Version 1.1 zu unterstützen. Allerdings ist nicht klar, wie lange dies noch zur Spezifikation gehört. Es ist noch weniger klar, wie genau sich die Produkthersteller an diese Vorgabe halten werden. Vor allem aber ist fraglich, ob es für ein Projekt sinnvoll ist, manche Beans 1.1konform zu behalten, während neue Beans für EJB 2.0 entwickelt werden. Das ist unübersichtlich und führt dazu, dass im Entwicklerteam Know-How über ein an sich schon veraltetes System gepflegt werden muss. Für Projektarbeit ist dies ein kritischer Punkt. Entscheidet sich ein Projekt mit dem Einsatz eines Applikationsservers für EJB 2.0, alle Beans nach 1.1 abzulösen, so müssen all diese Beans umgeschrieben werden. Software, die nach der Änderung der technischen Basis ohne geänderte fachliche Anforderungen angepasst werden muss, ist eindeutig T-Software. Schwierigkeiten bei der praktischen Programmentwicklung • Mit Home-, Remote-Interface, der Bean-Implementierung selbst und dem Deployment-Deskriptor müssen vier Dateien konsistent gehalten werden – ohne dass der Compiler dabei Unterstützung leisten kann. Abbildung 4.2 zeigt die komplexen Abhängigkeiten. • Entwicklungszyklen sind recht lang: Selbst Hot-Deployment dauert von einigen Sekunden bis mehrere Minuten; bei WebLogic (dem Marktführer(!)) z. B. funktioniert es nicht immer, und Herunter- und Hochfahren des Servers dauert lang. • Debugging ist schwierig, da die Beans unter der Kontrolle des Applikationsservers und nicht der Entwicklungsumbegung laufen. Hier helfen Debug-Ausgaben; das Projekt Log4J der Apache Foundation leistet gute Dienste. 47 5 Persistenzoptionen von A-Entitäten • Die EJB-Spezifikation ist noch im Fluss. Dies hat zur Folge, dass man entweder mit einem Stand entwickelt, der bald abgelöst wird, oder mit einer Spezifikation, für die die Tool-Unterstützung (sowohl Entwicklungsumgebungen als auch Applikationsserver) noch unzulänglich funktionieren. Die Hersteller von SEUs und Applikationsservern sind zu beschäftigt damit, die neuen Versionen zu implementieren und können ihre Software nicht ausreifen lassen. 5.1.2 Zusammenfassung Aus den Überlegungen der letzten Abschnitte kann folgender Schluss gezogen werden: Entity-Beans sind T-Software, sie dürfen keine Anwendungs-Logik enthalten, um jede Vermischung von A- und T-Software zu vermeiden und wartbare Systeme zu erstellen. Allerdings eignen sich Entity-Beans durchaus, um Datenbankzeilen zu repräsentieren und gleichzeitg transparenten Transaktionsschutz zu bieten. Deshalb können sie für diesen Zweck gewinnbringend eingesetzt werden. Die gleichen Überlegungen treffen auch auf die Aufteilung im Team zu: Beim naiven Einsatz von EJB muss jeder Entwickler Anwendungslogik und (EJB-) Technik kennen. Wie dies zu vermeiden ist, zeigt der folgende Abschnitt. 5.2 Persistenz über native Java-Objekte Im vorangegangenen Abschnitt wurden die Möglichkeiten untersucht, entsprechend dem Vorschlag der EJB-Spezifikation die Anwendungsobjekte als Entity-Beans zu implementieren. Ein zweiter Weg besteht darin, den Anwendungskern nicht aus EntityBeans aufzubauen, sondern native Java-Klassen zu programmieren, die nicht an die Restriktionen von Entity-Beans gebunden sind und eine längere Lebenserwartung haben, da sie nur an fachlichen Anforderungen ausgerichtet sind, nicht auch noch an technischen Gegebenheiten. Für die Implementierung der Persistenz von nativen Java-Klassen existiert eine Vielzahl an Möglichkeiten, von denen die wichtigsten im Folgenden beschrieben werden. 5.2.1 Ausprogrammieren mit JDBC Man kann JDBC-Code von Hand schreiben. Dieser Weg wird in erstaunlich vielen Projekten noch beschritten, obwohl er mühsam und fehleranfällig ist. Vorteil kann sein, dass so gezielte, projektspezifische Optimierungen eingebaut werden können. Zwei Varianten sind anzutreffen: 1. Jede Klasse kennt ihre Abbildung in die Datenbank, genauso wie sie beispielsweise ihre Repräsentation als String kennt. Das hat zur Folge, dass JDBC-Code 48 5.2 Persistenz über native Java-Objekte überall im Programm verstreut wird; Lesbarkeit und Wartbarkeit leiden erheblich. 2. Datenbankzugriffs-Code wird zentralisiert; es handelt sich also um eine speziell für das Projekt entwickelte Zugriffsschicht. Das kann praktisch sein, wenn entweder nur begrenzte Funktionalität erforderlich ist, oder ganz gezielte Optimierung des Datenbankzugriffs anders nicht möglich ist. Aber es drängt sich die Frage auf, warum nicht gleich eine der in den folgenden Abschnitten beschriebenen Zugriffsschichten herangezogen wird. Es ist zu erwarten, dass eine ausgereifte Zugriffsschicht – sie es ein Eigenbau oder ein kommerzielles Werkzeug – in der Performance nicht schlechter abschneidet, die Komplexität aber ohne eine solche unnötig hoch ist. Diesen Weg zu wählen, sollte sehr genau geprüft werden, denn sobald die A-Entitäten komplex genug werden, kommt man hier wegen der Fehleranfälligkeit schnell an die Grenzen. 5.2.2 Zugriffsschichten: QDI und TopLink Für die Abbildung von nativen Java-Klassen auf eine relationale Datenbank existiert eine Reihe von kommerziellen Werkzeugen wie z. B. TopLink, Avantis Persistency Bridge, JavaBlend, Cocobase; die OpenSource-Community hält weitere Werkzeuge bereit, z. B. DBGen, Jaws o. ä. Je nach Umfeld steht eventuell noch eine Eigenentwicklung zur Diskussion, wie z. B. bei sd&m Research das QDI. Eine Zugriffsschicht ist in Abbildung 5.2 dargestellt. Eine ideale“ Zugriffsschicht kapselt die Eigenschaften des Datenbanksystems so ge” schickt, dass man ihr von außen nicht ansieht, dass sich dahinter eine relationale Datenbank verbirgt. Um diesem Anspruch gerecht zu werden, muss sie folgende Punkte erfüllen. Die Anforderungen stammen einerseits aus dem Bereich der Objektorientierung, andererseits aus dem Datenbankbereich [He97, Kapitel 6], [Atk+89]. 1. Transparenz: Klassen werden durch ein Schlüsselwort als persistent markiert. Einzelobjekte können dynamisch als persistent markiert werden. 2. Datenbankobjekte werden mit einer objektorientierten Abfragesprache gelesen, die in die Programmiersprache integriert ist und alle SQL-Funktionen unterstützt (auch datenbankspezifische Erweiterungen). Die Abfragen werden vom Laufzeitsystem auf dynamisches SQL oder von einem Generator auf statisches SQL abgebildet. 3. Vererbung ist uneingeschränkt möglich. Abschnitt 3.1.4 führt dieses Problem aus. 49 5 Persistenzoptionen von A-Entitäten Abbildung 5.2: 3-Schicht-Architektur mit Datenbank-Zugriffsschicht. 4. Beim Lesen aus der Datenbank werden die Fremdschlüssel-Beziehungen zwischen den Tabellenzeilen auf Objektzeiger abgebildet. 5. Die Objektidentität wird beachtet. Siehe dazu Abschnitt 3.1.3. 6. Daten werden automatisch gespeichert, die Anwendung gibt nur die Transaktionsgrenzen an. 7. Die Zugriffsschicht unterstützt die optimistische oder pessimistische Sperrstrategie. Siehe hierzu Abschnitt 3.2.1. 8. Die Zugriffsschicht verbirgt Tabellen- und Spaltennamen vor der Anwendung und bildet Anwendungsdatentypen auf die elementaren Datentypen der Datenbank ab. 9. Ein DDL-Generator erzeugt die DDL (data definition language) aus der Definition der persistenten Klassen. 10. Man kann DDL und/oder DB-Zugriffe nachträglich optimieren, und zwar ohne Eingriff in den Anwendungskern. 11. Die Zugriffsschicht kann zwischen Client und Server verteilt werden. 12. Die Leistungen der relationalen Datenbank sind uneingeschränkt verfügbar (Views, Durchsatz, Mehrbenutzerfähigkeit, Berechtigungen, Abfragesprache, Transaktionen, Zugriffsoptimierung, Recovery, Constraints, Trigger, Stored Procedures, ...). 50 5.2 Persistenz über native Java-Objekte Diese Anforderungsliste lässt freilich offen, wie die Zugriffsschicht diese Ziele erreichen soll. Klar ist, dass ihr irgendwie der Zusammenhang zwischen Objektmodell und Tabellenmodell bekanntgegeben werden muss. Der Anwendung sollen zwar die konkreten Eigenschaften der Datenbank verborgen bleiben sollen, nicht jedoch die Tatsache, dass die Anwendungsdaten irgendwo abgelegt und von dort wieder gelesen werden. Diese Bemerkungen schränken die Forderungen an eine ideale Zugriffsschicht nicht ein. Einsatz einer Datenbankzugriffsschicht Prinzipiell ist abzuwägen zwischen den Kosten einer speziellen Lösung und denen einer Zugriffsschicht. Eine spezielle Lösung wie in Abschnitt 5.2.1 verursacht Entwicklungs- und Wartungskosten. Bei Einsatz einer Zugriffsschicht sind Lizenzgebühren fällig, bzw. bei Eigenentwicklung muss diese bezahlt werden; schließlich muss die Einarbeitungszeit in das Werkzeug berücksichtigt werden. QDI: Quasar Database Interface Im Rahmen des Quasar-Projektes bei sd&m ist die Datenbankzugriffsschicht Quasar ” Database Interface“ (QDI) entstanden. Der Entwurf des QDI wird geleitet durch die Komponenten-Definition wie in [Si01]: Die Schnittstellen sind so schmal wie möglich gestaltet, um technische Abhängigkeiten zu kapseln und um die Implementierung austauschbar zu halten. Sie drücken alle wichtigen Konzepte aus, die zum Entwurf eines datenbankbasierten Systems notwendig sind. Was kann das QDI Das QDI stellt als zentralen Teil der Schnittstelle das Workspace-Konzept zur Verfügung. Ein Workspace enthält die Methoden insert, update und delete zum Verwaltung von Objekten sowie beginTransaction, commit und rollback zur Verwaltung von Transaktionen. Dieses Konzept ermöglicht der Anwendung, den Transaktionsrahmen zu setzen; die Implementierung des QDI bildet diese Transaktionen auf Datenbanktransaktionen ab. So kann die Anwendung immer eine konsistente Sicht auf die Objekte behalten, mit denen sie gerade arbeitet. Das QDI arbeitet mit einem optimistischen Transaktionskonzept, d. h. dass ein commit auch mit einem Fehler beenden kann. Darauf muss die Anwendung zwar reagieren, aber der Workspace wahrt die konsistente Sicht auf die bearbeiteten Objekte. Das QDI bekommt das OR-Mapping deklarativ mitgeteilt und kapselt damit die JDBC-Aufrufe. Es ist in der Lage, diese aus dem OR-Mapping selbst zu erzeugen und nimmt so dem Programmierer einen erheblichen Teil der Arbeit ab. 51 5 Persistenzoptionen von A-Entitäten Die Generalisierung der Datenbank-Eigenschaften ermöglicht die Austauschbarkeit der darunterliegenden Datenbank genauso wie des verwendeten Treibers oder der gesamten Implementierung. Ein Datenbank-Update oder ein neuer Treiber erfordert keine Änderung der Anwendung; alle erforderlichen Anpassungen sind lokal auf die QDI-Implementierung begrenzt. Der Einsatz einer Schnittstelle, wie das QDI sie bietet, erleichtert auch die Strukturierung des Anwendungs-Codes. Betrachtet man den Einsatz irgendeiner Datenbank als gegeben, so ist eine Schnittstelle, die nur die essentiellen Datenbank-Funktionen nach außen trägt, 0-Software im Sinne der Software-Kategorien (vgl. Abschnitt 2.3.1). Wie baut man es ein? Bei der Verwendung einer Datenbank-Zugriffsschicht in einer objektorientierten Sprache sind prinzipiell vier Wege denkbar, um der Zugriffsschicht die Attribute zugänglich zu machen: 1. Erben von einer Superklasse bzw. Implementieren eines Interfaces mit Zugriffsmethoden auf die Attribute eines Geschäftsobjektes 2. Generische Mechanismen der Sprache (wie z. B. Reflection in Java) 3. Manipulation des Kompilats (der .class-Dateien bei Java, .o-Dateien bei C oder C++) 4. Generierung des Zugriffscodes aus Metainformationen (z. B. JavaBlend ) Das QDI arbeitet nach der ersten Methode, um generische und sprachabhängige Konstrukte zu vermeiden. Deshalb muss jede Klasse der Anwendungslogik, die persistent gemacht werden soll, ein Interface mit get- und set-Methoden zum Zugriff auf die Attribute implementieren. Die Verbindung zwischen Klassenmodell und Datenbankschema erfolgt mit Hilfe eines sogenannten Repository, das einmalig zur Laufzeit aufgebaut wird; Objekte werden mit Factories erzeugt. Anfragen werden über ein Query-Objekt gestellt, das mit einem Workspace verbunden ist. Die gefundenen Objekte werden automatisch in diesen Workspace eingestellt. So bilden Workspace und Query die Verbindung zur Datenbank. Existierende Implementierungen Als proof of concept“ wurde das QDI in verschiedenen Varianten implementiert: ” • Als Dummy-Implementierung, die nur im Hauptspeicher und nicht gegen eine Datenbank arbeitet 52 5.2 Persistenz über native Java-Objekte • Auf JDBC aufbauend; zur Zeit mit Treibern für Oracle, MS Access und MySQL • Auf JDBC in Verbindung mit dem Connection-Pool eines Applikationsservers basierend Die Tatsache, dass unterschiedliche Implementierungen erstellt wurden und tatsächlich austauschbar sind, zeigt die Mächtigkeit der Schnittstelle. Aktuell ist das QDI in acht Projekten von sd&m im Einsatz und beweist seine Praxistauglichkeit. TopLink Das Produkt TopLink der Firma WebGain (vormals ObjectPeople) stellt leistungsfähige Werkzeuge für die objekt-relationale Abbildung für Java zur Verfügung. Es arbeitet nicht-invasiv, d. h. im Programmcode der A-Entitäten muss der Einsatz von TopLink noch nicht explizit vorgesehen sein. Statt dessen baut TopLink auf Reflection auf. Im Wesentlichen besteht TopLink aus einem grafischen Werkzeug (Workbench) zur Eingabe der OR-Abbildung, und aus einem Laufzeitsystem, das die Zugriffe auf die Datenbank ausführt und die benötigten Objekte erzeugt. Mit der Workbench werden die Klassenstrukturen auf die Tabellenstrukturen abgebildet. Dieses Werkzeug kann ein neues Datenbank-Schema erzeugen oder ein vorhandenes verwenden. Als Ausgabe produziert es wahlweise einen Deskriptor im XML-Format, der die OR-Abbildung beschreibt und vom Laufzeitmodul eingelesen wird, oder es erzeugt Klassen, welche mit dem Projekt übersetzt werden und die OR-Abbildung zur Laufzeit vornehmen. So bleiben Datenbankschema und Klassenmodell möglichst unabhängig voneinander; die Klassen der Anwendungs-Entitäten bleiben unabhängig vom Werkzeug, da TopLink nicht-invasiv vorgeht. Zur Formulierung von Abfragen setzt TopLink mit der UnitOfWork ein Konzept ein, das den Workspaces des QDI ähnelt, aber nicht genau die gleiche Semantik hat bezüglich der Sicht auf die Objekte, die im Moment bearbeitet werden. 5.2.3 Exkurs: Objektorientierte Datenbanken Mit der weiten Verbreitung der objektorientierten Programmiersprachen haben sich auch objektorientierte Datenbanken entwickelt. Als Vorteile versprechen sie: • Eine nahtlose Integration der Datenbank in die objektorientiert programmierte Anwendung. Damit fällt das ganze OR-Mapping weg, und das spart natürlich Entwicklungszeit und vermeidet mögliche Fehler, Kosten und ggf. Einarbeitungszeit für ein OR-Mapping-Tool. 53 5 Persistenzoptionen von A-Entitäten • Das Datenbank-Schema wird durch das verwendete Klassenschema selbst beschrieben, also in der Programmiersprache der Anwendung. Der Designer muss also nicht zwei, sondern nur noch ein Datenschema entwickeln. • Alle Forderungen an eine ideale Zugriffsschicht“, s. Abschnitt 5.2.2, können ” kompromisslos erfüllt werden, da ja kein Bruch mehr zwischen der Repräsentation in der Anwendung und in der Datenbank existiert. • Effizienz: Eine allgemeine Aussage ist wohl nicht so einfach. Aber da in relationalen Datenbanken z. B. Vererbung nachgebildet werden muss und deshalb in der Regel mehrere DB-Zugriffe erforderlich sind, um ein Objekt vollständig auszulesen, sind OO-Datenbanken u. U. im Vorteil. • Der wichtigste Vorteil von objektorientierten Datenbanken ist die Möglichkeit, über Objektgeflechte zu navigieren. Diese Fähigkeit haben relationale Datenbanken nicht; das ist auch der Hintergrund, vor dem objektorientierte Datenbanken entstanden sind. Neben den Vorteilen von OO-Datenbanken gibt es einige Argumente, die auch bei Systemen mit objektorientierten Programmiersprachen nach wie vor für den Einsatz von relationalen Datenbanken sprechen: • Relationale Datenbanken sind besser verstanden 1. bei den Anwendungsentwicklern 2. auch bei den DB-Produkt-Herstellern. Daher sind RDBMS in der Regel ausgereifter (stabiler und effizienter), die unterstützenden Tools (Monitore, Backup-Unterstützung, Schemamigration, Indexverwaltung, usw.) sind besser (stabiler und besser auf Praxis-Anforderungen ausgerichtet) 3. bei Systemverwaltern und DBAs Diese Argumente sind zwar nicht prinzipieller Natur, aber in der Praxis sind es die schlagkräftigeren. Know-How bei den Anwendungsentwicklern ist ein sehr wichtiger Punkt, spätestens aber die Qualität der Tools kann entscheidend sein bei der Wahl des Systems. • Sie sind vielseitiger einsetzbar. Es ist viel einfacher, mit verschiedenen Anwendungen in unterschiedlichen Programmiersprachen auf die DB zuzugreifen. Mit OO-Datenbanken legt man sich auf eine Programmiersprache fest. • Sehr oft ist man sowieso schon auf eine relationale Datenbank festgelegt. Aus diesen Gründen haben objektorientierte Datenbanksysteme praktisch keine Bedeutung am Markt. 54 5.2 Persistenz über native Java-Objekte Persönliche Einschätzung des Autors: Zur Zeit können OO-Datenbanken bei kleineren, vielleicht auch mittleren Systeme mit kürzeren Design- und Implementierungsphasen ( time to market“) durchaus Vorteile bringen. Für große Systeme allerdings ” bieten sie genau nicht die Flexibilität, die sie mit der Objektorientierung versprechen. 5.2.4 Datenbankzugriff über Entity-Beans mit CMP Dieser Ansatz folgt einer Idee des Fast-Lane-Reader -Patterns3 in [Sun01b] (s. Abbildung 5.3: Der Zugriff auf die Datenbank erfolgt über zwei Wege: Zum Lesen wird nicht transaktionsgeschützt JDBC verwendet; zum Schreiben (sei es ein neu erzeugtes Objekt oder ein Update) werden Entity-Beans eingesetzt, um die Schreibaktionen in den Transaktionskontext des aktuellen Aufrufes zu setzen. So kann das Lesen von Objekten oder von Listen von Objekten ohne Sperren auf der Datenbank oder im Applikationsserver erfolgen. Besonders bei Anwendungen mit hohen Client-Lasten ist dies von hoher Bedeutung, denn z. B. Suchanfragen mit großen Ergebnismengen werden nicht zum Performance-Engpass, weil nicht für alle gefundenen Datenbankeinträge gleich Entity-Beans erzeugt werden, sondern nur die Beans, mit denen der Client tatsächlich arbeiten möchte. Lazy-loading-Strategien werden möglich und können fein gesteuert werden. Zum schreibenden Zugriff wird der Transaktionsschutz des EJB-Containers ausgenutzt. Fast-Lane Reader werden in der Regel so implementiert, dass vor dem Benutzer verborgen bleibt, welcher Zugriffsweg gewählt wird. Sie liefern ein Value Object an den Aufrufer zurück. Abbildung 5.3: Fast-Lane Reader Pattern, wie es bei Sun [Sun01b] vorgestellt wird. Vorteil dieses Weges ist, dass nur EJB-Hausmittel“ eingesetzt werden und einerseits ” schneller, nicht gesperrter Zugriff erreicht wird, solange das von der Anwendung her 3 früher bekannt als Bimodal Data Access-Pattern 55 5 Persistenzoptionen von A-Entitäten möglich ist, schreibender Zugriff aber dennoch transaktionsgeschützt stattfindet, ohne dass der Programmierer mit den Details des Transaktionsschutzes konfrontiert wird. Nachteilig ist der doppelte Entwicklungsaufwand: Obwohl der Container verwendet wird, um auf die Datenbank zuzugreifen, muss die OR-Abbildung zusätzlich in JDBC-Code gefasst werden. Dieser Nachteil kann aufgehoben werden bei einfach strukturierten Daten, wenn es möglich ist, den Zugriffscode zu generieren. Insofern wird das Data Access Object-Pattern (DAO) aus [Alu01] genau auf den Kopf gestellt, da nicht Entity-Beans über ein DAO mit der Datenbank verbunden werden, sondern umgekehrt Entity-Beans als DAO-Implementierung zur Abbildung von A-Entitäten verwendet werden. Die Idee der Entity-Beans als persistente Anwendungsobjekte wird damit untergraben, sie werden nur noch als Datencontainer verwendet. Diese Erkenntnis liegt im Prinzip auch schon dem Value-Object-Pattern [Alu01] zugrunde. 56 6 Persistenz über ein neutrales Interface 6.1 Entwurf wartbarer Systeme mit EJB Der prinzipielle Aufbau betrieblicher Informationssysteme wurde bereits in Kapitel 2 betrachtet. An dieser Stelle sei nochmals auf Abbildung 2.1 hingewiesen und an die Begriffe Anwendungskern (AWK), Anwendungs-Entität (AE) und AnwendungsEntitäten-Verwalter (AV) erinnert. Diese Darstellung berücksichtigt aber nur die fachliche Komponentenbildung, nicht jedoch die technischen Konstruktionen, die für ein Funktionieren noch erforderlich sind: Die Speicherung der Daten in einer Datenbank, die Kommunikation mit mit Nachbarsystemen und die Darstellung der Benutzeroberfläche. 6.1.1 Innovationszyklen betrieblicher Informationssysteme In betrieblichen Informationssystemen sind neben den A-Bausteinen auch T-Teile erforderlich. Entscheidend ist, diese Teile sauber voneinander zu trennen. Informationssysteme unterliegen zwei unabhängigen Innovationszyklen: Sowohl die technischen als auch die fachlichen Anforderung bzw. Gegebenheiten können sich ändern. Ist das der Fall, so sollte nur ein genau definierter Teil des Quelltextes von den Änderungen betroffen sein. Werden hingegen A- und T-Aspekte vermischt (AT-Software), so müssen diese Teile sowohl bei fachlichen als auch bei technischen Änderungen überarbeitet werden. Dies ist zu vermeiden. Daraus stellt sich die Frage, wie die technischen mit den fachlich motivierten Bausteinen zu verbinden sind, ohne A- und T-Software unzulässig zu vermischen und gleichzeitig keine Performanz-Engpässe zu schaffen. 6.1.2 Entkoppelung des Anwendungskerns vom Datenbankzugriff über ein neutrales Interface Aus diesem Wunsch ergibt sich eine Architektur, wie sie in Bild 6.1 dargestellt ist. Der Anwendungskern oder die Anwendungskomponente ist genau nach Abbildung 2.1 aufgebaut. Für jede Komponente existieren ein oder mehrere A-Verwalter, die alle 57 6 Persistenz über ein neutrales Interface A-Entitäten dieser Komponente erzeugen, aus der Datenbank lesen und in die Datenbank schreiben. Für den Datenbankzugriff arbeiten sie gegen ein neutrales Interface, das die technischen Eigenschaften der Datenbank vor ihnen verbirgt. Abbildung 6.1: Entkopplung der Anwendungskomponente vom Datenbankzugriff Das neutrale Interface ermöglicht es, den Datenbankzugriff an einer zentralen Stelle zu konzentrieren. Es kann über einen der in Abschnitt 5.2 vorgestellten Wege in Form eines Data Access Object (DAO) implementiert werden. So wird bezüglich des Datenbankzugriffs das Geheimnisprinzip (information hiding, [Par72]) umgesetzt, und gleichzeitig stehen nach wie vor alle erwünschten Dienste des EJB-Applikationsservers zur Verfügung. In Abbildung 6.1 ist das neutrale Interface unabhängig von der Technik, aber von der Anwendungslogik mitgeprägt, und das DAO wird als R-Software implementiert. Es ist auch möglich, das neutrale Interface generisch als 0-Software zu implementieren, das DAO ist dann reine T-Software. Das neutrale Interface für eine Kundenverwaltung könnte wie in Listing 6.1 entworfen werden. Listing 6.1: Erste Vorstellung des neutralen Interfaces public interface DAOCustomer { // Suchen: CustomerData findCustomerById (String id); Collection findCustomerByName (String lastName); 58 6.1 Entwurf wartbarer Systeme mit EJB : : // Aendern: void updateCustomer (CustomerData customer) throws MyOptimisticLockException; // Loeschen: void removeCustomer (String id); void removeCustomer (CustomerData customer) throws MyOptimisticLockException; } Der Anwendungskern wird in nativen Java-Klassen implementiert. Der Entwurf ist frei von irgendwelchen Restriktionen, denen beispielsweise Entity-Beans ausgesetzt wären. Er kann direkt nach den Prinzipien der Komponentenorientierung im Grobdesign bzw. der Objektorientierung im Feindesign erfolgen. Mehrwert von EJB Der Entwurf einer Architektur wie in Abbildung 6.1 wirft die Frage auf: Welcher Mehrwert bleibt dann noch von EJB? Die Antwort ist einfach: Man bekommt einen Applikationsserver, der u. a. folgende Dienste bietet: • Ressourcen-Management: Pooling von Datenbankverbindungen, Pooling von Objekten, um häufiges und teures Erzeugen und Löschen von Objekten zu vermeiden, Threadverwaltung, um einer hohen Clientzahl gerecht zu werden. • Erleichterung des Systembetriebs: Monitoring und Wartung des laufenden Systems, Lastverteilung, Ausfallsicherheit. • ORB (Object Request Broker) zur Verteilung der Anwendung, MOM (Message Oriented Middleware) zur asynchronen Kommunikation, Transaktions-Monitore, Web-Integration. Diese Dienste müsste man ohne den Applikationsserver aus vielen Einzelprodukten zusammenstellen, womit der Konfigurationsaufwand für das ganze System steigen würde. Mehrwert der vorgestellten Architektur Parnas et al. gehen in [PCW83] auf die Kosten von Entwicklung und Wartung ein: The primary goal of the decomposition into modules is reduction of software cost ” by allowing modules to be designes and revised independently.“ 59 6 Persistenz über ein neutrales Interface Ein derart aufgebautes System erreicht genau dieses Ziel, indem es die Forderungen von Parnas nach dem Geheimnisprinzip [Par72] und von Dijkstra [Dij76] nach der Trennung von Zuständigkeiten auf Architekturebene umsetzt. Muss eine Änderung oder Erweiterung in das System eingefügt werden, so ist nur ein lokal begrenzter Teil des Systems davon betroffen. Es ist leichter möglich, Optimierungen am Datenbankzugriff durchzuführen, denn die Auswirkungen von Änderungen sind lokal begrenzt. A-Entitäten sind nicht von einer Änderung im Datenbankschema oder -Zugriffsmechanismus betroffen. Ebenso muss der Datenbank-Zugriff nicht überprüft werden, wenn fachliche Funktionalität geändert wird. Im Idealfall bleibt völlig transparent, mit welchen Mitteln der Datenbankzugriff programmiert wird: Wird alles selbst erstellt, oder steht ein Werkzeug (wie TopLink) zur Verfügung? Wenn sich eine solche Entscheidung im Projektverlauf ändert, betrifft dies nur einen Teil des Programmcodes und nur einen Teil des Teams. Diese Vorteile kommen nicht nur dem Programmcode an sich zugute, sondern haben einen hohen Einfluss auf die Teamarbeit. Es ist leichter, das Team in Datenbank- und Anwendungs-Experten aufzuteilen als in Teilteams für verschiedene Komponenten, die sich dann alle um Datenbankzugriff und um Anwendungslogik kümmern müssen. Daher trifft dieser Vorteil nicht erst in der Wartungsphase eines Projektes zu, sondern schon während der Entwicklungszeit, da die Aufteilung des Teams entsprechend des vorhandenen Wissens besser möglich ist. Abbildung 6.1 sieht zwar etwas komplexer aus als die entsprechenden Darstellungen für reine“ EJB-Architekturen, aber dieser Eindruck täuscht: Diese Architektur führt ” nicht dazu, dass mehr Code benötigt wird. Sie legt lediglich fest, wohin der jeweils für eine bestimmte Aufgabe benötigte Code geschrieben wird. 6.2 Anforderungen an das neutrale Interface Der entscheidende Punkt dieser Architektur ist die Gestaltung des neutralen Interfaces. Dazu müssen zunächst die Anforderungen an ein solches Interface geklärt werden: • Der Anwendungskern wird von technischen Details des Datenbankzugriffs entkoppelt: Die Mächtigkeit der Datenbank bleibt genauso verborgen wie der eingesetzte Zugriffsmechanismus. • Es enthält keine technik-getriebenen Elemente, ist also A- oder 0-Software. • Der Anwendungskern darf nicht von den Einschränkungen der EJB-Vorgaben betroffen sein. • Die Dienste, die der EJB-Applikationsserver bietet, müssen aber dennoch zugänglich sein. 60 6.3 Design des neutralen Interfaces 6.3 Design des neutralen Interfaces Diese Anforderungen erinnern an das Data Access Object-Pattern [Sun01b] (siehe Abbildung 6.2, die [Sch01] entnommen ist), welches auf dem Bridge-Pattern von Gamma et al. [Gam94] beruht. Das DAO-Pattern will unterschiedliche Datenbank-Techniken kapseln und vereinheitlichen; das neutrale Interface kapselt verschiedene Zugriffsmechanismen. Die Idee des DAO wird aufgegriffen und erweitert um das in Quasar geforderte Konzept, an Schlüsselstellen immer gegen Interfaces und nicht gegen Klassen direkt zu programmieren. Nur durch die Definition eines technikunabhängigen Interfaces kann der darauf zugreifende Code, in diesem Fall ein A-Verwalter, per Konstruktion technikunabhängig sein. Die technikabhängigen Bausteine sind leicht identifizierbar; es sind die Klassen, die das Interface implementieren. Abbildung 6.2: Sequenzdiagramm für ein DAO Für die Schnittstelle des DAO existieren zwei Varianten: • Die Schnittstelle ist neutral (0-Software) und die Implementierung nur technikgetrieben mit generischem Zugriff auf die Anwendungsdaten (T-Software). So ist das DAO-Pattern in [Alu01] beschrieben. 61 6 Persistenz über ein neutrales Interface • Die Schnittstelle wird nur von der Anwendug motiviert (A-Software), die Implementierung ist technikgetrieben mit Wissen über die Anwendungsdaten (RSoftware). So findet sich das DAO-Pattern in [Sun01b]. Dies ist der in Abbildung 6.2 dargestellte Ansatz; Listing 6.1 ist nach dieser Variante aufgebaut. Diese Entscheidung hat keine Auswirkungen auf die Architektur des Gesamtsystems, wohl aber auf die Gestaltung des neutralen Interfaces. Änderungen am neutralen Interface erfordern Anpassungen im A-Verwalter. Das ist zwar auch lokal begrenzt und betrifft nicht den gesamten Anwendungskern, sollten aber dennoch vermieden werden. Deshalb sollte diese Entscheidung früh getroffen werden, bevor mit der Codierung der A-Verwalter begonnen wird. In [Sun01b] und auch in [Alu01] wird vorgeschlagen, das Business Object als EntityBean zu implementieren, welches über das DAO in die Datenbank geschrieben wird. An dieser Stelle wird hier von dem Pattern abgewichen. Statt dessen sollen AEntitäten über das DAO in die Datenbank abgebildet werden. Es kann also die Idee und der Ablauf aus Bild 6.2 übernommen werden, aber das Muster wird für native Java-Objekte verwendet. 6.4 Implementierung des neutralen Interfaces Das neutrale Interface ist dazu da, den Anwendungskern vom Datenbankzugriff zu trennen. Für die Implementierung des Datenbankzugriffs gibt es eine Vielzahl an Möglichkeiten. Hier wird auf Abschnitt 5.2 zurückgegriffen, der verschiedene Wege zur Abbildung von nativen A-Entitäten auf die Datenbank im Detail vorgestellt hat; ihre Einsatzmöglichkeiten zur Implementierung des neutralen Interfaces werden hier diskutiert. 6.4.1 Spezifische JDBC-Lösung Wie schon in Abschnitt 5.2.1 dargelegt, ist der naive Ansatz, für jede A-Entität das OR-Mapping in JDBC-Code zu fassen und einzeln auszuprogrammieren. Im generischen Fall (neutrales Interface als 0-Software, DAO als reine T-Software) handelt es sich um nichts anderes als eine projektspezifische Zugriffsschicht, wie sie auch sonst (d. h. ohne EJB) eingesetzt würde. Wird sie zu speziell gehalten, könnte die Pflege aufwändig werden. Ist der Ansatz dagegen zu allgemein, könnte die Entwicklung teurer werden als die Verwendung einer vorhandenen Zugriffsschicht. Listing 6.2 zeigt für eine Implementierung für das Interface in Listing 6.1. Muss solcher Code manuell entwickelt werden, schleichen sich schnell Fehler ein, die der Compiler nicht finden kann und die womöglich erst zur Laufzeit auftreten. Hier handelt es sich um AT-Software, bestenfalls um R-Software. 62 6.4 Implementierung des neutralen Interfaces Listing 6.2: Beispiel für eine JDBC-Implementierung import java.sql.*; public class DAOCustomerJDBC implements DAOCustomer { public CustomerData findById (String customerId) { Connection dbc; Statement stmt; try { dbc = myDataSource.getConnection ( "myLogin", "myPassword"); stmt = dbc.prepareStatement ( "SELECT id, version, lastname, firstname " + "FROM customer WHERE id = ?"); stmt.setString (1, id); ResultSet rs = stmt.executeQuery (); if (rs.next ()) { CustomerData cd = new CustomerData (); cd.setId (rs.getString ("ID")); cd.setVersion (rs.getInt ("VERSION")); cd.setLastName (rs.getString ("LASTNAME")); cd.setFirstName (rs.getString ("FIRSTNAME")); // usw. } return cd; } finally { dbc.close (); } } // ... } 6.4.2 Einsatz einer Datenbankzugriffsschicht Der Vorteil von Zugriffsschichten wurde in Abschnitt 5.2.2 hervorgehoben. 63 6 Persistenz über ein neutrales Interface Umsetzung mit QDI Entstanden ist das QDI ohne die Idee, in Zusammenhang mit einem Applikationsserver verwendet zu werden. Aber durch die Verwendung des Datenbank-ConnectionPools eines J2EE-Servers kann das QDI problemlos mit dem Applikationsserver zusammenarbeiten und dessen Transaktionsverwaltung ausnutzen. Das QDI schreibt mit seinem Workspace-Konzept nur die Daten in die Datenbank zurück, die tatsächlich geändert wurden. Der Applikationsserver kann am Ende seines Transaktionsrahmens entscheiden, ob die Datenbanktransaktion mit commit oder rollback abgeschlossen wird. Die Vorteile sind: + Bewährtes und mächtiges OR-Mapping + OR-Mapping an genau einer Stelle konzentriert + Weitere mächtige Konzepte, wie z. B. Unterstützung für große Treffermengen, sind vorhanden und können verwendet werden. Listing 6.3: Beispiel für eine Implementierung mit dem QDI import com.sdm.quasar.qdi.*; public class DAOCustomerQDI implements DAOCustomer { public void initRepository (IQRepository rep) { QRecordDef customer = new QRecordDef (CustomerData.QNAME, "CustomerTable", new CustomerDataFactory()); customer.setIdentifier (CustomerData.ID, "id", "id", new QStringFieldDef (32)); customer.setEntryDef (CustomerData.VERSION, "version", "version", new QIntegerFieldDef (32), 1); customer.setEntryDef (CustomerData.FIRSTNAME, "firstname", "firstname", new QStringFieldDef (32), 1); customer.setEntryDef (CustomerData.LASTNAME, "lastname", "lastname", new QStringFieldDef (32), 1); rep.register (CustomerData.QNAME, customer); } public CustomerData findById (String customerId) { IQuery query = ws.createQuery(CustomerData.QNAME, 0); 64 6.4 Implementierung des neutralen Interfaces query.addEquality("id", customerId); ForwardIterator it = query.start(); if (it.atEnd()) { return null; } CustomerData customer = (CustomerData) it.get(); return customer; } // ... } Das Code-Beispiel 6.3 zeigt in der Methode initRepository die Initialisierung der OR-Abbildung. Sie wird vom QDI verwendet, um SQL-Befehle zu erzeugen und die benötigten Objekte zu erzeugen. Die Beschreibung des OR-Mappings ist getrennt von der Formulierung der Abfrage, welche sehr kompakt formuliert werden kann. Umsetzung mit TopLink Der Einsatz eines bewährten Werkzeuges wie TopLink hat einige Vorteile: Als Datenbankzugriffsschicht konzentriert es die komplette OR-Abbildung an einer Stelle; das OR-Mapping ist sehr mächtig; es ist in der Lage, auch komplexe A-Entitäten direkt in die Datenbank abzubilden. Wenn schon ein Werkzeug wie TopLink zur Verfügung steht, drängt sich die Frage auf, warum es nicht gleich als Container eingesetzt wird, um komplexer strukturierte Beans zu erstellen. Hier sei deshalb nochmals auf die Abschnitte 5.1.2 und 6.1.2 verwiesen. Ziel ist es, die A-Komponenten frei von technischen APIs zu implementieren. Mit TopLink als Container würde diese Ziel nicht erreicht. 6.4.3 Umsetzung mit CMP Wird ein System als EJB-Anwendung erstellt, bietet sich die Implementierung des DAO nach dem Fast-Lane Reader-Pattern an, das in Abschnitt 5.2.4 beschrieben wurde. In Systemen, in denen es sinnvoll ist, einzelne Tabellenzeilen als Entity-Beans darzustellen und keine aufwändigen Objektgeflechte benötigt werden, bewährt sich diese Lösung im produktiven Einsatz. Bei solchen Systemen ist es möglich, sowohl die benötigten Entity-Beans als auch den typischerweise benötigten JDBC-Zugriffscode aus dem Datenbankschema zu generieren; nur noch an wenigen Stellen ist gezielte Handarbeit erforderlich. 65 6 Persistenz über ein neutrales Interface Abbildung 6.3: Sequenz-Diagramm der Implementierung mit dem Fast-Lane-ReaderPattern Abbildung 6.3 zeigt einerseits, dass zum Lesen und zum Schreiben zwei verschiedene Wege gewählt werden, und es zeigt andererseits, dass diese Tatsache hinter einem DAO versteckt wird. Für den Client ist es transparent, dass Lesen und Schreiben unterschiedlich implementiert sind. 6.5 Beurteilung des Entwurfs Die verschiedenen Möglichkeiten, den Datenbankzugriff zu implementieren, werden unter den Gesichtspunkten verglichen, die sich aus der Aufgabenstellung dieser Arbeit ergeben: • Wartbarkeit des Codes • Unabhängigkeit von aktueller EJB-Spezifikation und von herstellerspezifischen Features • Effizienz bei komplexen Komponenten, komplexen Anwendungsfällen (Transaktionen), bei großen Datenbanken, bei vielen Benutzern 6.5.1 Wartbarkeit des Codes Die Wartbarkeit von Programmcode ist umso leichter, je unabhängiger verschiedene Programmteile voneinander sind, da notwendige Änderungen entweder der fachlichen Anforderungen oder der technischen Gegebenheiten nur lokale Auswirkungen haben. Diese Unabhängigkeit wird durch die hier diskutierte Architektur erreicht. Die Variante über den Fast-Lane Reader“ schneidet in diesem Punkt aber schlechter ab, ” 66 6.5 Beurteilung des Entwurfs denn es müssen zwei unterschiedliche Wege zum Lesen und zum Schreiben von Daten programmiert werden. Die Lösungen mit einer Zugriffsschicht sind kompakter. Weiterhin hängt die Wartbarkeit schlicht von der Lesbarkeit des Quelltextes ab. Auch hier liegen die Zugriffsschichten vorne, denn die Aspekte Objekterzeugung, OR-Abbildung und Transaktionsabbildung sind klarer und kompakter zu formulieren. JDBC-Code, der für den Fast-Lane Reader erforderlich ist, vermischt diese Dinge etwas mehr. Dies wird bei komplexeren Abbildungen der Datenmodelle oder der Transaktionen zu einem Nachteil. Andererseits hängt die Lesbarkeit nicht nur von der Kompaktheit ab, sondern auch davon, wieviel Kenntnisse der Leser (also der Programmierer) mitbringen muss. Der Einsatz einer Zugriffsschicht verlangt in Entwicklung und Pflege detailliertes Wissen über Benutzung, Nebeneffekte und Performance-Engpässe des verwendeten Produkts. Die Lösung mit Entity-Beans könnte im Vorteil sein, da sie nur mit den Standardmitteln von EJB arbeitet. 6.5.2 Unabhängigkeit von der verwendeten Basistechnologie Unabhängigkeit von der Basistechnologie ist im Prinzip auch ein Aspekt von Wartbarkeit. Die Applikation ist leichter über Versionswechsel der eingesetzten Basissysteme (Datenbanken, Applikationsserver) zu pflegen, je besser hersteller- und versionsspezifische Besonderheiten konzentriert und gegen den Rest der Anwendung abgekapselt sind. Auch hierfür leistet die vorgestellte Architektur ihren Beitrag. Das wichtige Ziel, den anwendungsorientierten Programmcode unabhängig von Datenbank und Applikationsserver zu halten, wird erreicht. 6.5.3 Effizienz Welche Variante bessere Antwortzeiten ermöglicht, hängt in hohem Maße von den Anforderungen an das System ab. Jede Variante kann so implementiert werden, dass keine unnötigen Performance-Engpässe erzeugt werden. Sie verhalten sich aber unterschiedlich, und es kann keine allgemeine Reihenfolge angegeben werden. Abschnitt 7.2.6 zeigt ein Beispielszenario für die vorgestellten Implementierungen, das als Entscheidungshilfe für ein konkretes Projekt verwendet werden kann. 6.5.4 Zusammenfassung Die Vorteile der hier dargestellten Architektur sind: + Die Anwendungskomponenten können frei entworfen werden, unabhängig vom Datenbankschema, vom Zugriffsmechanismus und von den Fähigkeiten des Datenbanksystems. Komplexes OR-Mapping ist möglich mit Vererbung, abhängigen Objekten usw. 67 6 Persistenz über ein neutrales Interface + Die Teamaufteilung kann nach Expertenwissen erfolgen. + Im DAO gekapselter Datenbank-Zugriffscode: Optimierungen sind leichter zu implementieren (lazy-/eager loading, Denormalisierung, usw.) Als Nachteil könnte man anführen: − Da der Ansatz komplexer aussieht als Lehrbuchbeispiele, ist zunächst eine psychologische Hemmschwelle zu überwinden, diesen Ansatz doch zu verfolgen. Es könnte passieren, dass der Kunde erst vom Nutzen zu überzeugen ist. Zur Wahl der Implementierung des DAO sind hier nochmal die drei vorgestellten Varianten gegenübergestellt. Ansatz Spezielle Lösung JDBC- Fast-Lane Reader Zugriffsschicht 68 Indikatoren sollte bei größeren Projekten nicht verwendet werden. Einfaches OR-Mapping, spezielle Optimierungen auf JDBC-Ebene erwünscht, keine Zugriffsschicht verfügbar Komplexes OR-Mapping, großes Projekt, lange Lebensdauer Vorteile Nur Standardmittel von EJB verwendet, kein zusätzlicher Einarbeitungsaufwand in ein weiteres Werkzeug Vielseitiges OR-Mapping möglich, keine Einschränkung beim Entwurf der A-Entitäten, Optimierungen und Features der Zugriffsschicht nutzbar 7 Fallstudie: Telekom-Billing-System Das Telekom-Billing-System (TBS) ist entstanden, um ein vereinfachtes, aber dennoch realitätsnahes Informations-System umzusetzen, das bei sd&m Research zu Forschungs- und internen Schulungszwecken eingesetzt werden kann. Es setzt die Ideen der Standard-Architektur Quasar ein. Für dieses System existieren verschiedene Implementierungen; im Rahmen dieser Diplomarbeit wurde eine weitere, auf EJB basierende Implementierung neu erstellt. Parallel hierzu ist eine weitere Diplomarbeit im Bereich GUI (Web und nativ) entstanden [Ha01], die auf dem hier entwickelten Anwendungskern aufsetzt. So konnte gezeigt werden, dass dieser Ansatz in eine Gesamtarchitektur integrierbar ist. Mit dieser Anwendung sind auch die Screenshots in diesem Kapitel entstanden. Da es sich beim TBS um einen extra für diese Arbeit angefertigten Prototyp handelt, wurde das Modell so gewählt, dass bestimmte technische Herausforderungen gezielt gesucht und ihre Lösung demonstriert werden können. Dazu gehören: • Wahl von übersichtlichen Komponenten, die aber größer als eine einzelne Klasse sind • Vererbung: OR-Mapping und polymorphe Suche • Umgang mit Beziehungen und mit abhängigen Objekten • Lazy vs. eager loading • Gestaltung der Schnittstelle und der Transferobjekte • Interaktive Anwendungsfälle und Batch-Verarbeitung • Optimistisches Sperrkonzept Die Liste ließe sich erweitern, z. B. um lang laufende Transaktionen und große Treffermengen. Im Interesse der Übersichtlichkeit wurde aber der Umfang des Prototypen eingegrenzt. Zunächst wird das fachliche Modell beschrieben, anschließend werden zwei unabhängige Implementierungen vorgestellt und mit ihren Vor- und Nachteilen einander gegenübergestellt. 69 7 Fallstudie: Telekom-Billing-System 7.1 Überblick: Die Komponenten des TBS Das Telekom-Billing-System ist dazu gedacht, für einen Telekommunikationsanbieter Rechnungen zu erstellen. Eingangsdaten sind dazu die Gesprächsdaten (CallDataRecords), die von den Ortsvermittlungsstellen erfasst und an dieses System weitergegeben werden. Zum TBS gehören eine Kundenverwaltung, eine Rechnungsverwaltung und eine Tarifkomponente. Abbildung 7.1 stellt die Komponenten schematisch dar. Abbildung 7.1: Überblick über die Komponenten des TBS 7.1.1 Kundenverwaltung Die A-Entitäten Ein Kunde kann entweder Privat- oder Geschäftskunde sein. Im objektorientierten Modell wird dies durch Vererbung ausgedrückt. Ein Kunde sei abstrakt und tritt entweder in der Ausprägung Privatkunde oder Geschäftskunde auf. Damit wird eine polymorphe Suche benötigt. Gemeinsame Attribute der Kunden sind: • Kundennummer: Dies ist die eindeutige ID (Primärschlüssel). • Name 70 7.1 Überblick: Die Komponenten des TBS • Telefonnummern: u. U. mehrere. Die Telefonnummer ist keine eindeutige Identifikation, da eine Nummer über die Zeit an mehrere Kunden vergeben werden kann. Die Suche über die Telefonnummer ist aber möglich und führt zu einem eindeutigen Ergebnis. • Vereinbarter Tarif Unterschiedlich bei Privat-/Geschäftskunden sind die folgenden Attribute: Privatkunde zwei Adressen: Heimat- und Rechnungsanschrift Geschäftskunde n Niederlassungen, 1 Stammsitz, also n + 1 verschiedene Anschriften Ansprechpartner: einer pro Geschäftskunde Abbildung 7.2 zeigt die A-Entitäten der Kundenverwaltung als Klassendiagramm. Abbildung 7.2: Klassendiagramm der A-Entitäten der Kundenverwaltung Der A-Verwalter Der A-Verwalter der Kundenkomponente ist sowohl für die Privatkunden als auch für die Geschäftskunden zuständig. 71 7 Fallstudie: Telekom-Billing-System Die Anwendungsfälle Name des AWF Kunde suchen Kunde suchen Parameter Kunden-Nummer Nachname Kunde neu eintragen Kunde löschen Kunde ändern KundenTransferstruktur Kunden-Nummer KundenTransferstruktur Ergebnis Kunde polymorphe Liste von Kunden, die leer sein kann Exception Nicht gefunden keine Primärschlüssel schon vorhanden keine Optimistischer Sperrkonflikt Die Anzeige eines Kunden ist in Abbildung 7.3 abgebildet. Abbildung 7.3: Pflege eines Kundendatensatzes Wird ein Kunde über seinen Nachnamen gesucht, so werden alle zutreffenden Kunden in einer Auswahlliste angezeigt (s. Abbildung 7.4). Es kann ein Kunde gewählt werden, mit dem dann wie in Abbildung 7.3 weitergearbeitet werden kann. Methoden, die z. B. den vereinbarten Tarif eines Kunden ermitteln, sind keine Anwendungsfälle, sondern Methoden auf der A-Entität bzw. Attribute des Transferobjektes. 72 7.1 Überblick: Die Komponenten des TBS Abbildung 7.4: Suche eines Kunden über den Nachnamen 7.1.2 Rechnungsverwaltung Die A-Entitäten Eine Rechnung hat folgende Eigenschaften: • Sie besitzt einen Abrechnungszeitraum, • eine eindeutige Rechnungs-Nummer, • ist einem Kunden zugeordnet und • ist aufgeschlüsselt nach den Telefonnummern des Kunden. • Auf Basis des vom Kunden gewählten Tarifs setzt sie sich zusammen aus – der Grundgebühr und – einer Liste von Rechnungsposten: den einzelnen Gesprächen. Sie wird erzeugt aus den Gesprächsdaten, die von der der Ortsvermittlungsstelle in die Datenbank eingetragen werden.1 Bei der Rechnungserstellung wird lesend auf diese Daten zugegriffen. 1 Im Beispielsystem sind diese Daten natürlich zufällig generiert. 73 7 Fallstudie: Telekom-Billing-System Abbildung 7.5: Klassendiagramm der Rechnungskomponente Die einzige A-Entität der Rechnungsverwaltung ist die Rechnung. Weiterhin gibt es noch die Rechnungsposten und die eben erwähnten Gesprächsdaten; das sind aber abhängige Objekte und keine A-Entitäten. Abbildung 7.5 zeigt die Datenstruktur innerhalb der Rechnungsverwaltung. Die Anwendungsfälle Neben den einfachen Anwendungsfällen, eine Rechnung zu suchen oder zu löschen gibt es hier auch einen A-Fall mit etwas mehr algorithmischer Komplexität: Das Erstellen der Rechnung. Dazu werden die Gesprächsdaten gesucht, die in den Abrechnungszeitraum fallen, um daraus die Rechnung zusammenzustellen. Name des AWF Rechnung suchen Rechnung suchen Parameter Rechnungs-ID Kunden-ID Rechnung erstellen Rechnung löschen Kunden-ID, Zeitraum Rechnungs-ID Ergebnis Rechnung Liste von Rechnungen, die leer sein darf keins keins Exception Nicht gefunden keine Rechnung schon vorhanden Nicht gefunden CallDataRecord Die Gesprächsdaten werden CallDataRecord genannt und enthalten die anrufende und die angerufene Telefonnummer sowie den Beginn und die Dauer des Gesprächs. 74 7.2 Implementierung Diese Informationen sind erforderlich, um den Preis eines Gespräches zu ermitteln. CallDataRecords werden nicht als eigene Komponente aufgefasst. Die Annahme ist, dass diese Daten von einem Nachbarsystem in unsere Datenbank eingetragen werden. Sie werden vielmehr nur von der Rechnungskomponente benötigt, welche auch genau auf die CallDataRecords abgestimmt ist. Rechnungsposten haben z. B. eine sehr ähnliche Struktur. Deshalb werden CallDataRecords der Rechnungskomponente zugeordnet. 7.1.3 Tarif Das Telekom-Unternehmen bietet verschiedene Tarife an, von denen jeder Kunde einen wählt. Sie werden beschrieben durch: • Name/Bezeichnung • Grundgebühr • Zeittaktung (z. B. 60/1 oder 60/60 usw.) • Preise pro Tarifsegment (Tag/Abend/Nacht/Wochenende etc) Die Tarifkomponente verwaltet alle Tarife; die Preisberechnung eines Telefongesprächs erfolgt durch eine A-Entität des betreffenden Tarifs. 7.2 Implementierung Die Implementierung besteht aus einem Anwendungskern mit Anwendungsfällen, AEntitäten und A-Verwaltern, den Transferobjekten und dem Zugriff auf die Datenbank. Es existiert eine Schnittstelle für das DAO; für das TBS wurde diese Schnittstelle fachlich gestaltet. Denkbar wäre auch eine neutrale Gestaltung. Für das DAO werden zwei unabhängige Implementierungen vorgestellt: Eine mit Entity-Beans und dem Fast-Lane-Reader-Pattern, die andere mit dem QDI. Des weiteren existieren jeweils noch Dummy-Implementierungen, die es erlauben, den Anwendungskern oder die GUI zu testen, während parallel die echte Datenbankanbindung entwickelt wird. 7.2.1 Überblick über die Klassen des Anwendungskerns Anwendungsfälle Jeweils für die Anwendungsfälle einer Komponente existiert ein stateless SessionBean. Dies stellt eine Vereinfachung gegenüber der vorgestellten Klassenstruktur dar, 75 7 Fallstudie: Telekom-Billing-System da die Anwendungsfälle direkt im Session-Bean implementiert sind und nicht die Aufrufe, wie in Abbildung 6.1 dargestellt, an eine native Klasse weitergereicht werden. Diese Vereinfachung wurde vorgenommen, da keine der Methoden länger als zehn Zeilen ist; die meisten enthalten nur eine einzige Zeile, die den Aufruf an den AVerwalter weitergibt. Zur Schnittstelle des Anwendungskerns zum Client hin gehören neben den Anwendungsfällen noch die Transferobjekte. Sie sind reine Datencontainer (Value Objects), die zwischen Anwendungskomponente und Client hin- und hergereicht werden. Für jede Komponente gibt es Transferobjekte; in diesem Beispiel sehen sie sehr ähnlich aus wie die A-Entitäten – nur ohne fachliche Methoden, lediglich mit get- und setMethoden für die einzelnen Attribute. Die Anwendungsfälle sind in den Session-Beans CustomerUCBean, InvoiceUCBean und TariffUCBean implementiert. Als Schnittstelle werden dem Client das Homeund das Remote-Interface bekannt gegeben, ebenso der JNDI-Name2 , um das Bean zu finden. A-Verwalter und A-Entitäten Der A-Verwalter ist dafür zuständig, aus der Datenbank benötigte Daten über das DAO-Interface zu lesen und A-Entitäten zu erzeugen. Diese Aufrufe sind in der Regel trivial. Der aufwändigste Anwendungsfall ist die Erzeugung einer Rechnung, denn hierzu müssen erst alle Telefonnummern eines Kunden herausgefunden werden, dann alle betreffenden CallDataRecords gesucht werden; anschließend wird eine Rechnung erzeugt und für jeden CallDataRecord eine Rechnungsposition. Die Tarifkomponente berechnet zu jedem Gespräch den Preis; dies hängt vom Tarif ab, den der Kunde gewählt hat. Für alle anderen Anwendungsfälle reicht der A-Verwalter die Aufrufe lediglich an das DAO durch. Die fachliche Logik verteilt sich also auf die Anwendungsfälle, die den Ablauf steuern, die A-Entitäten, die bestimmte Operationen auf ihren Attributen beherrschen, und die A-Verwalter, die für den Aufbau der A-Entitäten zuständig sind. A-Verwalter sind mit AV<Komponente> benannt, also AVCustomer, AVInvoice und AVTariff. Die A-Entitäten heißen analog AECustomer, AEInvoice und AETariff; die Kundenkomponente arbeitet mit unterschiedlichen Typen von Kunden, die mit AEPrivateCustomer und AEBusinessCustomer bezeichnet werden und von der abstrakten Klasse AECustomer erben. Kundenverwaltung Die Implementierung der Kunden mit Hilfe von Vererbung ermöglicht es dem AVerwalter, genau die richtigen Objekte zu konstruieren, während die meisten Anwen2 JNDI: Java Naming and Directory Interface, siehe z. B. [Ro99] 76 7.2 Implementierung dungsfälle nicht zwischen Privat- und Geschäftskunden unterscheiden müssen und deshalb mit der abstrakten Basisklasse AECustomer arbeiten können. Bild 7.6 zeigt die Klassenstruktur der Anwendungskomponente. Abbildung 7.6: Klassenstruktur der Kundenkomponente An dieser Stelle wird nochmals deutlich, dass eine naive Implementierung auf EntityBeans basierend nicht möglich wäre. Hier wird die Kunden-Komponente genau der fachlichen Motivation entsprechend mit den Mitteln der Objektorientierung umgesetzt. Rechnungsverwaltung Da jede Rechnung aus dem Rechnungskopf und den Einzelgesprächsnachweisen besteht, ist die Besonderheit in der Rechnungskomponente, mit diesen abhängigen Objekten richtig umzugehen. Je nach Anwendungsfall werden sie aus der Datenbank gelesen oder nicht. Werden z. B. alle Rechnungen eines Kunden gesucht, so sind die abhängigen Objekte nicht interessant. Erst wenn eine Rechnung vollständig angezeigt werden soll, müssen alle Einzelposten geladen werden. Der A-Verwalter (AVInvoice) muss also die Möglichkeit bieten, zwischen lazy loading und eager loading zu unterscheiden. Das Erstellen einer Rechnung obliegt vollständig dem A-Verwalter und ist eine umfangreiche Aufgabe. Erst prüft er, ob für den Abrechnungszeitraum schon eine Rechnung existiert. Wenn nicht, werden die CallDataRecords im entsprechenden Zeitraum aus der Datenbank gelesen, aus ihnen jeweils ein InvoiceItem erzeugt und einem neue A-Entität AEInvoice zugeordnet. Über die Kundenkomponente wird der vom Kunden gewählte Tarif ermittelt; von der Tarifkomponente wird ein AETariff erfragt, das die Preisberechnung für jedes Gespräch durchführt. 77 7 Fallstudie: Telekom-Billing-System Tarifverwaltung Die Berechnung der Gesprächspreise geschieht in einer Instanz eines AETariff. Alternativ wäre auch möglich gewesen, diese Methode in die Schnittstelle des Anwendungsfalles zu stellen. So wird ein AETariff an den AVInvoice weitergegeben. Diese Entscheidung kann nach den Überlegungen zum Komponentenentwurf in Kapitel 2 durchaus diskutiert werden. 7.2.2 Das neutrale Interface Listing 7.1 zeigt das neutrale Interface der Kundenkomponente. Es musste nach dem ersten Wunsch, der in Listing 6.1 gezeigt wurde, nicht mehr geändert werden. Auf dieser Grundlage ist die Implementierung ohne Probleme möglich. Listing 7.1: Das neutrale Interface package com.sdm.tbs.ac.customer3.impl; import com.sdm.tbs.uc.ifc.CustomerData; import com.sdm.tbs.uc.ifc.PhoneNumber; import java.util.Collection; import com.sdm.tbs.ac.util.TBSApplException; import com.sdm.tbs.ac.util.TBSSystemException; /** * DAOCustomer * * Neutrales Interface fuer die Kundenverwaltung. * Hier als A-Software formuliert. * ts, 17.09.01 */ public interface DAOCustomer { /* insert / create */ void insertCustomer (CustomerData customer) throws TBSApplException, TBSSystemException; /* find */ CustomerData findById (String customerId) throws TBSApplException, TBSSystemException; CustomerData findByPhoneNumber (PhoneNumber ph) throws TBSApplException, TBSSystemException; 78 7.2 Implementierung Collection findByName (String lastname) throws TBSApplException, TBSSystemException; /* weitere Suchanfragen denkbar */ /* remove */ void removeCustomer (CustomerData customer) throws TBSApplException, TBSSystemException; /* update */ void updateCustomer (CustomerData customer) throws TBSApplException, TBSSystemException; } Für jede Komponente exisitert ein analog aufgebautes Interface. Im Folgenden werden zwei Implementierungen dieses Interfaces vorgestellt. 7.2.3 Implementierung 1: Datenbankzugriff über Entity-Beans Die Implementierung nach dem Fast-Lane-Reader -Pattern erfordert sowohl die Erstellung eines Entity-Beans als auch den JDBC-Code für den lesenden Zugriff. Man beachte, dass kein JDBC für den Schreibzugriff benötigt wird. Insofern kann dies nicht als Verdoppelung des Aufwandes betrachtet werden. Erfolgt die Erstellung des JDBC-Codes manuell, so ist das mühsam und fehleranfällig. Ein produktives sd&m-Projekt generiert für eine Variante dieses Weges einen großen Teil des Codes aus dem Datenbankschema. Damit ist der Datenbankzugriff fast vollständig generiert, und die Abhängigkeit im Anwendungskern ist sehr gering, und die Performanz dieser Lösung genügt den Anforderungen eines hochverfügbaren Buchungssystems.3 Für das TBS entspricht die Implementierung ganz genau der Abbildung 6.3. Der Quelltext wurde von Hand geschrieben und nicht generiert. 7.2.4 Implementierung 2: Datenbankzugriff über das QDI Der Code ist kompakter, leichter lesbar und somit besser wartbar – zumindest für Programmierer, die das QDI kennen. In großen Projekten kann das Team entsprechend aufgeteilt werden, so dass dies keine Einschränkung darstellt. Wie die Darstellung der Praxiserfahrungen (siehe Abschnitt 7.2.6) zeigt, ist dieser Weg sehr empfohlen und sollte von jedem Projekt geprüft werden. Listing 6.3 zeigt einen Ausschnitt aus der Implementierung des DAO mit Hilfe des QDI. 3 Es handelt sich um ein sd&m-Projekt im produktiven Einsatz, das ca. 80.000 Buchungen pro Stunde durchführt. 79 7 Fallstudie: Telekom-Billing-System 7.2.5 Vergleich der zwei Implementierungen Zunächst ist festzuhalten, dass hier zwei unabhängige Implementierungen für die selbe Architektur erfolgreich umgesetzt werden. Die in [Si01] aufgestellte Forderung nach der Austauschbarkeit der Implementierung wird damit erfüllt und unterstreicht die Wartbarkeit des so erstellten Systems. Es ist nicht pauschal zu beantworten, welche Implementierung besser“ ist als die ” andere. Die Implementierung über Entity-Beans nach dem Fast-Lane-Reader-Pattern hat sich neben dem hier implementierten Prototyp auch in der Praxis bewiesen. Aus Architektursicht ist hier der Vorteil, dass mit den Standardmitteln von EJB eine leistungsfähige Lösung erreicht wird. Dieser Ansatz empfiehlt sich für Projekte, die kein komplexes Klassenmodell im Anwendungskern benötigen und keine weiteren Produkte oder Komponenten integrieren möchten. Die Implementierung mit Hilfe des QDI besticht durch die einfach umzusetzende Abbildung vom objektorientierten Klassenmodell auf das relationale Datenbankschema, selbst wenn das Klassenmodell durch Vererbung und abhängige Objekte sehr komplex wird. Gleichzeitig stehen so mächtige Mechanismen wie lazy loading und ein optimistisches Sperrkonzept zur Verfügung. Die Schnittstelle des QDI erreicht eine Mächtigkeit und eine Performanz, ohne eine lange Einarbeitungszeit zu verlangen. Liegt in einer Anwendungskomponente ein etwas komplexeres Klassenmodell vor, so ist der Einsatz einer ausgereiften Zugriffsschicht auf jeden Fall anzuraten. 7.2.6 Praxiserfahrungen Neben der qualitativen Beurteilung der Architektur- und Implementierungsvorschläge ist für ein Projekt auch die tatsächlich erreichte Performanz wichtig. Jedes Projekt hat unterschiedliche Anforderungen an die Antwortzeiten und den Durchsatz; sie ergeben sich aus den speziellen fachlichen Aspekten. Die wichtigsten Fragestellung bezüglich der Performanz sind u. a.: • Wie hoch sind Clientlast und Datenaufkommen zu erwarten? • Ist die Anwendung interaktiv, oder werden komplexe Vorgänge aus einem BatchSystem angestoßen? Sind also die Antwortzeiten von kürzeren, einfacheren Anfragen wichtig, oder kommt es auf den Gesamtdurchsatz bei komplexen Anfragen an? • Sind die meisten Anfragen lesend und nur wenige schreibend, wie z. B. in einem Online-Shop, oder sind viele Anfragen schreibend, wie z. B. bei einem Buchungssystem? Diese Frage beeinflusst die Wahrscheinlichkeit von Sperrkonflikten. • Welche weiteren Besonderheiten weist die Anwendung auf? 80 7.2 Implementierung • Was sind die wichtigsten Zugriffsmuster und Zugriffspfade? • Kann sich das Profil der Anwendung ändern? Bei welchen Punkten sind am ehesten Änderungen zu erwarten? Da die Anforderungen von verschiedenen Projekten sehr unterschiedlich sein können, wurde auch nicht versucht, eine genaue Performanz-Analyse durchzuführen. Statt dessen soll hier lediglich gezeigt werden, dass die vorgestellten Ideen nicht nur bezüglich der Wartbarkeit sinnvoll erscheinen, sondern dass auch die Performanz einen Einsatz in der Praxis erlaubt. Welche Variante ein Projekt wählt, kann erst nach Beantwortung der oben angeführten Fragen entschieden werden. Gegebenenfalls sind erst spezielle Lasttests durchzuführen, die genau die kritischen Punkte durchtesten und aussagekräftige Vergleiche ermöglichen. Beispielszenario In dem gewählten Beispielszenario soll die prinzipielle Tauglichkeit untersucht werden. Parallel mit der Entwicklung des Prototyps ist ein Testtreiber entstanden, der einen Client simuliert und jeden Anwendungsfall in mehreren Konstellationen aufruft. Er wurde so umgebaut, dass er mehrfach durchläuft und so nicht nur Einzelzugriffe, sondern viele Zugriffe erzeugt. So wurde die Datenlast erhöht. Weiterhin wird er in mehreren Threads gestartet, die gleichzeitig Anfragen abschicken, um eine gewisse Clientlast zu simulieren. An dieser Stelle kann variiert werden, ob die unterschiedlichen Threads auf unterschiedliche Daten zugreifen oder auf die selben, um das Verhalten in Konfliktsituationen zu beobachten. Getestet wurde so, dass zehn Threads (fast) gleichzeitig gestartet wurden, die jeweils einen Client simulieren. Jeder dieser Clients schickt eine Reihe von Anfragen an den Anwendungskern ab, die jeweils relativ wenig komplexe Anwendungslogik erfordern, aber intensiv auf der Datenbank arbeiten. Ergebnisse Diese Versuche sind bewusst nur sehr grob aufgebaut und stellen ein willkürliches Szenario dar. Deshalb lassen sich daraus auch keine Aussagen ableiten, welche Lösung die bessere“ sei. Man beachte, dass in diesem Prototyp noch keinerlei Optimierungen ” betrieben wurden, wie z. B. Datenbankindizes gezielt auf die häufigsten Abfragen hin zu optimieren, Datenbank-Denormalisierungen etc. Die Verusche zeigen, dass selbst dieser Prototyp bei erhöhter Datenmenge und Clientzahl mit beiden Implementierungen noch zuverlässig funktioniert. Daraus lässt sich zunächst der Schluss ziehen, dass die vorgestellte Architektur der Performanz 81 7 Fallstudie: Telekom-Billing-System nicht im Wege steht. Der nächste Schritt ist, sich für eine Implementierung zu entscheiden und diese in Verbindung mit dem Datenbankschema so zu implementieren, dass wirkliche Optimierungen erzielt werden. Entwicklung eines Lasttests für ein konkretes Projekt Ein Projekt, das den Einsatz eines EJB-Applikationsservers erwägt und eine Designentscheidung zu treffen hat, muss zunächst die Anforderungen an das System untersuchen und die Engpässe identifizieren. Um die geeignete Implementierung auszuwählen, sind entweder die Argumente, die sich aus den Anforderungen ergeben, schon kräftig genug, oder es muss ein gezielter Perfomanz-Test durchgeführt werden. Hier kommt wiederum der Vorteil der hier vorgestellten Architektur zum Tragen, dass die Anwendungslogik unabhängig vom Datenbank-Zugriffsmechanismus programmiert werden kann. Unter Umständen ist es auch möglich, eine Komponente aus diesem Beispiel-Prototyp auszuwählen und den Testtreiber entsprechend auszubauen, also die Zahl der simulierten Clients anzupassen, das Datenvolumen im gewünschten Umfang vorzugeben, die Zahl der Anfragen pro Sekunde zu verändern, das Verhältnis zwischen lesenden und schreibenden Anfragen einzustellen, die Zahl der provozierten optimistischen Sperrkonflikte zu variieren usw. Die Durchführung eines aussagekräftigen Lasttests ist nicht trivial: Das Verhalten bei erhöhtem Datenaufkommen hängt nicht nur vom Zusammenspiel von Anwendung, Applikationsserver und Datenbank ab, sondern genauso von den CachingEigenschaften des Applikationsservers und der Datenbank. So können die Antwortzeiten über einen gewissen Bereich linear ansteigen und ab einem bestimmten Punkt stark in die Höhe gehen. Das Gleiche gilt für die Erhöhung der Clientlast und das Verhalten bei einer hohen Zahl an konkurrierenden Zugriffen. Es sind ausführliche Vergleiche erforderlich, um alle gemessenen Effekte genau den richtigen Ursachen zuschreiben zu können; so z. B. Testreihen, die die Zugriffe auf die Datenbank direkt und über den Applikationsserver ausführen, Vergleiche von Clientanfragen, die an die Datenbank weitergereicht werden mit Clientanfragen, die von einem Dummy beantwortet werden, um genau herauszufinden, wieviel Laufzeit im Applikationsserver und beim Datenbankzugriff benötigt wird. Erst wenn die Messergebnisse genau ihren Ursachen zugeordnet werden können, sind exakte Aussagen über die Leistungsfähigkeit einer Architektur und einer bestimmten Implementierung möglich. Ein aussagefähiger Lasttest ist nur projektspezifisch durchführbar und würde den Rahmen dieser Arbeit sprengen. [Bri00, Kapitel 8] argumentiert, dass die Testumgebung erst möglichst nahe am echten Betrieb orientiert werden muss, bevor die Messergebnisse zu Entscheidungen herangezogen werden können. 82 7.2 Implementierung Weitere Entscheidungskriterien für Projekte Selbstverständlich muss sich eine Designentscheidung neben den technischen Fragestellungen auch noch auf die verfügbaren Mitarbeiter und das vorhandene Know-How stützen, und es muss die Variabilität der Anforderungen berücksichtigen. 83 8 Zusammenfassung 8.1 Erkenntnisse Der Entwurf von betrieblichen Informationssystemen stellt hohe Anforderungen sowohl von technischer als auch von fachlicher Seite. Derartige Systeme müssen verteilt arbeiten und mit hohen Datenvolumina und Clientlasten zurecht kommen. Gleichzeitig sind individuelle, fachlich unterschiedliche Lösungen zu implementieren, die zum Teil komplexe Algorithmen verlangen. Sowohl die technischen Randbedingungen als auch die fachlichen Anforderungen können sich ändern; ein betriebliches Informationssystem ist zwei unterschiedlichen Innovationszyklen ausgesetzt. Während sich fachliche Anforderungen von Projekt zu Projekt und von Kunde zu Kunde deutlich unterscheiden können, ähneln sich die immer wieder zu lösenden technischen Probleme, wie z. B. Datenbankzugriff, Transaktionskontrolle, Ressourcenmanagement usw. Applikationsserver bieten eine Integrationsplattform für technische Dienste; sie entlasten den Anwendungsprogrammierer von den immer wiederkehrenden Problemen. Sie stellen Programmierschnittstellen bereit, welche die benötigten technischen Dienste auf einer gewissen Abstraktionsebene verfügbar machen. Die Spezifikation von Enterprise Java Beans (EJB) definiert eine solche Programmierschnittstelle für einen sogenannten Container, der den Enterprise Java Beans als Laufzeitumgebung genau diese technischen Dienste zur Verfügung stellt. Ein Container läuft innerhalb eines EJB-Servers; Implementierungen für Container und Server existieren von mehreren namhaften Firmen wie auch von der Open-Source-Gemeinde. Die EJB-Spezifikation legt ein Komponentenmodell fest und verspricht, dass die Anwendungsprogrammierer sich damit rein auf die fachlichen Aspekte der Anwendung konzentrieren können und die technischen Abhängigkeiten deklarativ lösen können. Doch es zeigt sich, dass die EJB-Spezifikation und die verfügbaren Produkte sowohl hinter dem Anspruch der Portierbarkeit als auch hinter dem der Trennung zwischen fachlicher Logik und technischer Abhängigkeit zurückbleiben. Der Grund hierfür liegt einerseits in der Spezifikation, die an vielen kritischen Punkten unklar bleibt und den Produktherstellern Freiheiten lässt, bzw. ihnen die Arbeit auferlegt, vernünftige Lösungen zu finden. Andererseits setzen die Hersteller aufgrund unterschiedlicher Erfahrungen mit Vorgängerprodukten unterschiedliche Schwerpunkte. Das Komponentenmodell von EJB ist weder zur Wiederverwendbarkeit von Enterprise Beans 84 8.2 Vorteile des Ansatzes geeignet, noch leistet es einen wertvollen Beitrag zur Strukturierung von großen Softwaresystemen (siehe Kapitel 5.2). Dies führt dazu, dass Anwendungen, welche sich zu sehr auf die speziellen Eigenschaften von EJB oder einem bestimmten EJB-Server oder -Container einlassen, nur noch schwer wartbar sind: Enterprise Beans sollen fachliche Logik aufnehmen, ihr Design aber wird dort allein von technischen Überlegungen bestimmt. Systeme, die auf der Spezifikation 1.1 aufbauen, sind zwar gerade mal ein Jahr alt und dennoch Altsysteme, denn mit der Spezifikation 2.0 ändern sich grundlegende Mechanismen. EJB steht für die Lösung komplexer technischer Probleme und integriert Mechanismen zur Persistenz, zum Transaktionsschutz und zur verteilten Verarbeitung. Eine solche Technologie ist naturgemäß komplex und kann nicht auf naive Weise eingesetzt werden. 8.2 Vorteile des Ansatzes Diese Diplomarbeit stellt zunächst dar, welche Anforderungen an ein betriebliches Informationssystem gestellt werden und untersucht, welche Möglichkeiten EJB in diesem Zusammenhang bietet. Sie ordnet die Mächtigkeit dieser Möglichkeiten ein und schlägt eine Architektur vor, welche einerseits die Dienste des Applikationsservers so weit wie möglich in Anspruch nimmt, um nicht das Rad neu zu erfinden, andererseits aber Wert darauf legt, sich nicht wiederum von der Technik des Applikationsservers abhängig zu machen. Diese Architektur orientiert sich streng an dem Prinzip, die Implementierung der fachlichen Logik von der Implementierung der technischen Umsetzung zu trennen. Sie nutzt die Dienste des EJB-Applikationsservers — allerdings nicht auf dem Weg, den die Spezifikation und die meisten Lehrbücher vorschlagen, da dort die fachliche Logik mit den Spezifika der EJB-Version und des verwendeten Applikationsservers vermischt wird. Die zentrale Idee ist die Entkoppelung der Anwendungslogik von den technisch motivierten Teilen über ein neutrales Interface. Das neutrale Interface formuliert ausschließlich fachliche Funktionalität; erst die Implementierung dieses Interfaces legt sich auf eine konkrete technische Umsetzung fest. So bleiben die Auswirkungen einer technischen Änderung lokal begrenzt, genauso wie sich eine Änderung der fachlichen Anforderungen nur auf die Programmteile mit rein fachlicher Logik beschränkt. Dadurch wird nicht nur die Wartbarkeit des Systems dramatisch erhöht; auch die Arbeitsteilung im Team wird einfacher und effektiver: Datenbankexperten können genau abgestimmte Optimierungen durchführen, während sich ein anderer Teil des Teams tatsächlich auf rein fachliche Probleme konzentrieren kann. Zwei unabhängige Implementierungen des vorgestellten Beispiel-Prototyps und in der Praxis bewährte Projekte zeigen, dass dieser Ansatz sinnvoll ist in dem Sinne, 85 8 Zusammenfassung dass er den in der Einleitung genannten Zielen gerecht wird: Die fachliche Logik kann unabhängig von der verwendeten Basistechnologie implementiert werden, und Änderungen der fachlichen Anforderungen bleiben lokal begrenzt, fordern also keine Überprüfung von Software-Teilen, die ausschließlich technisch motiviert sind. 8.3 Ausblick Im direkten Anschluss an diese Arbeit Diese Arbeit argumentiert für den Einsatz von Applikationsservern, warnt aber gleichzeitig davor, den Werbeversprechen zu glauben und von einer naiven Implementierung gleichzeitig Wartbarkeit und Effizienz zu erwarten. Sie liefert einen Architekturvorschlag für reale Projekte; bevor aber eine spezielle Implementierung gewählt werden kann, sind noch Untersuchungen im Hinblick auf die Generierbarkeit und ein genau auf die Anforderungen eines Projektes hin abgestimmter Lasttest durchzuführen. Solche Untersuchungen könnten im direkten Anschluss an diese Arbeit durchgeführt werden, um neben dem Architekturvorschlag auch noch detailliertere Implementierungsvorschläge mit einer differenzierteren Bewertung vorzulegen. Solche Implementierungsvorschläge sollten auch den Einsatz von kommerziellen Werkzeugen mit einschließen. Die vorgestellte Architektur reicht vom Datenbankzugriff bis zum Anwendungskern. Eine vollständige Anwendung benötigt natürlich noch eine Benutzerschnittstelle. Interessant ist deshalb die Entwicklung einer Gesamtarchitektur, die einen Anwendungskern mit einer GUI verbindet. Eine solche Gesamtarchitektur wurde bei sd&m Research entwickelt, aber noch nicht als solche veröffentlicht. Weitere wichtige Themen schließen z. B. Kriterien für den Entwurf von Anwendungs-Komponenten oder die Transaktionsverwaltung mit ein. Auch zu diesen Themen laufen zur Zeit Forschungsprojekte bei sd&m Research. Über den Tellerrand hinaus Applikationsserver sind ein guter Weg, die technisch motivierten Dienste für Informationssysteme zu standardisieren und sie auf einem höheren Abstraktionsniveau anzubieten, um es den Anwendungsentwicklern ermöglichen, sich mehr auf die Anwendungslogik zu konzentrieren. Es liegt auf der Hand, dass sich die ApplikationsserverTechnologie weiterhin schnell entwickeln wird. Es wird neue EJB-Versionen geben, die mit .Net, weiteren Corba-Versionen oder völlig neuen Technologien um die Gunst der Entwickler und Kunden konkurrieren werden. Persönliche Einschätzung des Autors, Stand Januar 2002: Probleme sind zur Zeit bei der Integration verschiedener Werkzeuge zu sehen. Applikationsserver integrieren die 86 8.3 Ausblick Bereiche Transaktionsschutz, Persistenz und Verteilung; aber in jedem Bereich gibt es einzelne Werkzeuge, die besser sind als die in Applikationsservern verfügbaren Mittel. Hier wird noch mehr Konsolidierung stattfinden, wie z. B. mit der Zusammenarbeit von Bea WebLogic und TopLink schon geschehen. Das wird einerseits die Qualität der verfügbaren Applikationsserver erhöhen, andererseits die Portabilität zwischen ihnen erschweren. Es wird dann keine Applikationsserver-Experten geben, sondern jeweils Produktexperten. Die in Kapitel 6 vorgestellte Architektur zeigt, wie man die aktuell vorhandene Technik optimal ausnutzen kann, ohne sich zu sehr von ihr abhängig zu machen. So wird erreicht, dass die Implementierung der fachlichen Logik in ihrer Lebensdauer nur von den fachlichen Anforderungen abhängt. In dieser Arbeit wird das für EJB gezeigt; ein solches Vorgehen wird auch für künftige Anwendungen ein sinnvoller Weg sein. 87 8 Zusammenfassung 88 Index A/T-Trennung, 7 ACID-Eigenschaft, 16 Bean-Klasse, 26 Bimodal Data Access, 55 Clientlast simulieren, 81 Datenlast simulieren, 81 Deployment-Deskriptor, 26 EJB, 22 als T-Software, 47 Aufbau, 24 Deployment, 24 Entwurfsmuster, 55 Lebenszyklus, 29 Probleme, 42 Produkte, 23 Programmierung, 24 Spezifikation, 22 Version 2.0, 37 Entity-Beans sind T-Software, 48 Fast Lane Reader, 55 Home-Interface, 26 impedence mismatch, 13 Java, 22 JDO, 37 Lebenszyklus, 29 Objektidentität, 14 OR-Mapping, 13 Paradigmenbruch, 13 Partitionierung typisiert, horizontal, vertikal, 14 Primärschlüssel, 26 QDI, 51 Quasar, 6 Database Interface, 51 Softwarekategorien, 7 Remote-Interface, 25 RMI Effizienzvergleich, 45 Softwarekategorien, 7 Sperrstrategien, 16 Spezifikation Lücken, 32 TBS, 69 Teamarbeit, 60 TopLink, 53 Transaktionen, 16 Transaktionsstrategien, 16 Optimistisch, 17 Pessimistisch, 17 Transaktionsverhalten deklarativ, 26 Versant für CMP einsetzen, 35 virtuelle Maschine, 22 Zugriffskontrolle deklarativ, 26 Zugriffsschicht Zugriff auf A-Entitäten, 52 89 Literaturverzeichnis [Alu01] Deepak Alur, John Crupi, Dan Malks: J2EE Patterns, Prentice Hall, 2001 [Amb98] Scott Ambler: Mapping Objects to Relational Databases, AmbySoft, 1998: http://www.AmbySoft.com/mappingObjects.pdf [Am01] Scott Ambler: Complex Persistence, Kapitel 14 des Buches Mastering EJB, ausgelegt zum public review auf dem Diskussionsforum www.theserverside.com, Mai 2001 [Atk+89] Malcolm P. Atkinson, François Bancilhon, David J. DeWitt, Klaus R. Dittrich, David Maier, Stanley B. Zdonik: The Object-Oriented Database System Manifesto, in Deductive and Object-Oriented Databases“, Pro” ceedings of the First International Conference on Deductive and ObjectOriented Databases (DOOD’98), pp. 223-240 [Ben01] Gerd Beneken, Johannes Siedersleben: Quasar: QDI Tutorial, Schriftenreihe sd&m Research, Februar 2001 [Bri00] Chris Britton: IT Architectures and Middleware, Addison-Wesley, 2000 [BrSi00] Peter Brössler, Johannes Siedersleben: Software-Technik, Hanser-Verlag, 2000 [BrWh96] Kyle Brown, Bruce G. Whitenack: Crossing Chasms, A Pattern Language for Object-RDBMS Integration, White Paper, Knowledge Systems Corp. 1995. A shortened version is contained in: John M. Vlissides, James O. Coplien, Norman L. Kerth (Eds.): Pattern Languages of Program Design 2, Addison-Wesley, 1996 [DePe00] Stefan Denninger, Ingo Peters: Enterprise JavaBeans, Addison-Wesley, 2000 [Dij76] Edsger W. Dijkstra: A Discipline of Programming, Prentice Hall, 1976 [Gam94] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Design Patterns, Elements of Reusable Software, Addison-Wesley, 1994 90 Literaturverzeichnis [GrRe92] Jim Gray, Andreas Reuter: Principles of Transactionprocessing: Concepts and Techniques, Morgan-Kaufmann, 1992 [Ha01] Tobias Hain: Anwendungskernanbindung nativer und webbasierter Benutzeroberflächen für betriebliche Informationssysteme, Diplomarbeit, Universität Siegen, 2001 [He97] Andreas Heuer: Objektorientierte Datenbanken: Konzepte, Modelle, Systeme, Addison-Wesley, 2. Auflage, 1997 [Ke97] Wolfgang Keller: Mapping Objects to Tables: A Pattern Language, in Proceedings of the 1997 European Pattern Languages of Pro” gramming Conference, Irrsee, Germany“, Siemens Technical Report 120/SW1/FB1997 [Mon99] Richard Monson-Haefel: Enterprise JavaBeans, O’Reilly, 1999 [Öb01] Rickard Öberg: Mastering RMI, Wiley, 2001 [Par72] David L. Parnas: On the Criteria to be used in Decomposing Systems into Modules, Communications of the ACM, Vol. 15, No. 12; pp. 10531058, 1972 [PCW83] D. L. Parnas, P. C. Clements, D. M. Weiss: Enhancing Reusability with Information Hiding, Proceedings of ITT Workshop on Reusability, Stratford, CT, 1983 [Ro99] Ed Roman: Mastering EJB, Wiley, 1999 [Sch01] Manfred Schamper: Muster zum komponentenorientierten Entwurf betrieblicher Informationssysteme unter Verwendung von EJB, Diplomarbeit, Ludwig-Maximilian-Universität München, 2001 [Si01] Johannes Siedersleben et al.: Was ist Quasar?, Schriftenreihe sd&m Research, März 2001 [Sla99] Dirk Slama, Jason Garbis, Perry Russel: Enterprise Corba, Prentice Hall, 1999 [Sun99] Vlada Matena, Mark Hapner: Enterprise Java Beans Specification, Version 1.1, Final Release, Sun Microsystems, 24.11.99 [Sun01a] Linda G. DeMichiel, L. Ümit Yalçinalp, Sanjeev Krishnan: Enterprise Java Beans Specification, Version 2.0, Final Release, Sun Microsystems, 14.08.01 [Sun01b] Sun Microsystems: Java Blueprints: J2EE Patterns. Webseite: http:// java.sun.com/blueprints/patterns/j2ee patterns, November 2001 91 Literaturverzeichnis [Szy99] Clemens Szyperski: Component Software – Beyond Object-Oriented Programming, Addison-Wesley, 1999 [Top01] TopLink von der Firma WebGain, Inc., USA. Webseite: http:// www.webgain.com, November 2001 [Ver01] Versant enJin von der Firma Versant Corporation, USA. Webseite: http://www.versant.com, November 2001 [Wrox01] Professional EJB, Wrox, 2001 [ZiBe00] Jürgen Zimmermann, Gerd Beneken: Verteilte Komponenten und Datenbankanbindung, Addison-Wesley, 2000 92