Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  4/2005
suche: 
Dieser Artikel richtet sich an Entwickler objektorientierter (Java-) Lösungen und solche, die es werden wollen.

Glossar

Annotations
Anmerkungen im Java Source Code, die zur Laufzeit des Programms ausgewertet werden können. Annotations sind seit Java 5 Bestandteil von Java. Als geistigen Vater dieser Annotations kann man das Open Source Projekt XDoclet ansehen. XDoclet erlaubte es, auch schon in früheren Java Versionen mit Annotations zu programmieren.
Anti-Pattern
Während Pattern oder Entwurfsmuster in der Software-Entwicklung beschreiben, wie man bestimmte Aufgabenstellungen löst, weist ein Anti-Pattern auf Lösungen und die damit verbundenen Probleme hin, die man besser nicht wählen sollte.
JDO
Java Data Objects (JDO). Die JDO sind eine Spezifikation für ein herstellerunabhängiges Framework zur persistenten Speicherung von Java-Objekten in Datenspeichern, z. B. relationalen Datenbanken, objektorientierten Datenbanken, XML-Dateien etc..(Quelle: [2]).
J2EE

Java 2 Platform, Enterprise Edition ist die Spezifikation einer Standardarchitektur für die Ausführung von J2EE-Applikationen. Hierzu werden in der Spezifikation Softwarekomponenten und Dienste definiert, die primär in der Programmiersprache Java erstellt werden.

Die Spezifikation dient dazu, einen allgemein akzeptierten Rahmen zur Verfügung zu haben, um mit modularen Komponenten verteilte, mehrschichtige Anwendungen zu entwickeln.

Klar definierte Schnittstellen zwischen den Komponenten und Schichten sollen dafür sorgen, dass Softwarekomponenten unterschiedlicher Hersteller interoperabel sind, wenn sie sich an die Spezifikation halten, und dass die verteilte Anwendung gut skalierbar ist (Quelle: [3]). In jüngerer Zeit hat Sun diesen (und andere) Namen geändert, in dem es die 2 aus dem Namen genommen hat, um etwas versionsunabhängiger zu sein.

O/R-Mapper
Object/Rational Mapper werden eingesetzt, um in objektorientierten Programmiersprachen Objekte auf (Datenbank-)Relationen zu mappen. Mit ihnen ist es möglich, diese Objekte in einer relationalen Datenbank abzuspeichern und wieder daraus zu erstellen. Dies geschieht weitestgehend automatisch, so dass sich der Entwickler nicht um die eigentliche Datenbankschnittstelle kümmern muss.

Ein Lösungsansatz für Probleme in der objektorientierten Software-Entwicklung

Von POJOs und anämischen Objekten

J2EE, SOA und wie sie alle heißen: Bei allen neuen Techniken, Technologien und Spezifikationen sollten wir in der Software-Entwicklung nicht den Blick für etwas Wesentliches verlieren. Dass das aber schnell passieren und was man dagegen machen kann, entdecken Sie in diesem Artikel.

Es waren einmal
"Sieben Stufen zur objektbasierten Glückseligkeit"

Geht es Ihnen auch so? Manchmal kommt einem etwas seltsam vor, aber man weiß nicht genau, was es ist. Haben Sie in letzter Zeit mal einen skeptischen Blick auf Ihre Geschäftsobjekte geworfen, d. h. in die Klassen, in denen Sie ihre Geschäftsdaten erwarten?

Sie finden dort sicher Ihre sämtlichen Geschäftsdaten sauber modelliert vor. In den meisten Fällen auch noch mit den entsprechenden Get- und Set-Methoden. Der eine oder andere Konstruktor ist meist ebenfalls anzutreffen. Soweit so gut. Und wenn Sie dort noch wesentlich mehr (Methoden) antreffen, brauchen Sie an dieser Stelle vermutlich gar nicht erst weiter zu lesen.

Sollten Sie aber kaum mehr als das Genannte vorfinden, dann sollten Sie zumindest ein wenig ins Grübeln kommen: War da nicht etwas mit der Objektorientierung?

Wenn man die Get- und Set-Methoden und den Konstruktor vernachlässigt, stellt man fest, dass man es in den Klassen eigentlich nur mit den klassischen Strukturen der prozeduralen Programmiersprachen zu tun hat. Dazugehörende Geschäftslogik: Fehlanzeige!

Anders ausgedrückt, wodurch unterscheidet sich diese Implementierung von beispielsweise einem C-Programm mit einem einfachen struct?

Sind wir auf Bertrand Meyers "Sieben Stufen zur objektbasierten Glückseligkeit" [1] etwas ins Stolpern geraten? Offensichtlich. Die Frage ist nur, ob uns nicht jemand anderes aus dem Tritt gebracht hat? In der aktuellen Software-Entwicklung gibt es einige Gesellen, denen so etwas zuzutrauen ist.

Schuld ist das Management

Vermutlich der älteste unter diesen Gesellen ist die Management- oder Controller-Klasse, die durchaus nahezu immer ihre Daseinsberechtigung hat. Ursprünglich gedacht, um die eigentlichen Geschäftsobjekte zu verwalten, ist die Versuchung groß, dort auch gleich ein wenig Geschäftslogik unterzubringen.

Und wenn Sie nicht aufpassen, sind die Grenzen bald verwischt. Und das ist noch steigerungsfähig: Beim nächsten Refactoring wird die restliche Geschäftslogik sauber aus den ursprünglichen Geschäftsklassen entfernt und in die Management-Klasse(n) verlagert. Die ursprüngliche Geschäftsklasse wird somit zur reinen Datenstruktur degradiert.

Anämische Objekte

Martin Fowler nennt so etwas "Anämische Objekte" und hat daraus gleich ein schreckliches Anti-Pattern gemacht, das "AnemicDomain-Model". ("The fundamental horror of this anti-pattern is, that it‘s so contrary to the basic idea of object-oriented design; which is to combine data and process together.")

Auch er nennt Ross und Reiter als weitere Anstifter für dieses "So-sollte-es-nicht-sein": Dies sind alle Frameworks, Spezifikationen und Bibliotheken, die umfangreiche Konventionen und externe Abhängigkeiten für die eigenen Klassen erzwingen.

Das können beispielsweise zwingend zu implementierende Schnittstellen, einzuhaltende Namenskonventionen oder notwendige Annotationen sein. Mit den J2EE Entity Beans sind auch gleich die Lieblingsschurken der Szene ausgemacht.

Doch auch JDO oder andere Object-Relational(O/R)-Mapper verführen den ahnungslosen Entwickler schnell und komfortabel, diese blutarmen Klassen zu entwerfen. Und da passt es sehr gut, dass sich momentan alles um Services dreht: Eine service-orientierte Architektur (SOA), Web-Services oder zumindest aber ein Service Layer muss die Applikation haben.

Darin lässt sich dann hervorragend die gesamte Geschäftslogik unterbringen. Somit fügt sich zusammen, was nicht zusammen gehört und alles hat seinen Platz: Die Daten in den Entity-Klassen und die Business-Logik in den Service-Methoden bzw. -Funktionen.

Weit verbreitet

Das Erstaunliche an diesem Anti-Pattern ist, dass es den Entwicklern mit einer "datenzentrierten Vergangenheit" kaum auffällt. Da es doch noch den einen oder anderen Entwickler mit dieser Vergangenheit gibt, kann man sich die Verbreitung dieses Anti-Patterns ausmalen.

Ehre wem Ehre gebührt

Zur Ehrenrettung aller Services muss auf jeden Fall gesagt werden, dass auch sie einen berechtigten und wichtigen Platz in der Software-Technologie haben. Allerdings ist ihre Aufgabe oft wesentlich dedizierter als das, was ich manchmal zu Gesicht bekomme.

Diese Services sollten nämlich in einer möglichst dünnen Schicht über den Geschäftsobjekten liegen und enthalten per Definition erst einmal keine Geschäftsregeln und kein Business-Know-how.

Vielmehr benutzen sie dafür die ihnen unterliegenden Geschäftsobjekte, um beispielsweise größere Aufgaben zu koordinieren oder mit anderen Anwendungen oder einem Benutzer zu kommunizieren.

Retter in der Not

Bei so vielen Bösewichtern fehlt der heldenhafte Retter in der Not. Und es gibt ihn tatsächlich, wenngleich sein Name wenig ruhmreich klingt: POJO.

POJO ist die Abkürzung für Plain Old Java Object, also ein eigentlich „ganz normales“ Objekt der guten, alten, objektorientierten Schule. Der Ausdruck wurde u. a. von Martin Fowler geprägt, um diese Objekte von anderen zu unterscheiden, die mit vielen externen Abhängigkeiten und Konventionen belastet sind.

Mit POJOs lassen sich also ohne externe Zwänge alle Geschäfts-daten, -regeln und -verfahren modellieren. Der Entwickler kann sich voll und ganz auf die fachliche Entwicklung konzentrieren und sich dabei aus dem vollen Baukasten der Objektorientierung bedienen.

POJOs stellen zwar bei weitem keine Garantie für gutes Design dar, sind aber zumindest eine gute Grundlage dafür. Rein technisch und organisatorisch lässt sich auf jeden Fall sagen, dass ein POJO durch die in sich ruhende Unabhängigkeit leichter wart- und wiederverwendbar ist.

POJOs stellen auch eine wichtige Basis für eine komponentenorientierte Architektur dar. Das fängt bereits mit der guten Testbarkeit eines POJOs an.

Versuchen Sie dagegen, "mal eben auf die Schnelle" ein Entity Bean zu testen. Diese Beans sind perfekt in die J2EE-Umgebung integriert, so dass ein Herauslösen – insbesondere „nur“ zu Testzwecken – eine echte Herausforderung darstellt.

"Entity Bean versus Hibernate"

Dies ist keine "Abrechnung" mit den J2EE EJBs – zumal eine solche mit der Einführung von EJB 3.0 etwas zu spät käme. Es geht in diesem Beitrag um ein „gutes“ Beispiel für die hier genannten Probleme. Von daher stellt der Artikel auch keinen ansatzweise vollständigen Vergleich dar.

Während Hibernate als POJO-Implementierung kaum Vorgaben bei der Programmierung der Geschäftsobjekte macht, mussten wir bei EJBs bisher immer das Remote und Home Interface sowie etliche, oft überflüssige Callback-Methoden implementieren.

Durch Fleißarbeit oder entsprechende Tools war dies bisher noch zu kompensieren. Das gelingt jedoch dann nicht mehr, wenn objektorientierte Grundlagen wie Vererbung und Polymorphie der EJBs untereinander nicht möglich sind. Das hat dann gleichzeitig einen wesentlichen Einfluss auf das Design der Anwendung.

Wir waren mit der Objektorientierung einmal angetreten, die fachlichen Probleme dieser Welt zu lösen. Daher ist es ernüchternd, festzustellen, dass wir uns für bestimmte technische Anforderungen, wie z. B. die Möglichkeit einer Anwendungsverteilung, von Anfang an in unseren Ausdrucksmitteln beschneiden lassen.

Abb. 1: Anmerkungen zur Implementierung.

Diese Schwachstelle ist aber mittlerweile identifiziert und geht mit der Enterprise Java Beans (EJB) 3.0 Spezifikation in Richtung POJO. Maßgeblichen Einfluss auf diese Entwicklung hatte das O/R-Mapping-Tool Hibernate (siehe Abbildung 1). Als einer der Stars der Open Source Szene hat Hibernate gezeigt, wie einfach eine Persistenzschicht aussehen kann und wie einfach sie benutzt werden kann.

Lesen Sie mehr zu Hibernate

Auch andere Open Source Projekte, wie z. B. Spring, Hivemind und PicoContainer, sind als kleine, aber feine (Teil-)Alternativen zu den großen und mächtigen J2EE-Umgebungen entstanden. Sie werden daher oft als leichtgewichtige Frameworks bezeichnet.

Eine kleine Randbemerkung: Streng genommen sind auch Java-Beans keine POJOs, da sie per Konvention Get- und Set-Methoden enthalten müssen, um Zugriff auf ihre Properties zu ermöglichen. Allerdings sollten wir diesen Umstand nicht überbewerten.

Objekte mit Objekten injizieren

Interessant ist die Frage „Wie gehen diese Frameworks mitsamt ihrer POJOs denn mit externen Abhängigkeiten um?“ Externe Abhängigkeiten komplett zu ignorieren, würde sie nutzlos machen.

public class Umsatzberechnung {
      // 1. ext. Abhängigkeit (Logging):       private final static Log log = new SimpleLog("TEST");
      public double berechneGesamtUmsatz() {             // 2. ext. Abhängigkeit (Datenquelle):             TestDatenKundenDB kundenDB = new TestDatenKundenDB();             Kunde[] alleKunden = kundenDB.getAll();             double sum = 0;             for (int i = 0; i < alleKunden.length; i++) {                 log.debug("addiere Kunde " + alleKunden[i].getName());                 sum += alleKunden[i].getGesamtUmsatz();             }             return sum;         }
      public static void main(String[] args) {             Umsatzberechnung berechnung = new Umsatzberechnung() ;             System.out.println(berechnung.berechneGesamtUmsatz());       } }
Abb. 2: Beispiel für eine Umsatzberechnung mit zwei externen Referenzen.

In Abbildung 2 ist eine Umsatzberechnung für Kunden dargestellt, die zwei externe Referenzen aufweist. Die eine externe Abhängigkeit entsteht durch die Verwendung der Log-Klasse und die andere durch die Instanziierung der Datenquelle für Kundendaten.

Aktuell werden also ganz konkrete Implementierungen für das Logging und den Zugriff auf Kundendaten zum Test benutzt. Es ist nun aber nicht möglich, die Klasse unverändert auch für die Produktivdaten zu benutzen. Ähnliche Probleme hat man beim Ändern der Logging-Implementierung. Ändert man sie zu diesem Zweck, so ist sie wiederum nicht länger in einem automatischen Test zu verwenden. Es muss also eine Lösung geben.

Auch hier haben wir es Martin Fowler zu verdanken, dass die – eigentlich sehr einfache – Lösung einheitlich unter Inversion-of-Control oder seit einiger Zeit unter Dependency Injection bekannt geworden ist.

Dependency Injection heißt, dass die Kontrolle über bestimmte Vorgänge über eine definierte Schnittstelle nach außen verlagert wird. Dabei wird, meist zur Laufzeit, das Objekt mit anderen Objekten injiziert, die die fachfremden Aufgaben durchführen. Die oben genannten Open Source Projekte, wie Spring und Hivemind, ermöglichen dies und machen davon sehr intensiven Gebrauch.

public class Umsatzberechnung {
      private Log log;
      private KundenDB kundenDB;
      public double berechneGesamtUmsatz() {             // 1. ext. Abhängigkeit (Datenquelle):             Kunde[] alleKunden = kundenDB.getAll();             double sum = 0;             for (int i = 0; i < alleKunden.length; i++) {                   // 2. ext. Abhängigkeit (Logging):                   log.debug("addiere Kunde " + alleKunden[i].getName());                   sum += alleKunden[i].getGesamtUmsatz();             }             return sum;       }
      public void setKundenDB(KundenDB kundenDB) {                   this.kundenDB = kundenDB;       }
      public void setLog(Log log) {             this.log = log;       }
      public static void main(String[] args) {             Umsatzberechnung berechnung = new Umsatzberechnung() ;             berechnung.setKundenDB(new TestDatenKundenDB());             berechnung.setLog(new SimpleLog("TEST"));
            System.out.println(berechnung.berechneGesamtUmsatz());       } }
Abb. 3: Beispiel für eine mit dem Dependency Injection Prinzip verbesserte Umsatzberechnung.

Das einfache Beispiel aus Abbildung 2 ist in Abbildung 3 nach dem Dependency Injection Prinzip verbessert worden.

Für die Kundendaten-Schnittstelle ist hier ein eigenes Interface definiert und verwendet worden, das alle notwendigen Methoden enthält. Für das Logging wiederum wurde auf das Interface des Apache Jakarta Common Logging Projektes zurückgegriffen.

Entscheidend ist, dass die Klasse diese beiden Schnittstellen nach außen öffnet und dass das aufrufende Code-Segment diese Schnittstellen vorher setzt (in diesem Fall die main-Methode).

Wer sagt, das sei nichts wirklich Neues, hat Recht. Es kommt nur auf die konsequente Umsetzung an. Nur so lässt sich diese Klasse in einem beliebigen Kontext einsetzen und insbesondere auch testen.

Einige der o. g. Frameworks wie z. B. Hivemind können diese Verknüpfung auf Basis einer entsprechenden Definition auch automatisch erstellen (auto-wiring). Dem Entwickler bleibt diese Arbeit dann weitestgehend erspart.

Fazit

Die vorangegangenen Worte üben zwar Kritik an J2EE oder vergleichbaren Spezifikationen im Allgemeinen, jedoch keineswegs so grundlegend, dass der Autor von ihrer Verwendung abraten würde. Dazu haben sie zu viele Vorteile. Vor- und Nachteile sollten im jeweiligen Kontext individuell betrachtet werden.

Allerdings sollten wir bei allen Spezifikationen und neuen Entwicklungstrends nie die ursprünglichen Grundlagen aus den Augen verlieren. Stolperfallen sollten als solche identifiziert und umgangen werden.

Auch ein (selbst)kritischer Umgang mit neuen Technologien und Spezifikationen kann helfen. Wenn die Kritik dabei nicht nur die Qualität der eingesetzten Tools und Frameworks beleuchtet, sondern ebenfalls hinterfragt, inwieweit wir die dahinter stehenden Konzepte auch wirklich verstanden haben, dann sind wir einen Schritt weiter gekommen auf dem Weg zur produktiven Software-Entwicklung.

Axel Röber (info@ordix.de).