EventAggregator-Pattern (PRISM)

Dieser Artikel soll einen kurzen Überblick über das EventAggregator-Pattern geben. Das EventAggregator-Pattern wird häufig in größeren Enterprise-Applikationen eingesetzt und hat zum Ziel das Event-Handling zu zentralisieren bzw. zu vereinfachen. Dieses Pattern kann aber auch in „normalen“ bzw. kleineren Applikationen eingesetzt werden. Dies wird innerhalb dieses Artikels auch an einer Beispielapplikation (YAHW – Yet Another Hardware Monitor) gezeigt.

Wie sich aus dem Namen des Patterns bereits schließen lässt kümmert sich der EventAggregator um die Verarbeitung von Events. Klären wir hier zunächst einmal den Begriff Event. Ein Event ist vereinfacht gesagt eine Nachricht, die von einem Objekt beim Eintreten ein bestimmten Aktion gesendet (raise) wird. Alle Applikationen (von klein bis groß) nutzen Events um anzuzeigen, dass eine bestimmte Aktion oder ein spezieller Programmstatus eingetreten ist. Diese Aktion kann zum einen durch eine Benutzeraktion (z.B. ein Buttonklick) oder durch die Programmlogik (z.B. Eigenschaftsänderung) selbst ausgelöst werden. Das Objekt, welches das Event auslöst wird auch als Event-Sender (Publisher) bezeichnet. Der Event-Sender (Publisher) hat im Normalfall keinerlei Kenntnis über die Objekte oder Methoden (Subscriber), die das jeweilig ausgelöste Event empfangen. Als einfaches Beispiel kann man z.B. den Button mit dem Click-Event oder das PropertyChanged-Event einer Klasse, die die Schnittstelle INotifiyPropertyChanged implementiert, nennen. Diese Art von traditionellem Event-Handling erlaubt also eine mehr oder weniger lose Kopplung zwischen zwei oder mehr Objekten, die auf diese Art und Weise miteinander kommunizieren. Der Prozess des traditionellen Event-Handlings sieht dabei wie folgt aus:

  1. Der Publisher stellt die jeweiligen Events nach außen hin zu Verfügung und entscheidet wann diese ausgelöst werden
  2. Ein oder mehrere Subscriber registrieren sich auf die jeweiligen Publisher-Events
  3. Der Publisher löst die jeweiligen Events aus und informiert somit die Subscriber

Dieses Konzept hat allerdings ein paar Nachteile:

  • Gibt es mehrere Publisher und Subscriber wird der Quellcode ziemlich schnell unübersichtlich und schwer zu debuggen
  • Der Subscriber eines Events muss den Publisher des Events kennen und bezieht sich direkt auf diesen (was also eine enge Kopplung zwischen diesen beiden Objekten nach sich zieht)
  • Diese enge Kopplung erlaubt keinen einfachen Austausch der Publisher- und Subscriber-Objekte

Bei genau diesen Nachteilen setzt nun das EventAggregator-Pattern an. Dabei versucht das Pattern die Einschränkungen bzw. Nachteile des traditionellen Event-Handlings damit zu eliminieren, in dem eine zentrale Stelle für die Veröffentlichung und Registrierung von Events zur Verfügung gestellt wird. Diese zentrale Stelle (wer hätte es gedacht ;-)) ist der EventAggregator. Dabei übernimmt der EventAggregator die Aufgaben der Registrierung (subsribe), Deregistrierung (unsubsribe) und das Veröffentlichen/Auslösen (publish) der Events. Die einzelnen Publisher und Subscriber kennen dabei nur den EventAggregator und haben somit keine Kenntnis voneinander. Das folgende Schaubild soll diesen Zusammenhang verdeutlichen:

Event-Aggregator PRISM EventAggregator (Quelle: [1])

Das Pattern verfolgt also die folgenden Ziele:

  • Zentrales Handling von Events
  • Vereinfachung bei der Registrierung und Deregistrierung von Events
  • Lose Kopplung zwischen Publisher und Subscriber was eine einfache Austauschbarkeit dieser beiden Objekte ermöglicht
  • Einfaches Hinzufügen von Events

Kommen wir nun mal zu einem kleinen Beispiel auf Basis des YAHW – Yet Another Hardware Monitor. Der YAHW liest ja verschiedene Hardwaresensoren aus und visualisiert diese in Diagrammen, TextBoxen, Kacheln, usw. Hier steht man bei der Umsetzung nun vor dem Problem der Aktualisierung der einzelnen Sensoren. Zunächst einmal kurz zum technischen Aufbau des YAHW:

Der YAHW nutzt Unity als DependencyInjection-Container. Innerhalb des DI-Containers werden beim Applikationsstart diverse Objekte und Services registriert, die dann applikationsweit zu Verfügung stehen, u.a. wird auch ein Service registriert, der die verschiedenen Hardwaresensoren zur Verfügung stellt. Somit ist es möglich aus jedem beliebigen Dialog auf diesen Service zuzugreifen und die einzelnen Sensoren auszulesen. Der erste Ansatz war nun in jedem Dialog, der Hardwaresensoren visualisiert, einen Timer mitlaufen zu lassen und beim Tick-Event des Timers die jeweiligen Sensoren zu aktualisieren. Soweit so gut. Allerdings hat dieser Ansatz einen gravierenden Nachteil: Je nach Anzahl von Dialogen und Kacheln laufen dann x-Timer, die sich negativ auf die Performance der Gesamtapplikation auswirken. Darüber hinaus hat man auf die einzelnen Timer keinen Einfluss bzw. laufen die einzelnen Timer nicht synchron, d.h. hat man jetzt 10 Dialoge, die alle bestimmte Hardwaresensoren visualisieren, laufen mit diesem Ansatz auch 10-Timer-Instanzen. Da jetzt nicht alle Dialoge zum gleichen Zeitpunkt gestartet werden haben alle Timer unterschiedliche Startzeitpunkte. Das heißt im Umkehrschluss: Hat man jetzt 10-Timer mit einem Aktualisierungsintervall von 1 Sekunde, die alle unterschiedliche Startzeitpunkte aufweisen, wird der Service mit den Hardwaresensoren nicht 1-mal pro Sekunden angefragt sondern evtl. 5-mal oder im WorstCase 10-mal pro Sekunde. Das sich das jetzt nicht gerade positiv auf die Gesamtperformance der Applikation auswirkt dürfte jedem klar sein.

YAHW_TraditionalEvents

Daher musste ein anderer Ansatz her und dieser basiert (man ahnt es schon) auf dem EventAggregator-Pattern. Zunächst einmal das folgende Schaubild:

YAHW_EventAggregator

Wie aus dem Schaubild ersichtlich gibt es bei diesem Ansatz jetzt nur noch einen einzelnen Timer (innerhalb des Hardware-Service). Innerhalb des Tick-Events wird nun über den EventAggregator ein neues Event veröffentlicht. Auf dieses Event registrieren sich dann die einzelnen Dialoge. Bei Eintreffen des Events (z.B. jede Sekunde) wird dann die Anzeige im Dialog und den Kacheln aktualisiert. Innerhalb der Subscriber ist damit kein Timer mehr notwendig, der der „Takt“ vom Publisher vorgegeben wird und nicht mehr von den Subscribern. Dies hatte natürlich auch positive Auswirkungen auf die Gesamtperformance. Wobei diese natürlich auch von der Implementierung des EventAggregators abhängig ist. Wie dies in der Softwareentwicklung so ist braucht man das Rad nicht jedes Mal neu zu erfinden, sondern kann meistens auf bestehende Implementierung zurückgreifen (gerade bei den gängigen Design-Patterns). Innerhalb des YAHW wird der EventAggregator aus der PRISM-Library verwendet. PRISM ist ja ebenfalls OpenSource und somit kann sich jeder bei Interesse die Implementierung des EventAggregators anschauen (siehe dazu Quellen und Weblinks).

So zum Abschluss noch ein wenig Quellcode. Innerhalb des Hardware-Service läuft eine Timer-Instanz. Innerhalb des Tick-Events werden die einzelnen Sensoren aktualisiert und ein entsprechendes Event über den EventAggregator veröffentlicht (Zeile 12). Hinweis: Die Instanz des EventAggregators wird über den DI-Container ermittelt.

Die einzelnen Dialoge registrieren sich dann wie folgt auf dieses Event (Zeile 9):

Das Deregistrieren von Events erfolgt analog. Hier wird dann einfach die „Unsubscribe“-Methode des EventAggregators aufgerufen
Der eigentliche Event-Handler sieht dann wie folgt aus (hier können dann beliebige Aktionen ausgeführt werden):

Der PRISM EventAggregator bietet jetzt diverse Funktionen auf die ich hier jetzt nicht näher eingehen werde, aber es gibt u.a. gibt Möglichkeit den Callback-Handler direkt auf dem UI-Thread auszuführen (was eine Aktualisierung von UI-Elementen vereinfacht) oder die Möglichkeit Subscriptions zu filtern. An dieser Stelle verweise ich mal auf die PRISM-Dokumentation. Bei Gelegenheit werde ich mal einen eigenen Beitrag über den PRISM EventAggregator schreiben.

Literaturverzeichnis und Weblinks

Abk.Quelle
[1]PRISM EventAggregator
[2]PRISM Library
Fork me on GitHub