Datenbankversionierung – Vor- und Nachteile bestimmter Praktiken

Gerade hatte ich mal wieder ein Schlüsselerlebnis in meiner Programmierung. In diesem Fall gab es ein Kollision mit YAML (YAML Ain’t Markup Language) – ich spreche explizit von Kollision, denn meine Erfahrung ist für den verwendeten Anwendungsfall einfach nur schlecht bzw. ich sehe leider aktuell einige Nachteile.

Datenbank-Schemata versionieren

Zur Versionierung von Datenbank-Änderungen bzw. um den Weg von einer “leeren” Datenbank bis hin zum aktuellen Stand der Entwicklung zu beschreiben verwendet man in der Regel entsprechende Tools wie Liquibase oder Flyway. Das Prinzip ist im Prinzip immer das Gleiche – mit ein wenig “Rahmenwerk” drum herum steuert man die Ausführung der Datenbank-Statements. Dabei kann man auf verschiedene Szenarien eingehen, einer der häufigst genannten ist, dass man ein Produkt mit verschiedenen Datenbank-Servern laufen lassen kann. Das ist beispielsweise wichtig wenn man Software an Firmen (seltener private Anwender) ausliefert, sich aber darauf einstellen muss, dass dort nicht unbedingt die aktuelle Version von MySQL läuft sondern eben im Zweifel auch ein PostgreSQL oder eine andere (kommerzielle) Datenbank wie Oracle. Continue reading

So bitte nicht – Schnittstellen

Es ist an der Zeit die lose Reihe mal wieder etwas weiter zu führen, mit einem Thema das mich die letzten Tage wiederholt beschäftigt hat: Schnittstellen.

In der IT-Welt beschreiben Schnittstellen verschiedenste Dinge – den meisten vertraut sind die Begrifflichkeiten aus der Hardware – hier erkennt man auch noch recht klar die Eigenschaft einer Schnittstelle, sie bildet die Grenze zwischen zwei Geräten, verallgemeinert von Systemen. Welches System an einer Schnittstelle andockt ist im ersten Moment unerheblich – die meisten Schnittstellen sind universell gehalten: Ob man eine Tastatur oder eine Maus in eine USB-Buchse einstöpselt ist im ersten Moment unerheblich. Die Schnittstelle ist so ausgelegt, dass über verschiedene Ebene hinweg eine Kommunikation nach einem bestimmten Muster stattfinden kann. USB beispielsweise gibt verschiedene Operations-Modi für unterschiedliche Anwendungszwecke – von der Maus als Eingabegerät über den GPS-Sensor bis hin zum Speichermedium. Die häufig verwendeten Hadware-Schnittstellen sind durch verschiedene Normen und Vorschriften festgelegt. In der Industrie gibt es zudem verschiedene Eigenentwicklungen für spezielle Zwecke, diese sind nicht zwingend so universell wie USB, aber sie erlauben auch weiterhin den Austausch von Informationen von einem System zu einem anderen hin. Im Fahrzeug z.B. per CAN-Bus: Dort meldet die Klima-Regelung an die Klima-Anlage die gewünschte Temperatur und die aktuelle Temperatur, der Klima-Kompressor wird daraufhin entsprechend in der Leistung geregelt.

Soweit einmal der Sinn und Zweck von Schnittstellen an anschaulichen Beispielen, aber mit der Hardware ist ja noch lange nicht Schluss. Vielmehr ist das ja noch alles nachvollziehbar und man kann sich unter der Definition einer Schnittstelle sehr leicht etwas vorstellen. Aber auf etwas höherer Abstraktionsebene existieren weiter Schnittstellen. Diese sind nicht so leicht erkennbar, aber dennoch vorhanden.

Jeder Programmierer der nicht absolut blutiger Anfänger ist, kennt die Erzeugung von wieder verwendbarem Code – nahezu jede Programmiersprache (von einigen Exoten einmal abgesehen) kennt den Begriff einer Funktion oder Methode. Das Prinzip dieser Konstrukte lehnt sich an der Mathematik an: Meist gibt man eine bestimmte, wohldefinierte Menge von Werten in eine Funktion hinein, die daraus etwas errechnet, etwas auslöst etc. in aller Regel gibt es auch noch einen Rückgabe-Wert (je nach Programmiersprache gibt es auch Möglichkeiten mehrere Werte zurück zu geben, entweder über den Stack oder einen reservierten, gemeinsam genutzten Speicherbereich, oft als Heap bezeichnet – das hier zu erläutern würde allerdings zu weit führen …). Wenn man das Prinzip der Funktionen einmal verstanden hat, sind diese einfach praktisch zu verwenden. Wichtig ist hierbei immer die Parameter-Reihenfolge (wie in der Mathematik auch) und je nach Programmiersprache auch noch der Werte-Typ der Parameter (starke oder schwache Typisierung: In Skriptsprachen wie PHP und Perl ist der Typ egal, in Java oder C müssen auch die Typen übereinstimmen – für beide Standpunkte gibt es gute Argumente, ich tendiere mittlerweile eher zu starker Typisierung und würde sie mir auch in PHP wünschen … aber auch das würde diesen Artikel leider sprengen). In ganz ähnlicher Weise lässt sich das auf objektorientierte Programmierung ausweiten. Auch dort gelten die gleichen Grundsätze, zusätzlich gibt es noch ein paar nette Gimmiks on top.

Gehen wir noch eine Abstraktionsstufe höher, dann sind wir bei Abläufen und Prozessen und eigentlich nicht mehr zwingend in der Softwarewelt verhaftet. Jeder Austausch von Daten über Systemgrenzen hinweg benötigt eine Schnittstelle. Egal ob die beiden Systeme nun Rechner sind, Menschen, Unternehmen oder sogar jede beliebige Kombination aus den vorgenannten. Überlegen Sie einmal inwiefern ein Rechner ihnen bestimmte Schnittstellen eröffnet und wann sie diese nutzen. Etwas Hirnakrobatik, aber wenn man es mal gemacht hat, sind viele Dinge am Rechner mit einmal logisch verständlich.

Was leider immer wieder schief geht ist die Spezifikation entsprechender Schnittstellen in dieser Abstraktionsebene. Oftmals sind sich die beiden Kommunikationspartner nicht bewusst, dass eine klar definierte Sprache/Syntax/Form der Kommunikation notwendig ist um Missverständnisse und Fehler zu vermeiden. Das beginnt meist mit solchen Sprüchen “wir bekommen dann eine Datei, da steht alles drin was uns unser Partner mitteilen möchte und was wir wissen wollen…”. Wie diese Datei am Ende aussieht ist bei solchen Sachen meisten leider erst einmal egal – dank der Intelligenz vor dem Monitor ist es dem Betrachter auch egal ob ein Textdokument als reiner Text, Word-Datei, Vektorgrafik oder als pixelbasierte Bilddatei vorliegt. Lesen und verstehen kann der Mensch das alles. Problematisch wird es wenn es dann an die Automatisierung geht. Da sind nicht alle Formate wirklich gut geeignet – jeder der einmal versucht hat aus einem abfotografierten Text wieder einen editierbaren Text zu machen weiß wovon ich spreche: Ohne Optial Character Recognition (OCR – Texterkennung) geht da gar nichts und selbst die ist meist nicht sonderlich fehlerfrei, von der Semantik der einzelnen Texteile und dem Layout mal ganz zu schweigen. Besser sind dann schon Excel oder Word-Dateien wobei die auch nicht immer kompatibel untereinander sind. Es gilt auch hier das alte Mantra: Je einfacher, je simpler um so besser. In der IT hat man für diverse Datentypen gut erprobte Ablage-Möglichkeiten entwickelt. Tabellen kann man recht gut in SQL oder CSV ausdrücken, wenn es etwas weniger stark strukturiert ist, kommen XML-Varianten in Frage.

Was lernen wir aus diesen Sachen?

Erstens: Es geht nicht ohne Schnittstellen, selbst innerhalb eines Programms gibt es in der Regel verschiedene Module oder Funktionen die Daten untereinander austauschen und so zum Gesamtsystem beitragen. Diese Schnittstellen sind in der Regel durch die verwendete Programmiersprache und die Programmierweise vorgeben (funktionale, prozedural oder objektorientiert).

Zweitens: Schnittstellen nach außen hin (egal ob Mensch oder Maschine) müssen klar erkennbar und definiert sein. Man denke hierbei immer an die Zukunft und überlege ob es vielleicht sinnvoll ist, an einigen Stellen bereits die Option für eine externe Schnittstelle zu schaffen, später nachrüsten ist immer schwierig und bestehende Schnittstellen verändern ist wie beim Auto plötzlich Kupplungs- und Gas-Pedal zu vertauschen: Die Konsequenzen sind bestenfalls amüsant, schlimmstenfalls gefährlich und es kostet sehr viel Nerven…

Drittens: Bereits bei der Planung von Systemen berücksichtigen welche Daten mit externen Systemen automatisiert ausgetauscht werden sollen und wie. Einerseits muss klar definiert sein, welche Daten an externe Systeme bereit gestellt werden sollen. Evtl. ändert sich durch solche Anforderungen der Erfassungsbedarf in der eigenen Anwendung/dem eigenen System oder lässt einigen Sachverhalten eine neue Priorität zukommen. Auch wichtig ist das Format – dieses muss für den Anwendungsfall geeignet sein, Bilder in Word einzufügen um sie daraus wieder zu extrahieren ist ungeschickt – einfacher geht es wenn man Bilddateien direkt verschickt. Ist die Information tabellenförmig, so kommen Excel oder die simple Variante CSV (comma separated values) in Betracht. Für komplexere und ggf. nicht immer einheitliche Datenaustausche kann man sich mit XML behelfen. Wobei immer zu beachten ist: In aller Regel steht hinter jeder XML-Datei und jedem System auch wieder ein relationales Backend (aus Performance-Gründen) – es lohnt also, ggf. einmal auch die Betreuer und Entwickler des Partner-Systems über deren Struktur zu fragen.

 

 

MySQL und der Null-Pointer

Da denkt man mal wieder an nichts Böses und prompt holt einen das 2. Gebot der C-Programmierung ein – obwohl man nicht in C programmiert, sondern eigentlich nur in einer Datenbank unterwegs ist.

Die 10 Gebote finden sich hier:

http://www.geekhideout.com/c-ten-commandments.shtml

Und die Aussage “Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.”  ist mir dabei mal wieder wie Schuppen von den Augen gefallen.

Was war passiert? Ich habe eine Datenbank-Tabelle, die mir angibt welche Subunternehmen an einer Arbeit mit beteiligt werden: Pro Arbeit kann es mehrere Subunternehmer geben, und jeder Subunternehmer kann an beliebig vielen Arbeiten mitwirken. Soweit so gut, ein klassischer Fall einer n:m Beziehung (wie man das im ER-Modell bezeichnen würde, UML ist da etwas genauer und würde in diesem Fall von einer 0..n:0..m Beziehung sprechen). Nun habe ich feststellen müssen: Selbst wenn das offiziell nur ein Subunternehmen ist, dann kann das immer noch recht groß ausfallen – die reine Angabe einer Firma als Referenz reicht also nicht unbedingt aus. Soweit ja kein Problem: Es gibt ja auch noch eine Tabelle aller am Projekt beteiligten Mitarbeiter – und ggf. ist einer davon als Ansprechpartner definiert. Leider nur ggf. und nicht zwingend – bei kleineren Firmen gibt es eine solche Zuweisung leider nicht.

Nun ja, was macht der geneigte Datenbank-Programmierer, genau, er macht das was er mal gelernt hat: Wenn es keine Referenz gibt, dann tragen wir den speziellen Wert “NULL” ein. Selbst die referentielle Integrität kommt damit nicht aus dem Tritt – vielmehr ist es ein ganz klares Zeichen: Hier muss gar nicht erst nach einem passenden Partner in der anderen Tabelle gesucht werden – es gibt ihn schlichtweg nicht. Alles nicht richtig spannend und für mich bis dato kalter Kaffee.

Spannend wurde es im Zusammenhang mit der Modifikation der Datensätze: Um zu verhindern das Doppelte Einträge vorhanden sind, habe ich einen Unique-Key über die drei Spalten “Arbeit”,”Firma”,”Ansprechpartner” angelegt. Zudem enthält die Tabelle noch ein paar zusätzliche Information, die aber nichts zu Sache tun. Was liegt also näher als mit einem “Insert ignore”, “Upsert” bzw. dem praktischen MySQL-Konstrukt “Insert into …. on duplicate key update ….”  zu arbeiten? Das ist im Prinzip wie geschaffen für den beschriebenen Fall: Gibt es schon einen entsprechenden Eintrag in den  Schlüsselspalten, dann muss man nur noch die Satellitendaten entsprechend dem Bedarf aktualisieren (das lässt z.B. gut Luft für eine Überwachung durch wen und wann die letzte Änderung durchgeführt wurde).

Doch was ist das: Beim Testen der Funktion tauchen nach dem Speichern der Modifikation plötzlich Einträge doppelt und dreifach auf – also gerade nicht das was man erwartet hätte.

Erster Schritt in der Fehlersuche: Irgendwas im Code wurmig? – Nein, da stimmt alles und es gibt auch keine enstsprechenden Extra-Inserts – die Statements sehen so aus wie man sich das gewünscht hat. Nächster Schritt: Statements “manuell” ausführen und … aha! – man kann Einträge bei denen der Ansprechpartner auf “NULL” steht beliebig oft einfügen ohne dass die Schlüsselmechanismen ansprechen…. erster Verdacht: MySQL-Bug! – aber weit gefehlt: “It’s not a bug, it’s a feature” zumindest in den Augen der MySQL-Entwickler … aber Abhilfe gibt es auch: einfach einen “primary” anstelle eines “unique” keys nehmen…. gesagt getan: Aber dann hagelts erst richtig: Denn in einem “Primary”-Key dürfen keine “NULL”-Werte auftauchen – was macht MySQL intern: Man behilft sich, und verändert sie Spalte – sie darf jetzt nicht “NULL” enthalten und der Default-Wert ist “0” – und das trotz eines Fremdschlüssels? – Und genau da beißt sich die Schlange dann in den Hintern … denn die bestehenden Daten sind komischerweise “valide” – keine Schlüsselverletzung. Aber Einfügen geht nicht mehr: Will man “NULL” einfügen sperrt sich die Tabellendefinition, will man “0” verwenden, gibt es von Seiten der referentiellen Integrität auf die Finger … So ein Mist!

Lösungen gibt es bisher dafür keine 😐 zumindest nicht auf Datenbank-Ebene. Ich habe jetzt einen Hack-Around gemacht: Man lösche alle Einträge zur Arbeit und lege sie danach alle wieder an und schon kann man mit dem Unique-Key leben, man hat praktisch die Prüfung etwas weiter nach oben verlagert (wo sie in meinen Augen nicht hingehört).

Aber wie schon da Commandement von oben sagt: Null-Pointer sind ganz böse Dinge – und wenn man nicht aufpasst findet man sich in der Hölle wieder 😉