Boot-Trick für Linux-Systeme wenn USB per BIOS nicht geht

Da wollte ich nur einen etwas älteren Laptop wieder soweit flott bekommen. Also die eigenen Daten sichern und eine Neuinstallation erzwingen. Leichter gesagt als getan. Backup dank ein wenig Ordnung im System und der Tatsache, dass es ein Linux-System ist war recht flott erledigt. Einfach die betreffenden Home-Verzeichnisse auf ein Backup-Medium (Netzwerk, externe Platte oder Stick) packen und schon ist man so gut wie fertig. Die Einstellungen des Systems waren mir in dem Fall nicht weiter wichtig – die waren ohnehin etwas “verbastelt”, da kam die Neuinstallation ja gerade recht.

Kniffelig wurde es dann aber diesmal beim Neuinstallieren des Betriebssystems: Von wegen einfach USB-Stick einstöpseln – Reboot und gut – leider nicht! Denn das alte Gerät verweigert sich jeglichem Boot-Versucht von einem USB-Stick und auch die SD-Card findet das System nicht beachtenswert wenn es ums Booten geht.

Nächster Versuch: Klassisch eine Boot-CD brennen – scheitert leider daran, dass die meisten Images mittlerweile größer als eine CD mit ihren 700MB sind. Dabei wollte ich schon aus gutem Grund ein ressourcenschonendes Lubuntu installieren (war auch vorher bereits drauf). Älter CD-Images waren auch nicht so ohne weiteres aufzutreiben. DVDs kann das alte optische Laufwerk nach einigen Versuchen nicht lesen (es steht leider auch nichts auf der Laufwerksklappe). Falls jetzt jemand fragt was man mit derart alten Systemen noch anfangen kann: Sie sind als Notbehelf in der ein oder anderen Situation doch ganz nützlich – vor allem wenn die Gefahr besteht, dass man das Gerät im Zweifel nicht wieder sieht oder es irreparabel beschädigt wird (das man auf derartigen Systemen auch keine privaten Daten lagert sollte jedem klar sein).

Jetzt war ich schon fast davor einen PXE-Server zu installieren und dann das System hoffentlich per Netzwerkboot auf die Spur zu bekommen, aber das ist ja doch etwas Umstand. Daher war das der absolut letzte Notnagel den ich gehen wollte. In der Zwischenzeit hatte ich auch irgendwo im Netz noch eine uralte Lubuntu-ISO-Datei gefunden (14.x – steinalt auf gut deutsch). Doch der Download zog sich etwas in die Länge. Mehr als genügend Zeit sich noch mit anderen Optionen zu beschäftigen.

Eigentlich ist die Lösung recht simpel: Was das BIOS nicht kann, kann das OS bzw. der Bootloader vielleicht dann doch. Da auf dem System ja bereits ein Linux samt Grub als Bootloader vorhanden war geht es dann wie folgt, nachdem man aus dem Bootmenü mit “c” in den Consolen-Modus gewechselt ist.

ls
set root=(hd1)
chainloader +1
boot

Wobei ls einem die möglichen vorhandenen Partitionen listet und man dann bei set root=…. natürlich die passende Partition angeben muss – hier hilft im Zweifel ein wenig ausprobieren. Mehr als nicht booten oder das alte OS booten kann ja nicht schiefgehen. Danke an die hilfreichen Kommentare hier.

Von der Kunst der Vereinfachung

Abstrahieren und vereinfachen liegt in der Natur des Menschen. Wir alle haben es natürlich gerne wenn eine Aufgabe “leicht” zu bewältigen ist oder ein Sachverhalt “einfach zu begreifen” ist. Eine grundlegende Eigenschaft dabei ist es, dass wir Dinge “ausblenden” die für die aktuelle Aufgabe nicht relevant sind oder deren genaue Zusammenhänge wir nicht bis ins Detail verstanden haben müssen um die Aufgabe zu lösen oder den Sachverhalt zu verstehen. Oftmals reicht es auch aus, die Hintergründe einmal umfassend bearbeitet zu haben und dann mit den gewonnenen Erkenntnissen und ggf. auch Erfahrungen weiter zu arbeiten. Jeder der ein Kraftfahrzeug fährt, hat sich (hoffentlich) einmal mit der dahinter stehenden Physik beschäftigt – doppelte Geschwindigkeit vervierfacht die Energie usw. Das alles setzt ein geübter Fahrer zusammen mit ggf. weiteren Erfahrungswerten ein um sein Fahrzeug im Verkehr sicher zu führen.

Wir Menschen sind auch Meister darin immer mehr dieser Denkarbeit zu automatisieren und uns somit das Leben noch leichter zu machen. Waren die ersten Computer noch groß und nur mit dem notwendigen Fachwissen zu bedienen, so benutzen wir heute fast schon wie selbstverständlich ein Smartphone dessen technische Komplexität um mehrere Größenordnungen über den ersten Großrechnern oder gar Heim-PCs liegt. Durch den Einsatz von Technik und entsprechenden Schnittstelle (der Fachmann spricht vom User-Interface) kann heute jeder mit minimalen Vorkenntnissen ein Smartphone zumindest grundlegend bedienen. Natürlich gibt es auch weiterhin die PowerUser bzw. Spezialisten die nochmals mehr aus dem Gerät “herausholen” bzw. auch bei bestimmten Fehlerbildern Abhilfe schaffen können (oder zumindest einmal wissen wo man nachschlagen kann um ein Problem zu beheben). Continue reading

Domain Name Server mit PowerDNS – Schritt 1 – Konzept, Netzwerk und Authorative Server

Nachdem ich ja schon einiges in der letzten Zeit über DNS und Co geschrieben habe, geht es diesmal um eine konkrete Umsetzung. Lange Zeit habe ich schon einen eigenen DNS-Server betrieben, damals noch mit BIND. Mit den letzten Serverumzügen habe ich das dann der Einfachheit halber meinem Hosting-Anbieter überlassen (das richte ich ein, wenn der Rest läuft …). Wie das mit aufgeschobenen Aufgaben so ist, irgendwann wird der Status Quo zu einen “Dauerorium” (die verstetigte Version eines Provisoriums).

Nun wollte ich es endlich angehen, und dabei auch einige zusätzliche Funktionen nachzurüsten. Unter anderem einen eigenen Ipv6 fähigen DynDNS-Service. Nach etwas Recherche stand ein grober Plan:

  • PowerDNS mit MySQL Backend
  • MySQL Server (läuft bereits, bekommt nur ein zusätzliches Schema)
  • Ein (Dyn)DNS-Frontend zur einfacheren Verwaltung und als HTTP(s) Schnittstelle zum Updaten
  • Microservice-Ansatz für die Einzelteile mit Docker als Paravirtualisierung
  • soweit möglich alles nativ in IPv6

Continue reading

Grundlagen zum Domain Name Service

Wie ich beim Schreiben meiner Artikel gemerkt habe, ist es sinnvoll gewisse Grundlagen einmal abzuspalten, um darauf verweisen zu können. Ich werde daher immer einmal wieder den ein oder anderen Grundlagen-Artikel hier zusammenschreiben, der im ersten Moment vielleicht etwas aus dem Zusammenhang gerissen scheint, aber ggf. wertvolle Informationen für alle Einsteiger enthält oder auch dem ein oder anderen Erfahrenen als Auffrischung dienen kann. Ich versuche das möglichst prägnant und wo immer möglich an Beispielen zu beschreiben.

In diesem Artikel geht es im um das Domain Name System – das Konstrukt welches zu Arbeit am Rechner etwas leichter macht, da sich das menschliche Gehirn in der Regel Namen besser merken kann als Zahlenfolgen.

Der langjährige Standard für DNS-Server im Unix und Linux-Umfeld ist der BIND-Daemon und so ziemlich jeder der sich schon einmal mit DNS-Namen hat etwas eingehender beschäftigen dürfen (oder müssen), der weiß, dass die Syntax der Config-Files nicht gerade in die Kategorie “leicht verdaulich” fällt. Zudem war BIND immer mal wieder ein Sorgenkind wenn es um Sicherheitslücken ging. Sehr verständlich, dass sich viele Website-Betreiber da lieber auf einen Hoster verlassen der das schon für sie regeln wird. Für mich auch lange ein valider Weg, aber eben auch einer mit Einschränkungen.

Continue reading

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

Container und IPv6 – Basics zum Verständnis / Netzwerkzuschnitt

In diesem Beitrag erläutere ich erst einmal die grundlegenden Probleme welche sich beim Einsatz von Container-Lösungen im Netzwerk ergeben und wie man sie sinnvoller Weise technisch lösen kann. Schlüssel hierfür ist die Verwendung von IPv6 anstelle von IPv4. In einem weiteren Artikel beschreibe ich dann die konkrete Umsetzung. Wer sich mit IPv6 und Containern bereits sicher fühlt kann diesen Artikel ggf. überspringen.

Container sind der aktuelle Stand der Technik was die Auslieferung und die Laufzeitumgebung für Serversoftware betrifft. Die Para-Virtualisierung löst einige Probleme die man typischer Weise hatte, unter anderem das Alles-oder-Nichts-Problem, wenn es darum geht Systemsoftware zu aktualisieren. Mit jedem größeren Versionssprung gibt es die Gefahr, dass bestimmte Teile des eigenen Codes nicht mehr laufen. Aktuell gibt es immer noch genügend Projekte und Software welche auf ältere (um nicht zu sagen teilweise uralte) Versionen von PHP angewiesen sind um zu funktionieren. Mit dem Versionssprung von 5.x nach 7.0 sind einige Altlasten und Funktionen über Bord geworfen worden, unter anderem auch der gesamte Stack für die MySQL-Anbindung, was einigen Anwendungen dann doch das Genick bricht. Häufig stand man nun vor dem Problem, für einen Teil des gehosteten Angebots die Version anheben zu wollen, aber die Altlasten müsste man dafür erst einmal auf Stand bringen. Das ist nicht immer ohne weiteres möglich. Mit Containern schafft man sich hier ggf. kleinere Services mit jeweils ihrer maßgeschneiderten Umgebung – inklusive abgeschottetem Datenbankserver, den sich nicht mehr alle Angebote teilen müssen (über die Vor- und Nachteile von Datenbanken in Containern kann man einen eigenen Artikel schreiben).

Continue reading

Rasperry Pi 3B+ mit Samsung LE22S86BD

Bereits seit einiger Zeit habe ich einen Rasperry Pi, nachdem ich bereits einiges damit herumgespielt hatte soll er nun endlich seinen Stammplatz als Medienzuspieler am Fernseher bekommen, damit man nicht jedesmal einen Laptop anschließen muss, nur um einen Stream zu schauen. Zumal ich ja sagen muss, dass das aktuelle Fernsehprogramm mich ohnehin nicht vom Hocker haut und bei meiner ganzen sportlichen Aktivität bleibt mit selten die Zeit zum Fernsehschauen.

Auch daher muss es erst einmal der aktuell vorhandene Fernseher tun, ein etwas älteres Modell von Samsung, Modell LE22S86BD. Die Einrichtung des Raspi habe ich natürlich am Monitor mit samt Tastatur, Maus und Netzwerk durchgeführt und nicht am Fernseher. Das Ergebnis ist dann leider auch gar nicht Plug&Play sondern eher fühlt man sich in die Zeiten des “Plug&Pray” zurück versetzt. Denn nach dem Anschluss per HDMI sieht man nahezu nichts, genauer gesagt nur eine Fehlermeldung auf dem LCD-TV “Mode not supported”. Da kommen bei mir prompt Erinnerungen an die ersten Gehversuche mit Xfree86 als Grafikserver – damals musste man auch mit viel Trial&Error die richtigen Einstellungen für Grafikkarte und Monitor herausfinden. Wenn man das übertrieben hat, konnte man mit den Versuchen sogar durchaus Geräte dauerhaft unbrauchbar machen.

Mit folgenden Einstellungen in der “/boot/config.txt” bekommt man bei diesem Fernseher ein formatfüllendes Bild ohne Trauerränder hin:

hdmi_group=1 # CEA => TV-modes
hdmi_mode=5 # 1080i, 60Hz 
disable_overscan=1 # TV does not support overscan

Das ist die einzige Kombination die ich zuverlässig ans Laufen bekommen habe, auch wenn “tvservice” durchaus andere modi meldet:


pi@raspberrypi:~ $ tvservice -m DMT
Group DMT has 5 modes:
mode 4: 640x480 @ 60Hz 4:3, clock:25MHz progressive
mode 9: 800x600 @ 60Hz 4:3, clock:40MHz progressive
mode 16: 1024x768 @ 60Hz 4:3, clock:65MHz progressive
mode 35: 1280x1024 @ 60Hz 5:4, clock:108MHz progressive
mode 57: 1680x1050 @ 60Hz 16:10, clock:119MHz progressive


pi@raspberrypi:~ $ tvservice -m CEA
Group CEA has 9 modes:
mode 1: 640x480 @ 60Hz 4:3, clock:25MHz progressive
mode 2: 720x480 @ 60Hz 4:3, clock:27MHz progressive
mode 3: 720x480 @ 60Hz 16:9, clock:27MHz progressive
(native) mode 4: 1280x720 @ 60Hz 16:9, clock:74MHz progressive
(prefer) mode 5: 1920x1080 @ 60Hz 16:9, clock:74MHz interlaced
mode 17: 720x576 @ 50Hz 4:3, clock:27MHz progressive
mode 18: 720x576 @ 50Hz 16:9, clock:27MHz progressive
mode 19: 1280x720 @ 50Hz 16:9, clock:74MHz progressive
mode 20: 1920x1080 @ 50Hz 16:9, clock:74MHz interlaced
pi@raspberrypi:~ $

Falls also irgendjemand noch diese Kombination betreiben will, ich hoffe ich erspare ihm hiermit eine ziemlich mühsame Ausprobiererei – Kommentare und weiter funktionierende Modi sind natürlich herzlich willkommen.

 

Frameworks in der Programmierung – wirklich so essentiell wie immer behauptet?

Wenn immer man sich aktuell umschaut auf dem Jobmarkt, dann sind im Softwarebereich immer Erfahrungen mit Frameworks gefordert oder zumindest gern gesehen. Nun habe ich ja auch einige Erfahrung und ich sehe das immer wieder etwas zwiegespalten.

Machen wir uns erst einmal ein Bild mit welchem Ziel Frameworks entstanden sind. Mehrheitlich ist ihr Ziel die Entwicklung von Software zu vereinfachen und zu beschleunigen. Der Entwickler soll sich weniger Gedanken machen müssen über einige technische Details und soll sich ganz und gar auf seine Applikation konzentrieren können. Das klingt ja erst einmal nicht schlecht. Wer möchte sich schon gerne mit den Details einer TCP/IP-Verbindung womöglich sogar noch Verschlüsselung beschäftigen müssen wenn er nur Daten von einem Server irgendwo im Netz abholen möchte. Das klingt doch schon insgesamt sehr verlockend, aber jede Medaille hat bekanntlich zwei Seiten. Jede Technologie bringt ihre Tücken und ggf. auch Einschränkungen mit. Eine dieser Einschränkungen ist es, dass ein Framework gewisse Regeln vorgibt an die man sich halten muss, wenn man es verwendet. Das muss noch nicht mal negativ sein, vermeidet es doch, dass man sich unfreiwillig irgendwo eine Bremse einbaut, die man ggf. später wieder ausbauen muss.

Da ich relativ viel im Bereich Webentwicklung mache, beschränke ich mich im Folgenden bei den Beispielen auf dort häufig anzutreffende Frameworks und meine Erfahrungen mit Ihnen.

Eines der für mich wichtigsten Frameworks ist und bleibt JQuery und es ist eines der wenigen Frameworks, das ich schätzen gelernt habe. Warum das so ist? JQuery schreibt mir als Entwickler nicht irgendein Gerüst vor, vielmehr gibt es eine Reihe von best-practices und jede Menge Beispiele. Brauche ich aber nur eine simples Ein- und Ausblenden eines Formularelements dann muss ich mich nicht erst mit dem Bauen und Administrieren von Formularen im Sinne des Frameworks beschäftigen. Stattdessen kann ich es einfach aus und einblenden wie es gerade nötig ist. Minimaler, schlanker Code mit dem ich das Element finden und damit etwas machen kann. Derartige Abstraktion (noch dazu über Browser-Grenzen hinweg) finde ich sehr hilfreich. Auch wenn sich JQuery selbst als Framework bezeichnet – für mich hat es eher den Charakter einer umfangreichen Library (aber Library klingt natürlich so altbacken, das kannten ja schon die Entwickler aus C … daher kann man das natürlich in einem modernen Umfeld nicht offiziell so nennen).

Das nächste Framwork, mit dem ich auch immer noch arbeiten darf, ist ZendFramework. Mittlerweile ist davon die Version 3 auf dem Markt, vor allem nachdem die Version 2 reichlich wenig Akzeptanz gefunden hat. Im Arbeitsumfeld sind wir noch größtenteils mit Zend1 unterwegs. Das wird mittlerweile definitiv nicht mehr weiter entwickelt, aber wenn es läuft und es keine sicherheitskritischen Probleme gibt gilt die alte Weisheit: “never touch a running system”. Leider muss ich sagen, dass mir das Framework häufig mehr Arbeit verursacht als es je einsparen könnte. Vieles ist einfach nur sehr umständlich, trotz oder gerade wegen der vorgegeben Struktur. Auf der einen Seite ist sie recht starr – es gibt nur eine Möglichkeit bestimmte Ergebnisse zu erreichen. Auf der anderen Seite sind die Funktionen dann doch recht häufig wachsweich – nahezu an jeder Stelle kann (oder muss) man Parameter als indiziertes Array übergeben.

Richtig enttäuscht hat mich das Framework dann bei der Umsetzung von Commandozeilen-Parametern. Im Quellcode des Framworks stehen noch jede Menge offene Todo-Punkte. Die Funktion an und für sich kann man “out of the box” fast nicht gebrauchen, denn unbekannte Parametern führen zwangsweise zu einem Abbruch der Verarbeitung. Man kann natürlich dann kurzerhand die Framework-Klasse als Basis hernehmen und die notwendigen Routinen in einer eigenen Klasse überschreiben. Aber so wirklich prickelnd ist das nicht, noch dazu bindet man sich damit sehr stark an das Framework – will man die Funktionalität in einem anderen Projekt wieder verwenden, so muss man die dort mit hoher Wahrscheinlichkeit neu implementieren. Ich habe mich in diesem Falle dazu entschieden, es dann gleich selbst zu machen, PHP bietet von Haus aus bereits recht umfangreiche Funktionen, unter anderem sei hier Getopt genannt.

Natürlich wollte ich auch mal schauen was aktuelle Frameworks so können und vielleicht machen die es ja besser. Also habe ich mir Symphony angeschaut. Auf den ersten Blick ist wieder alles “nice and shiny”. Sobald man aber etwas damit arbeiten möchte, musste ich feststellen: Die Doku und die Beispiele sind einfach nur sehr akademisch gehalten. Ich habe das Framework nach einigen Versuchen ad acta gelegt, weil es ein verdammt hoher Aufwand ist, einen recht simplen Fall abzubilden: Man nehme eine einfache 1:n-Relation wie sie jede Datenbank ohne Probleme abbildet: Eine Art News-Artikel wird einer Kategorie zugeordnet. Ein priviligierter Nutzer ist in der Lage die Tabelle der Kategorien bei Bedarf zu erweitern. Ein klassischer Fall, den man im User-Interface gerne und effizient mit Drop-Down-Menu realisiert. In Symfony ist das ein echter Krampf, spätestens bei der Verwendung der Anzeigenamen als Array-Indizes und der eindeutigen Werte als Array-Werte habe ich mir nur noch an den Kopf gelangt. Das funktioniert zwar dank UTF-8-Unterstützung aber es widerspricht sämtlicher Intuition. Die Beispiele zu Formularen sind da auch keine wirkliche Hilfe – da wird einfach darauf gesetzt, dass man es ja doch per Text umsetzen könnte. Das mag für schnelle Projekte sinnvoll sein, aber im Businessumfeld sind die Regeln dann doch etwas strikter. Man kann es natürlich auf der Server-Seite dann wieder prüfen und eine Fehlermeldung ausspucken – aber warum muss der Anwender erst Tippfehler machen und ggf. raten wenn es nur wenige valide Auswahlen gibt?

Das ist jetzt sicherlich keine umfassende Evaluation, aber in vielen Punkten gleichen sich die Frameworks dann doch. Ich werde mir auch noch weitere anschauen. In einem der nächsten Posts werde ich einige generelle Kritikpunkte die mir bisher bei nahezu jedem “Backend-Framework” aufgefallen sind näher ausführen.

 

Fiese Falle in MySQL bei Verwendung von Views in (Left)-Joins

Da habe ich doch kurz vor Weihnachten noch eine echte Knobelaufgabe erlebt, deren Ergebnis für den ein oder anderen sicherlich interessant sein dürfte. Ausgangspunkt war folgendes eigentlich simple SQL-Statement:

Select * from tableA left join viewC 
     on tableA.id=viewC.lookup

Soweit so unspektakulär, auch wenn das Original noch mit einigen weiteren Filtern gespickt war und die View nicht gerade ein einfaches Query kappselt.

Was allerdings gar nicht lustig war, waren die Ergebnisse dieser Abfrage, denn sie entsprachen nicht den Erwartungen. Aus der View erhielt ich nämlich keinen Join-Partner obwohl ich diesen als Test extra geprüft hatte mit:

 Select * from viewC where lookup='TestValue'

Folgende Punkte habe ich als “übliche Verdächtige” abgeprüft:

  • Vergleich der Datentypen – auch nicht die Ursache
  • Collations (also Zeichensätze) – auch mit Casting, das Ergebnis bleibt das Gleiche

Spannender Nebeneffekt, der bei der weiteren Untersuchung heraus kam – nutze ich die view nicht sondern packe 1:1 den Code aus der Definition mit in mein Statement

 
Select * from tableA left join 
   (select * from tableB 
     left join tableF 
         on tableB.id=tableF.foo 
      where tableF.bar='baz' and deletedDate is null)
   as testview on testview.id=tableA.id

Dann funktioniert wieder alles wie erwartet. Es staunt der Laie und der Fachmann wundert sich. Höchste Zeit das Problem genauer unter die Lupe zu nehmen und die bekannten Bugs von MySQL bzw. den Deviraten wie MariaDB und Percona zu durchforsten. Ergebnisse sind leider recht dürftig und beschreiben nicht das was ich erlebe. Immerhin stoße ich auf weitere Probleme die erst einmal völlig losgelöst von der Thematik erscheinen, es geht dabei um möglich Einstellungen des Servers und den Standard-Verhalten. Das hat sich in den letzten Jahren immer mal wieder etwas verändert und spiegelt des “Erwachsen werden” von MySQL wieder. Wir erinnern uns: Es hat eine geraume Zeit gedauert bis MySQL endlich als Standard auf INNODB als Engine gewechselt hat und somit referenzielle Integrität in Form von Transaktionen und Foreign Keys unterstützt wurden. Ebenso aus der grauen Urzeit von MySQL stammen Probleme mit NULL-Werten bei Datumsfeldern (DATE,DATETIME). Und genau da liegt der Hase am Ende auch begraben. Die Server-Konfiguration gibt vor, dass der Server sich strikt verhalten soll, unter anderem bei den Datumsfeldern.
Somit ist es nicht mehr zulässig den “uralten NULL-Ersatz 0000-00-00 00:00:00” zu verwenden. Die Routinen für den Ersatz kann man aber per Session einschalten, und genau das ist bei der manuellen Abfrage der Werte jedes mal passiert, sei es per manuellem Join oder bei der Abfrage der einzelnen Tabellen. Dabei verhält sich das isnull je nach eingestelltem Modus. Mal liefert es Zeilen mit “0000-00-00 00:00:00” als Ergebnis, mal nicht, je nachdem ob strict-mode gesetzt ist oder nicht.

Warum läuft es dann beim Verwenden der View aus dem Ruder? Das liegt an der Art und Weise wie MySQL mit Views umgeht: Sind diese übermäßig komplex werden sie als separater Thread gestartet, der eine temporäre Tabelle bereitstellt auf die dann wiederum gejoined werden kann. Dieser Thread wird allerdings vom Serverprozess angestoßen, und dort steht der Modus per default auf strict und dieser wird dann auch angewandt. Somit ist das Ergebnis nicht das was ich bei der singulären Abfrage erhalten habe, sondern leider eben ein leeres da in der Tabelle anstelle NULL eben noch “0000-00-00 00:00:00” verwendet wird.

Die temporäre Lösung ist, den Join manuell auszuführen, mittelfristig werden wir den Server umstellen. Das sind aber eigentlich nur kosmetische Lösungen. Die eigentlich korrekte Maßnahme für dieses Problem ist die Verwendung von sauberen NULL-Werten anstelle obskurer Workarounds. Leider verlassen sich sehr viele Applikationen bei uns noch auf die Tabelle und diese kruden Werte, einfach Umstellen ist also erstmal leider nicht drin. Ich kann jedem Entwickler nur wärmstens empfehlen, sich auf derartige Altlasten nicht zu verlassen und schnellstmöglich dafür zu sorgen diese abzubauen.

Für und Wider Microservices

Serviceorientierte Architektur – das Buzzword ist in alle Munde. Auch ich darf mich regelmäßig im Rahmen meiner Arbeit mit diesem Architektur-Typ auseinander setzen.

Die Idee an und für sich ist ja schon interessant: Funktionen werden über wohldefinierte Schnittstellen (z.B. REST) angeboten, wie die Antwort zu einem Request entsteht ist dann völlig unerheblich – das reicht von einer anderen Programmiersprache bis hin zu einem vollständig anderen physikalischen System. Modularisierung auf die Spitze getrieben. Eigentlich der Traum eines jeden Entwicklers sollte man meinen. Minimale Kopplung, wenn ein Service ersetzt werden soll, muss nur noch die Schnittstelle erhalten bleiben, und man kann es sogar ganz einfach umstellen.

Klingt ja erst mal alles vernünftig, aber jede Medaille (und jede Technik) hat bekanntlich zwei Seiten. Wo ist die Schattenseite der Service-Orientierung?

Ein kurzer Ausflug in die Historie hilft uns das Konzept besser zu verstehen. Schon seit geraumer Zeit haben wir uns an eine Art Gewaltenteilung gewöhnt: Daten “leben” in der Regel in einem eigenen Umfeld, das unterschiedlich ausgestaltet sein kann – sei es eine relationale Datenbank (die es wiederum in verschiedensten Geschmacksrichtungen gibt: MySQL, MariaDB, Postgreß, DB2, etc), in Dateien oder auch in den modernen NoSQL-Datenbanken. Wichtig dabei ist nur: Man sollte die Datenhaltung so wählen, dass sie zum Anwendungszweck passt. Der Blick über den Tellerrand hat auch hier noch nie geschadet: Nur weil man bisher alles in ein relationales Schema gepresst hat, muss das für eine (neue) Anwendung noch lange nicht eine gute Wahl sein.

Ohne es sich groß bewusst zu machen, haben wir hier die erste Stufe einer Service-Orientierung eingeführt – angesprochen wird der Service über eine definierte Schnittstelle. In ganz vielen Fällen ist die Schnittstelle in der Sprache der eigentlichen Applikation bereits etwas abstrahiert um sie leichter nutzen zu können (auch bekannt als “Connector”). Ein ähnliches Konstrukt mit einem anderen Focus hat bereits Dan Bernstein für qmail verwendet. Triebfeder dort war die Sicherheit – auch das bietet eine definierte, gekapselte Schnittstelle.

Ist die Sache mit den Services also nur “neuer Wein in alten Schläuchen”? Ganz so einfach ist es nicht, es hat sich doch einiges geändert – die Schnittstellen haben sich verändert – weg von festen Techniken wie etwa einem MySQL-Adapter – hin zu gerneralisierten Schnittstellen wie SOAP oder JSON als Transport-Kappsel. Das hat natürlich Vorteile – man kann die Services etwas weiter auseinander ziehen, z.B. auf spezielle Server mit angepasster Hardware oder etwa einen zentralen Authentifizierungs-Server für Single-Sign-On.

Ein weiterer oftmals genannter Vorteil ist die Skalierbarkeit: Wenn mehrere Rechner den gleichen Service anbieten, dann kann man die Last darauf gleichmäßig verteilen. Besonders sinnvoll ist es wenn man die Server-Funktion dann noch fachlich genauer eingrenzen kann. So kann man sich vorstellen, dass man mehrere Server aufsetzt und dann alphabetisch anhand der Nutzernamen aufteilt – natürlich so, dass jeder Server im Zweifel die Aufgaben eines anderen nutzen kann – aber durch die Spezialisierung wirken Cache-Mechanismen um ein Vielfaches besser.

Soweit die vielen positiven Aspekte (es gibt noch einige mehr). Aber jede Medaille hat ja zwei Seiten. Was sind Nachteile einer derartigen Struktur?

Ausfallsicherheit gibt es nicht ohne Preis – es müssen ggf. mehrere Server gestartet werden, auch wenn das heute durch Container und Virtualisierungslösungen einfacher geworden ist, man muss sich im administrativen um eine ganze Menge mehr Maschinen kümmern.

Die nächste Stelle an der man mehr Aufwand hat, ist der Aufruf und die Kodierung der Daten. Hier handelt man sich bei Netwerkaufrufen gleich einmal den vollständigen TCP/IP-Stack ein, mit allen seinen sieben Schichten. Das mag nicht übermäßig viel sein, aber es kommt definitiv etwas Latenz hinzu – wenn der entfernte Server dann noch weiter weg steht und womöglich etwas knapp angebunden ist, merkt man das sehr schnell. Besonders heftig ist es, wenn man dann noch (ohne es zu wissen oder zu wollen) eine ganze Kette solcher Services lostritt, weil der abgefragte Server wiederum andere Services anstößt…

Ein weiteres oftmals vernachlässigtes Problem ist die Datenkonsistenz und Datenaggregation: Liegen die Daten alle in einer Datenbank gehört es zum guten Ton Fremdschlüssel zu verwenden und so etwaige Inkonsistenzen zu vermeiden. So kann man beispielsweise sicherstellen, dass mit jeder Veränderung eines Datensatzes der aktuell angemeldete Benutzer mitgeschrieben wird, und dass dieser gültig ist (also zum Beispiel seine ID in der Benutzertabelle existiert). Ändert sich nun der Benutzername, ist das alles kein Drama, die Datenbank bleibt in sich konsistent. Das Löschen einen Benutzers mit noch vorhandenen Datensätzen lässt sich ebenfalls unterbinden. Will man etwas in dieser Form mit getrennten Services realisieren hat man sehr schnell eine Synchronisationshölle geschaffen. Im besten Fall kann man noch eine ID des Benutzers oder dessen Login-Namen als String speichern. Wird dieser dann auf dem Authentifikationsserver gelöscht, bekommt die eigene Anwendung davon gar nichts mit. Der Benutzer kann sich zwar nicht mehr anmelden, aber die Tatsache, dass dieser Datensatz gelöscht wurde (was ggf. für die Applikation sehr interessant sein könnte) wird nicht weiter propagiert. Besonders heftig trifft es wenn Veränderungen am Login-Namen vorgenommen werden. Das passiert häufiger als man denkt: Ein Benutzer heiratet und schwupps ändert sich in vielen Firmen der Login, da er an den Nachnamen gekoppelt ist. Für das aufrufende System ist es ein neuer Benutzer. Der Benutzer kann seine Datensätze vom Vortag nicht mehr editieren, weil er ist ja nicht der Autor. Die Szenarien in dieser Hinsicht sind vielfältigst. Will man diesen Problemen Herr werden benötigt man wieder zusätzliche Services die einen Rückschluss auf die Veränderung zulassen (also z.B. die Frage: Wurde Benutzer x irgerndwann einmal umbenannt?)

Der Weg zu einer “hippen” Microservice-Architektur will also gut überlegt sein. Es kommt sehr auf den Anwendungsfall an und ob es mir im gegebenen Kontext überhaupt möglich ist einzelne Services so heraus zu lösen, dass sie vollständig eigenständig sind. Praktischerweise ist die Architekturwelt an dieser Stelle nicht vollständig digital bzw. schwarz/weiß, es gibt beliebig viele Abstufungen die man wählen kann und die alle legitim sind. Weder ist ein monolithische Architektur per se falsch und böse, noch sind Microservices das allein seelig machende Allheilmittel. Es hilft, sich über die Rahmenbedingungen im Klaren zu sein:

  • Habe ich tatsächlich unabhängige fachliche Teilbereiche?
  • Wie groß ist die Entwickler-Gruppe – lohnt es sich mehrere Teams zu bilden?
  • Gibt es eine hohe Abhängigkeit von Daten zwischen Anwendungen / Prozessen?

Man kann noch eine Reihe weiterer Fragen stellen um ein genaueres Bild zu erhalten.

Zum Abschluss noch ein kurzer Blick zurück in die Geschichte bzw. ältere Software-Architekturen. Modularisierung von Software gab es ja bereits schon früher. Dort hat sich der Ansatz “high cohesion, loose coupeling” bewährt. Wenn man es genau betrachtet, sind Mikroservices eine logische Weiterentwicklung dieses Ansatzes. Innerhalb eines Sources-Code-Repositories kann man diese Vorgabe nur als Regel für die Entwickler vorgeben: Es ist zu vermeiden, dass man von jeder beliebigen Stelle im Code andere Funktionen aufruft. Der Ansatz der Objektorientierung hat hier bereits wichtige moderierende Funktion. Ebenso kann man durch den Einsatz von Modulen das Bewusstsein schärfen, wenn ein (fachlicher) Bereich verlassen wird. Microservices schließlich erzwingen das Konzept durch weitere Hürden, wie das Verpacken in universelle Pakete (JSON, SOAP siehe auch oben). Ob derartige Zwänge immer zu besserer Software führen ist jedoch fraglich. Zudem gibt es in jedem Projekt doch immer wieder Funktionen die gleichartig sind – mit Mikroservices nimmt man bewusst in Kauf, dass ggf. Funktionalität zweimal ausprogrammiert wird. Ob das dann gute Architektur ist, darf sich jeder selbst überlegen.