Das Zeitgeist Nachrichtensystem 2
Jetzt mal ernsthaft – ihr könnt euch ruhig trauen hier im Blog mit Hilfe der Kommentare zu fragen, anstatt mir umständlich eine Mail zu schreiben.
Die Frage war:
Was ist dieses angesprochene Message-System?
Nun, einfach gesagt: Die Message-Klasse stellt Funktionalitäten bereit, um systemweite Textnachrichten zu verwalten.
Das Prinzip der systemweiten Nachrichten sollte vielen bekannt sein, die schon einmal etwas tiefer in die Dektop-Programmierung eingestiegen sind. Man kann es sich als eine Art Modul vorstellen, das von überall her angesprochen werden kann. An dieses Modul können Nachrichten übergeben und umgekehrt wieder angefordert werden. Auf diese Weise können alle Module eines Projekts miteinander kommunizieren.
Ein Beispiel
Nehmen wir einmal einen typischen Fall in einer Webapplikation: ein Benutzer kauft in einem B2B-Webshop den Inhalt seines Warenkorbs. Dieser einfache Klick setzt einige Abläufe in Gang – gehen wir der Einfachheit mal von folgenden Prozessen und Fragen aus, die das System nun anstößt:
- Ist der Benutzer eingeloggt?
- Kenne ich alle nötigen Angaben des Benutzers?
- Was ist denn der Inhalt des Warenkorbs
- Sind die Waren überhaupt noch verfügbar?
- Erstelle eine Rechnung
- Informiere das Warenwirtschaftssystem
Diese können leicht durch Klassen und deren Methoden beantwortet werden. Allerdings werden dabei viele, viele Zwischenfragen gestellt und lauter Klassen müssen ineinander greifen: Benutzerverwaltung, Warenkorb, Waren, Rechnungsstellung, Schnittstelle zum Warenwirtschaftssystem und so weiter. Der ganze Ablauf wird wahrscheinlich verwaltet durch eine Art Controller (in MVC), mit dem jede Klasse für sich sprechen kann. Doch wie können diese individuellen Klassen miteinander sprechen?
Falsch!
Einfache Antwort: in sauberem OOP sollen Arbeiterklassen auch gar nicht untereinander Daten austauschen. Gut, zugegeben. Aber abgesehen von Daten: wie wäre es, wenn sie Informationen über ihren Zustand austauschen? Oder sie alle wollen möglicherweise mit dem Controller oder gar dem Benutzer reden. Und dieses Mitteilungsbedürfnis kann eben über den einfachen Rückgabewert hinaus gehen. Wenn mir eine Methode einer Arbeiterklasse ein false zurückliefert, was ist denn eigentlich genau schief gelaufen? In welchem Zustand befindet sich dann die Arbeiterklasse? (An dieser Stelle würde ich ja gerne mit Exception Handling anfangen, aber die Implementation in PHP5 ist so mangelhaft, dass das leider nicht geht).
Also – gehen wir von einer Art Verbose-Modus innerhalb der Applikation aus, in dem jede Klasse ihre Schritte dokumentieren kann und alle anderen wissen, in welchem State sich die Applikation gerade befindet.
Die Umsetzung
Gundsätzlich könnte man jetzt das Problem mit globalen Variablen lösen. Mal abgesehen von dem “globale Variablen sind die Wurzel allen Übels”-Stigma sind sie wirklich nicht besonders sinnvoll. Es ist schwer im Blick zu halten, welche Funktion gerade was in welche Globale geschrieben hat, was Debugging und vor allem Testing zu einem Horrortrip macht.
Alternativ würden sich Session-Variablen anbieten, aber wenn man es genau betrachtet sind diese in dem Zusammenhang auch nichts anderes als Globale in Grün.
Was ist daran schlecht?
Das da:
Warum machst du es dann doch, obwohl sogar Google sagt, dass es schlecht ist?
Wer aufgepasst hat, der hat sich gemerkt, dass Singletons (über die wir hier reden) nicht per se schlecht sind. Man muss sie nur entsprechend designen. Denn grundsätzlich haben Singletons einige entscheidende Vorteile gegenüber einer Anzahl von “dummen” Globalen.
Zunächst einmal wäre da die Nachvollziehbarkeit. Die Klasse selbst kann nachvollziehen welche Methode oder Funktion gerade auf sie zugegreift und was diese verändert. Voraussetzung ist, diese Informationen fließen alle in eine Art Tracing-Mechanismus, was dem Entwickler zu jeder Zeit des Programms transparent macht, was an welcher Stelle passiert ist. In der Kombination mit dem Tracer bleibt dann nachvollziehbar, was genau mit dem Global State passiert (siehe Video).
Ich erwähnte bereits, dass das Nachrichtensystem die Informationen nicht nur speichert, sondern auch verwaltet. Eine Methode kann eine Nachricht mit einem expliziten Empfänger versehen. Somit erhält eine Methode an anderer Stelle bei einer Anfrage nach für sie relevanten Nachrichten auch nur die für sie gespeicherten Informationen.
Möglich ist auch eine effektive Rechteverwaltung, welche Module auf welche gespeicherten Informationen zugreifen bzw. diese verändern können.
Und schließlich: Nachrichten können über Aufrufe hinweg gespeichert werden. Ein Beispiel: wenn bei einem Aufruf einige Funktionen einen Fehler melden, der Benutzer daraufhin per Redirect auf eine andere Seite geleitet wird, kann das System immer noch nachvollziehen, welche Fehler eigentlich gemeldet wurden und dem Benutzer entsprechende Ausgaben präsentieren. Das ist eigentlich die Hölle für einen Global State, aber noch einmal: wir können leicht nachvollziehen wer den State wann gesetzt hat. Und umgekehrt können wir diesen auch leicht definieren, um ihn so testen zu können.
Fazit
Ja, das ist Overhead. Aber nicht so viel, wie man denkt. Macht es das Debuggen und Warten des Codes problematischer? Ja, ein bischen. Oder vielmehr: man sollte sich über ein paar Dinge im Klaren sein. Dann aber ist es immer nett, wenn man mehr Informationen zur Verfügung hat, was genau in der Applikation vorgeht – auch innerhalb der Applikation selbst.
Aber um eine Sache klarzustellen: wir reden hier von Nachrichten, Kommunikation und Übersicht. Die eigentilchen Arbeiterklassen sollten entsprechend korrektem OOP-Design erstellt werden (siehe oben, siehe Vortrag). Was nicht Sinn der Sache ist, dass (wie in unserem B2B-Shop-Beispiel) der Warenkorb plötzlich über das Nachrichtensystem von einer Arbeiterklasse zur nächsten gereicht wird. Dann sind wir in der Tat bei “Bad Global State”.
Das hört sich sehr kompliziert an und schwer implementierbar. Kannst Du mal einen konkreten Fall skizzieren?
Ein Beispiel ist der oben angesprochene Warenkorb. Nehmen wir als weiteres Beispiel mal eine fiktive Benutzerverwaltung:
Ausgangslage ist eine (einzige) Seite, auf der ein Benutzer a) seinen Nutzernamen b) seine Nutzerdaten (inlusive Email) und c) sein Passwort verändern kann.
Nehmen wir an, ein Benutzer macht alles 3:
1) er ändert seinen Benutzernamen in “MaxMustermann”, der jedoch bereits existiert. Die Nutzerklasse des Frameworks fängt das Problem ab und sendet eine Nachricht “Nutzername existiert schon” mit dem Attribut “Warnung” an das Nachrichtensystem.
2) Er ändert seine Nutzerdaten und gibt eine neue Email an. Diese ist in Ordnung und die Datenklasse des Projekts sendet die Nachricht “Die Mail wurde geändert” mit dem Attribut “Nachricht”.
3) Er gibt sein neues Passwort an, jedoch stimmt dieses nicht mit der Kontrolleingabe überein. Die Formularverwaltung fängt dies ab und sendet die Nachricht: “Die Passworteingabe stimmt nicht überein” mit dem Attribut “Warnung”.
Am Ende des Requests holt sich die Ausgabe alle Nachrichten ab (die sie betreffen) und stellt diese im Entsprechenden Stil da: Warnungen in Orange, Meldungen in grün.
Ergebnis: die Daten wurden alle von den unterschiedlichsten Klassen behandelt. Durch das Nachrichtensystem konnten die einzelnen Klassen jeweils ihre Zustände bzw. Meldungen weitergeben und aggregiert an die relevante Ausgabeklasse weitergegeben werden.