Facebook
Twitter
Google+
Kommentare
8

Wie baue ich mir ein Nachrichtensystem in PHP?

Mal Hand aufs Herz, wieviele Gedanken machst du dir über die Rückmeldungen deines Systems an die Systembenutzer? Ok, im Prinzip ist das eine ganz einfach Sache: Etwas passiert und dann wird das gemeldet. Nahezu trivial, oder?

Kommt dir dieser Dialog bekannt vor:

<DAU?>: Hallo, ich hab ein Problem mit deinem Programm.
<DU>:Ähm, ja? Worum geht es denn?
<DAU?>: Gehen trifft’s ganz gut, es geht nicht…

„Es geht nicht“ ist der Running-Gag der Fehlerbeschreibungen schlechthin. Wir alle lieben das, oder?

Doch der Dialog geht noch weiter:

<DU>: Ich bräuchte schon ein paar mehr Informationen, wenn ich dir helfen soll. Was hast du gemacht und welche Meldung ist gekommen?
<DAU?>: Ich wollte einen Benutzer anlegen, als Meldung kam: „Sorry, hat nicht geklappt“.

So, wer ist nun Schuld an der ungenauen Fehlerbeschreibung? Der Anwender ist leider gar nicht in der Lage wesentlich besseres Feedback zu geben. Er gibt uns ja nur zurück, was wir selbst verbrochen haben.

Mit solchen Meldungen schießt man sich selbst in den Fuß.Informationsverarbeitende Systeme erzeugen je nach Komplexität der Anwendung eine beachtliche Menge von Nachrichten. Diese können teils dem Anwender als direkte Rückmeldung präsentiert werden, oder z.B. an eine Instanz, die das System überwacht.

Es gibt zwei wichtige Anforderungen an Nachrichten:
Eine Nachricht muss korrekt und aussagekräftig sein. Aus ihr muss hervorgehen, was von dem Empfänger erwartet wird. Eine Nachricht muss identifizierbar sein. Ein Entwickler muss anhand der Nachricht rekonstruieren können, was schief gelaufen ist und wo das passiert sein könnte.
Idealerweise gibt es einen Fehler-Code, mit dem man den Fehler identifizieren kann, selbst wenn die Meldungen internationalisiert sind. ( In der Praxis werden hierbei Codenummern verwendet )

Grob können die Nachrichten in 3 Typen eingeteilt werden:

Systemmeldungen:
Das erfolgreiche Hinzufügen eines Datensatzes in einen Datenspeicher sollte z.B. mit einer kleinen, den Arbeitsfluss nicht blockierenden Nachricht, zurückgemeldet werden. So genannte Toast-Messages bieten sich hier an.

Behebbare Fehler:
Tritt ein Fehler auf, den der Benutzer selbst verursacht und damit auch selbst beheben kann, dann wird eine Meldung erwartet, welche den Benutzer darauf hinweist, was genau er falsch gemacht hat. Diese Meldung sollte vom Benutzer eine Reaktion erzwingen, so dass der Benutzer das Problem nicht ignorieren oder übersehen kann. In der Praxis wird das mit einen Bestätigungsfenster erreicht, welches erst mit einem Klick wieder verschwindet. Wichtig ist, dass die Meldung die passenden Informationen enthält, wie der Fehler zu beheben ist.

Kritische Fehler:

Kritische Fehler blockieren den Arbeitsfluss. Der Benutzer muss diese zur Kenntnis nehmen, hat aber keine Möglichkeit das Problem selbst zu beheben. Eine zweite Kategorie nach der die Nachrichten unterteilt werden müssen, sind die Empfänger der Nachrichten.

Ein kleines Beispiel:
Der Anwender eines Systems erhält beim Anlegen eines Datensatzes ein simples: „Ja, es hat geklappt“ als Feedback.
Im Gegensatz dazu will der „Bearbeiter“, welcher für die Datensätze zuständig ist, wesentlich detailliertes Feedback, z.B. Der User: XY hat um am 1.1.2009 das Projekt 33, Titel: „Das war ein Test“ gelöscht.

Weiter unterscheidet hier die Nachrichten, dass die Nachricht an den Anwender synchron und nicht persistent ist.
Dass etwas geklappt hat wird einfach nur als grafische Rückmeldung angezeigt. Dies geschieht nur einmalig. Nach dem Ausblenden der Meldung ist die Information nicht mehr in dem System.

Die Meldung an den „Bearbeiter“ hingegen ist asynchron und persistent. Seine Meldungen kann z.B. in einem Verlauf angezeigt werden, um ihm den Überblick über Änderungen am Datenbestand zu ermöglichen.

Ein Entwickler wiederum möchte an der selben Stelle wahrscheinlich lieber im Fehlerfall eine aussagekräftige Nachricht inklusive Debug-Informationen. Aus den aufgeführten Anforderungen und Beispielen lassen sich folgende Anforderungen an ein Nachrichtensystem ableiten:

  • Die Handhabung verschiedener Nachrichtentypen / Level
  • Synchrone und asynchrone Auslieferung der Daten
  • Persistente und nicht persistente Nachrichten
  • Unterscheidung der Nachrichten nach Ziel-Adressen
  • Der Nachrichtenfluss muss kontrollierbar sein
  • Die Verarbeitung der Nachrichten muss unabhängig vom Ausgabemedium geschehen können

Eine weitere Anforderung an das System ergibt sich aus der geforderten Genauigkeit und der Korrektheit der Nachrichten. In Kombination mit dem DRY Prinzips ( Don’t Repeat Yourself ) kann man daraus ableiten, dass es möglich sein muss die Nachrichten genau an der Stelle an das System zu übergeben, an dem die kompletten Informationen garantiert zutreffend vorhanden sind.

Geschieht die Übergabe der Nachricht zu tief im System, fehlen wahrscheinlich nötige Informationen für eine genaue Fehlermeldung und die Nachricht wird zu generisch. Wenn die Nachricht hingegen „zu weit oben“ in der Call-Hierarchie übergeben wird, besteht die Gefahr unnötige Redundanzen und inkonsistente Fehlermeldungen zu bekommen.

Somit gibt es (fast) immer einen richtigen Platz für die Übergaben einer Meldung an das System und logischerweise muss es an dieser Stelle auch von der Anwendungsarchitektur her möglich sein, die Meldung zu übergeben. Einfacher ausgedrückt, es muss bis vor der Ausgabe des Seiteninhalts möglich sein, Meldungen in das System zu schreiben, die dann ausgegeben werden können.

Die einfachste Implementierung für ein Nachrichtensystem ist die passive Variante. Bei dieser Variante handelt es sich um einen passiven Nachrichtenpool. In das System wird nur geschrieben, die einzige Aufgabe das Nachrichtensystems ist die Nachrichten gruppiert vorzuhalten, und bei Anfrage zurückzugeben.

Die Verarbeitung der Nachrichten übernehmen andere Systeme wie z.B die View bei der Ausgabe.

Hier ein vereinfachtes Klassendiagramm:

UML Diagramm für ein passives Messagesystem

UML Diagramm für ein passives Nachrichtensystem

Zum Gruppieren der Nachrichten werden benamte Queues verwendet. Sinnvoll ist es einen Standard-Queue anzulegen. Wenn nicht explizit eine, oder mehrere Queues, angegeben werden, landen die Nachrichten in dieser Queue. Weiter sollte die Implementierung des Pools es ermöglichen, gleichzeitig in mehreren Queues zu schreiben und in chronologisch sortierter Reihenfolge aus mehrere Queues zu lesen.

Hier ein Anwendungsbeispiel anhand eines Datenbank Eintrags:


// Beim Validieren werden im Fehlerfall direkt die Meldungen in das
// Nachrichtensystem geschrieben
// Die ist korrekt da beim Validieren direkt die passenden Fehlermeldungen
// generiert werden können
// Alle Fehler, auf die der Benutzer reagieren kann, werden beim Validieren geworfen
$entityUser = $formUser->validateInsert( $httpRequest );

// wenn bis jetzt (egal wo) Fehler aufgetreten sind, den Insert abbrechen
if( Message::hasError() )
return false;

// Entity in die Datenbank schreiben
// Ab hier können nur noch Dinge schief gehen,
// die der User nicht mehr selbst beheben kann
try
{

  // wirft Exception LibDbException im Fehlerfall
  $db->insert( $entityUser );

  // Nachricht an den Benutzer / schreiben in stdout
  Message::addMessage
  (
    // Die Meldungen sind zum besseren Verständnis hardgecoded
    'Der User: '.$entityUser->name.' wurde erfolgreich erstellt'
  );

  // Nachricht die in die "log" und "user_storage_log" queue kommt
Message::addMessage
  (
    'Der Benutzer '.$activUser->ident().' hat erfolgreich den User'
      .$entityUser->name.' angelegt' ,
array('log','user_storage_log')
  );

}
catch( LibDbException $error )
{

  // Nachricht an den Entwickler
  // Würde im Produktivsystem in der Exception mitgeschickt werden
  // Hier als Demo für die Queues
Message::addError
  (
    'Fehler im Datenbanksystem beim Erstellen eines Users: '
      .$e->getMessage().' mehr Informationen: '.$error->getLink() ,
    'developer'
  );
Message::addError
  (
    'Systemfehler: Der Benutzer '.$entityUser->name.' konnte nicht anlegen werden'
  );

  return false;
}

return true;

Da ein passives Nachrichtensystem behandelt wird, müssen die Komponenten, welche die Nachrichten weiterverarbeiten, diese aus dem Nachrichtenpool laden.

So könnte z.B. die Übergabe der Nachrichten an ein Template-System aussehen:


// Irgendwo kurz vor der Ausgabe der Seite
// übertragen der Meldungen in das Templatesystem zur Ausgabe
$template->setErrors( Message::getErrors() );
$template->setMessages( Message::getMessages() );

// Hier könnte der Code für dein Templatesystem stehen
$template->build();
$tenplate->publish();

Eine sehr einfache Möglichkeit das Nachrichtensystem in ein Aktives System umzubauen, gibt PHP ab Version 5.3, mit den neuen Closures an die Hand. Closures bieten sich hier wunderbar als dynamische Filterfunktionen an. Closures können als Observer in die Add-Methoden auf Queues gehängt werden und die Nachrichten direkt verarbeiten.

Das Konzept der Message Klasse, sowie die verwendeten Codebeispiele stammen aus dem WebFrap Framework.

Über den Autor

Dominik Bonsch

Kommentare

8 Comments

  1. Ein wirklich sehr guter Artikel. Bei uns läuft das hauptsächlich über den FlashMessenger des ZendFramework ab. Allerdings haben wir diesen derart erweitert, dass er im aktuellen Zyklus sowohl Nachrichten aufnehmen, als auch gleich wiedergeben kann. Außerdem werden alle Exceptions (wir arbeiten intern fast ausschließlich über Exceptions im Fehlerfall) von einem globalen ErrorController aus dem MVC-Pattern des ZendFrameworks gefangen, bzw. in eine Log übertragen und per Mail an die Entwicklung versandt. Das hat sich als sehr bewährtes Mittel erwiesen.

    Reply
  2. Ich finde es auch immer sehr schwierig für den Benutzer verständliche Meldungen zu erstellen. Wenn man die Meldungen als Entwickler erstellt sind sie einem einleuchtend und verständlich. Der Benutzer kann damit aber manchmal nichts anfangen …
    Sehr interessantes Thema – Toller Artikel

    Gruß
    Heiko

    Reply
  3. Danke für den Artikel, das Thema gut zusammengefasst incl. Codebeispielen.

    Hatte zwar aufgrund der Überschrift etwas anderes erwartet (Nachrichtensystem hört sich eher nach User-to-User-Nachrichten an), aber trotzdem gut geschrieben, und die Unterscheidung zwischen „sichtbaren Userbenachrichtigungen“ (die nicht-persistenten) und den persistenten Log-Nachrichten (in DB schreiben oder via Email senden) ist gut gelungen.

    Dabei ist mir auch aufgefallen, dass wir eigentlich viel zu selten Fehlercodes nutzen, um einen Fehler noch genauer zu spezifizieren und ihn später einfacher wiederfinden zu können.

    Ich muss auch Robert zustimmen, wir nutzen auch für diese Aufgaben den FlashMessenger (selbst etwas erweitert) bzw. Zend_Log für persistente Nachrichten.

    Reply
  4. Dankeschön! Schöner Artikel dazu, wobei es für mich eigentlich selbstverständlich ist nach diesem System vorzugehen. Liegt wohl daran, dass ich mit einem CMS arbeiten durfte, welches derart grottenschlecht programmiert war, dass ich mir vornahm nicht dieselben Fehler zu begehen…

    Weiter so!

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen