| « Wurst - die schönste Nebensache der Welt | Schland, oh Schland! » |
Exception!
Link: http://twitter.com/ElHibernetico
Wer eine Business-Anwendung programmiert, hebt sich - in der Theorie - von den oft als unsauber und amateurhaft geltenden Programmiertechniken der Skriptsprachen-Gemeinde ab. Streng objektorientierte Sprachen wie Java zwingen den Entwickler schon fast, eine saubere Struktur an Klassen für seine Software aufzubauen. Dank vielfältiger Fachliteratur wissen wir auch, was ein solcher objektorientierter Aufbau an Vorteilen mit sich bringt: Erweiterbarkeit, Anpassbarkeit, Modularität, geringe Kopplung, hohe Kohäsion und saubere Schnittstellen zählen da zu den Hauptargumenten. Eine schöne heile Welt? Wir werden sehen...
Natürlich benötigt jede ernsthafte Software mit irgendeiner Form von Datenbank im Hintergrund, in den allermeisten Fällen wird dabei auf SQL-basierte Systeme zurückgegriffen, wie z.B. den Microsoft SQL Server oder MySQL. Das große Problem aus Sicht der Puristen der Objektorientierung ist dabei jedoch die Tatsache, dass SQL-Datenbanken in aller Regel nicht mit Objekten arbeiten, jedenfalls nicht so, wie es die darunterliegende Programmiersprache benötigt. Die Kommunikation zwischen der Anwendung und der Datenbank läuft dabei mit Hilfe der robusten und stetig weiterentwickelten Abfragesprache SQL (Structured Query Language) bewerkstelligt. Unterschiedliche Datenbanken haben dabei unterschiedliche "Dialekte" dieser Sprache, d.h. Besonderheiten beim Escapen von Sonderzeichen, Spezialfunktionen, Trigger, Views, etc. Im Allgemeinen halten sich die Datenbanken bei einfachen Abfragen jedoch an den SQL-Standard. Die Anwendung muss sich ihre Objekte also aus der Ergebnismenge der SQL-Abfragen zusammenbauen, und beim Speichern dieser Objekte muss sie eine entsprechende Abfrage zusammenbauen.
Während viele Entwickler der Einfachheit halber die Abfragen mit Platzhaltern versehen und direkt in ihren Quelltext schreiben, herrscht besonders im Enterprise-Bereich die Meinung vor, dass sich zwischen der Anwendungslogik und der Datenbank eine abstrahierte Zwischenschicht befinden muss. Damit ist eine Komponente gemeint, die bei Lesevorgängen die zeilenbasierten Ergebnismengen einer SELECT-Abfrage in Objekte der Anwendungssprache umwandelt, und beim Speichern der Objekte eine INSERT- oder UPDATE-Abfrage generiert. Diese Zwischenschicht wird in der Fachsprache "Objektrelationaler Mapper" (ORM-Mapper) genannt.
Einer der (erschreckenderweise) besten und bekanntesten ORM-Mapper ist Hibernate. Diesen Mapper kann man in erster Linie mit Java oder .NET (nHibernate) verwenden. In vielen Kreisen wird Hibernate aufs Höchste gelobt, allerdings weiss jeder echte Programmierer, dass man sich auf die Lobgesänge der selbsternannten IT-Koryphäen nicht immer verlassen kann - denn das verhält sich ähnlich wie mit der Werbung für Alltagsprodukte wie Handys, Autos oder Versicherungsverträge. Man muss längerfristig mit dem System gearbeitet haben, um wirklich eine Aussage darüber treffen zu können.
Die vor allem bei Theoretikern vorherrschende Anerkennung für Hibernate wird bei der praktischen Anwendung dieses ORM-Mappers schnell schwinden: Der objektrelationale Mapper, der eigentlich die Arbeit mit Datenbanken vereinfachen soll, übernimmt nach und nach die Kontrolle über die gesamte Anwendungsstruktur. Der Entwickler muss nach und nach seine Autorität über seine Software an Hibernate abgeben - denn die geringste Abweichung von den Idealen und Vorstellungen von Hibernate wird diktatorisch aufs Härteste bestraft:
Mit einer Exception.
Nein, nicht mit irgendeiner hilfreichen Exception, die beim Progammierer ein Licht aufgehen lässt und ihn auf seinen Fehler hinweist, sondern mit einem sphinxischen Rätsel, was selbst die erfahrensten Entwickler erst nach tagelanger, zermürbender Fehlersuche lösen können. Man kann sich das etwa folgendermaßen versinnbildlichen: Ein verstauchter linker Fuß macht sich duch Schmerzen im rechten Handgelenk bemerkbar. OK, auch in diesem Fall kann man sich da den Zusammenhang erklären: Vielleicht ist im Fuß ein Nerv eingeklemmt und sendet fehlerhafte Signale an das Rückenmark, die dann als Schmerzen im Handgelenk interpretiert werden. Aber selbst wenn das biologisch möglich ist - man wird es wohl kaum einem Arzt vorwerfen können, dass er das Problem nicht auf den ersten Blick erkennt, vielleicht auch nicht auf den zweiten Blick.
Ein weiteres Highlight von Hibernate ist der Umgang mit Beziehungen zwischen Objekten bzw. Datenbanktabellen: Man nehme an, wir haben eine Klasse "Hersteller" und eine Klasse "Produkt". Ein Hersteller kann dabei n Produkte haben:
- Hersteller "CoffeeMaster" (ID=1); Produkte: "UltraJava 2000", "Espressomat 650i" und "SuperBean C300".
- Hersteller "SuperCafe" (ID=2); Produkte: "CoffeeCreator Plus" und "ExtraEspresso"
Man nehme an, wir möchten nun alle Produkte von CoffeeMaster löschen. Normalerweise würde man einfach ein SQL-Statement schreiben, welches alle Produkte mit der Eltern-ID 1 entfernt. Eine Zeile, fertig. Nicht jedoch bei Hibernate. Nachdem man dem System mit Hilfe von mindestens drei Klassen und sechs Methoden verklickert hat, dass man die Produkte löschen möchte, werden diese erstmal aus der Datenbank gezogen, schlimmstenfalls mit einem JOIN auf den Hersteller. Man muss doch schließlich genau wissen, was man da löscht. Logisch? NEIN. Danach werden die Produkte EINZELN gelöscht, mit einem Statement für jede Zeile. Der dahinterliegende Grund ist nämlich, dass die Hibernate-Logik sich massiv sträubt, mit simplen Parent-Child-Beziehungen zu arbeiten.
Man kann somit in der Tat auch kein Produkt erstellen und ihm als Parent-ID z.B. die 2 mitgeben, um es "SuperCafe" zuzuweisen. Man muss stattdessen das gesamte Objekt "SuperCafe" aus der Datenbank lesen und es beim Zusammenbauen des Produkt-Objektes an die Stelle stecken, wo normalerweise die 2 als Parent-ID ihren Platz hätte. Das heisst aber alles nicht, dass Hibernate nicht auch nur mit Wasser kocht: Nach dem Durchlaufen von hunderten von internen Methoden (und möglicherweise dem Werfen kryptischer Exceptions) generiert der ORM-Mapper am Ende auch nur ein Statement, welches eine neue Zeile in die Produkttabelle schreibt und ihr - so wie es schon seit Anbeginn der Menschheit gemacht wird - die 2 als Parent-ID mitgibt. Sämtliche Versuche, diese Logik zu umgehen, werden drakonisch mit furchterregenden Exceptions geahndet.
Aufgrund der vielen unnötigen Abfragen leidet zu einem gewissen Grad die Systemperformance. Das wird von Hibernate durch einen höchst umstrittenen Cache-Mechanismus gelöst: Die Grundidee dabei ist es, aus der Datenbank gelesen Objekte in einem Objekt-Cache zwischenzulagern. Wenn man also eine Anwendung mit vielen Benutzern und großen Datenmengen betreiben möchte, so ist es mit den 8GB RAM für das Serverchen längst nicht getan... Abgesehen davon, dass es eigentlich vollkommen kontraproduktiv ist, ineffiziente SQL-Abfragen durch ein Caching im Arbeitsspeicher auszugleichen (cf: das Waschbecken läuft über - egal, legen wir einfach mehr Lappen und Handtücher auf den Boden, wozu den Abfluss reinigen?), stellt der Caching-Mechanismus zudem auch noch Anforderungen an den Entwickler. Aufgrund der Zickigkeit von Hibernate müssen unnötige Klassen und Methoden erstellt werden und es müssen Kopplungen zwischen Klassen hergestellt werden, die für die Anwendungslogik keinerlei Sinn machen, sondern einzig und allein dem Zweck der Befriedigung von Hibernate dienen. Jede Entwicklung wird somit dank solcher vermeintlicher hilfreicher Frameworks zu einer schieren Dystopie.
Diese und viele andere Undinge kann man im Twitter-Account ElHiberntico verfolgen - und täglich kommen neue, furchteinflößende, gänsehauterregende oder schlichtweg deprimierende Berichte über die praktische Arbeit mit Hibernate - das Reservoir an möglichen Hibernate-Exceptions scheint nämlich unerschöpflich zu sein:
http://twitter.com/ElHibernetico
Viel Spaß beim Lesen - ich hoffe, es kriegt hier keiner Alpträume. Und wenn doch, dann sollte man einfach zur Beruhigung die Dokumentation zu den MySQL-Funktionen von PHP durchlesen, dann wird alles wieder gut :-)
So long
- Oleg





