Die Sache mit der Datenbank

Short Disclaimer: Wenn von Datebanken gesprochen wird, sind ausschließlich SQL-Datenbanken gemeint.

Als ich noch sehr frisch im Software Business war, geriet ich oft zwischen die Fronten, wenn die Teamkollegen über Datenbanken diskutierten. Ich fand diese Diskussionen zu Beginn sehr spannend, später eher anstrengend, da immer wieder die gleichen Punkte diskutiert wurden. Dabei gab es zwei Lager:

Die einen vertraten die Ansicht, dass Performance höchste Priorität hat und man mit Stored Procedures (in MSSQL Terminilogie, bei anderen Datenbanken oft als Prepared Query bekannt) sehr weit kommt und immer zu benutzen sind. Generell macht man so viel wie möglich in der Datenbank selbst, um möglichst auch alle Features auszunutzen. Desweiteren beginnt die Entwicklung bei der Datenbank, heißt, man entwirft zuerst ein fertiges Entity-Relationship Modell und baut danach die Anwendung auf diese fertige Datenbank auf.

Die anderen standen auf dem Punkt, dass gutes Anwendungsdesign mehr Priorität hat als eine sehr schnelle Datenbank. Entwicklung beginnt nicht bei der Datenbank, sondern bei der Anwendung. Die Datenbank wird nach Anforderungen der Anwendung entworfen, nicht umgekehrt. Dabei wird die Datenbank auch erst einmal auch nur als ein simpler Store behandelt. Man schreibt Daten hinein und irgendwann fragt man sie wieder ab, fertig. Wenn sich im Betrieb herausstellt, dass bestimmte Dinge langsam laufen, werden Änderungen am Schema vorgenommen oder einzelne Abfragen zum Beispiel in Stored Procedures umgewandelt.
Ich selbst fühle mich im zweiten Lager wohler. Ich habe mir oft schon schräge Blicke und Unverständnis eingefangen, als ich sagte, dass die Datenbank in erster Linie nur ein Store ist, den ich so abstrakt wie möglich behandeln möchte. Damit stand ich ziemlich allein da. Zumindest kam es mir oft so vor. Warum? „Kann der jetzt nicht mit SQL umgehen oder wie?“ Doch kann ich. Aber ich bin besser darin, Geschäftslogik, Abläufe usw. in z.B. Ruby, JavaScript, C# o.ä. auszudrücken als in SQL. Ein weiterer entscheidender Punkt ist, dass man keinen Kontext wechseln muss. Man liest Code und plötzlich muss man für diese eine wichtige Berechnung die Datenbank aufmachen, die passende Stored Procedure heraussuchen und verstehen, wie das nun in SQL abgebildet ist. Das geht, ist aber aufwändig.
Für mich ist es oft einfach ein großer, zusätzlicher Aufwand eine bestimmte Sache nun nicht in meiner Anwendung selbst, sondern in der Datenbank mit Stored Procedures, Functions und Triggern zu lösen. Dazu kommt auch, dass die gewonnene Performance meist nicht die zusätzliche Entwicklungszeit rechtfertigt.

Daten abfragen

Bei meinen ersten Gehversuchen mit einer SQL-Datenbank war es so, dass ich meine Abfragen direkt als String in den Code schrieb, diese ausführte und aus der Ergebnismenge die Daten suchte, die ich brauchte. Kann man machen, ist aber ab einem bestimmten Punkt nicht mehr effektiv.
Irgendwann lernte ich, dass es OR-Mapper gibt und viel Zeit sparen, wenn man weiß, wie sie funktionieren. Der erste OR-Mapper mit dem ich arbeitete, war das Entity-Framework (Bestandteil des .Net Frameworks). Daten abfragen und schreiben funktionierte ohne Probleme und für die ersten Projekte war das immer das Mittel der Wahl.

Ich kam in eine neue Umgebung, neue Menschen, neue Tools, neues Projekt. Dort stellte sich auch sehr schnell heraus, dass sich die EntwicklerInnen beim Thema OR-Mapping auch in zwei Lager teilten. Die einen befanden OR-Mapping für gut, die anderen waren strikt dagegen. Das Contra-Lager brachte an, dass man ja nicht im Ansatz in der Lage sei zu kontrollieren, welche SQL Abfragen generiert würden. Dadurch wäre die Anwendung erstmal bei Definition langsam. Resultat der ganzen Sache war, dass es nur ein einziges zu benutzendes Tool für den Job gäbe. Mir ist leider der Name entfallen, aber es funktionierte folgendermaßen:

Es gibt eine lange XML-Datei, die nur SQL-Abfragen enthält. Diese Abfragen enthalten Platzhalter, die später mit den eigentlichen Daten ausgefüllt werden. Jede Abfrage hat einen Key, mit dem sich die Anwendung die Abfrage aus der Datei laden kann. In der Anwendung werden dann die Daten serialisiert, in die Abfrage eingesetzt und dann zum DB-Server gesendet. Bei einem SELECT muss man dann selbst aus dem Resultset die Dinge heraussuchen, die man haben möchte. Viel Arbeit, aber es wurde versprochen, dass alles sehr schnell läuft. Ob das nun wirklich so war, weiß ich nicht. Allerdings gab es hier einen ganz gravierenden Nachteil: Schemaänderungen zogen einen immensen Arbeitsaufwand für EntwicklerInnen nach sich. Warum? Dadurch, dass alle datenbankspezifischen Informationen in XML-Dateien definiert waren, gab es zur Übersetzungszeit keinerlei Überprüfung, ob z.B. die Spaltennamen noch alle gültig sind. EntwicklerInnen müssen manuell mit Suchen und Ersetzen sicher stellen, dass z.B. ein neuer Spaltenname in der gesamten Anwendung da ist.
Die Anwendungen, an denen ich mitgearbeitet habe, haben zuerst Entity-Framework, später NHibernate benutzt. Hier wurde aus den sogennaten Entity-Klassen die Datenbank generiert, später sogar mit Unterstützung für Migrationen ähnlich wie bei Rails. Das heißt, das Umbennen einer Spalte findet zuerst im Code statt, dadurch kann der Compiler helfen und feststellen, ob weitere Teile im Code angepasst werden müssen. Dadurch waren Schemaänderungen meist innerhalb weniger Minuten fertig, wobei die anderen eher eine oder mehr Stunden gebraucht haben.
Das Argument, dass Abfragen mit OR-Mappern langsam sind, wurde von den enstprechenden Personen nie wirklich belegt. Auch war es meist nicht von großer Relevanz, ob wir am Ende genau wissen, welche Abfragen geschickt werden. Es gibt Fälle, da möchte man das wissen, um zum Beispiel langsame Abfragen zu optimieren. Aber in erster Linie ging es nicht darum, möglichst schnelle Datenbankabfragen zu produzieren, sondern um schnell neue Anwendungen zu entwickeln.