Diese Aufgabenstellung schreit extrem nach einem Service Bus. Hier hat man allerdings die Qual der Wahl. Nach einer Recherche, die mittlerweile doch Monate zurück liegt, kamen lediglich Lösungen zum Vorschein, die zwar das eigentlich gewünschte Szenario (Message senden und vergessen, Hauptsache sie wird verarbeitet) abbilden, allerdings noch viel, viel mehr. Die Systeme (beispielsweise MassTransit) sind aufwändig zu konfigurieren, ziehen zahlreiche Abhängigkeiten an und liefern weit mehr als tatsächlich benötigt werden. Ein einfacher “Message Bus” der nach dem Prinzip “Fire and Forget” funktioniert war nicht zu finden. Also mal eben selbst geschrieben – und zur Verfügung gestellt.

Hintergrund

Business-Anwendungen bestehen nicht nur aus zeitkritischen Aktionen. In vielen Fällen müssen auch Aufgaben durchgeführt werden, die durchaus außerhalb des eigenen Prozesses laufen können. Ein Beispiel hierfür sind Benachrichtigungen auf Basis von neuen/geänderten/gelöschten Objekten. Diese müssen nicht sofort gesendet werden (idealerweise jedoch zeitnah), sollten jedoch auf gar keinen Fall die eigentliche Anwendung beeinträchtigen. Würden sie jedoch – ausgeführt innerhalb desselben Prozesses – sehr wohl, da gerade das Versenden von E-Mails durchaus Zeit in Anspruch nimmt. Die Lösung besteht in der Verwendung eines Service/Message Buses.

Und genau das ist die Crux an der Geschichte: Ich persönlich möchte etwas, das sofort einsetzbar ist. Die Konfigurationspflicht muss sich in Grenzen halten und ich möchte mir für diese Aufgabenstellung möglichst wenig in meine Preconditions ziehen. FireAndForget war geistig geboren.

Ziele

Die Ziele für dieses Projekt ergeben sich großteils aus dem bereits Geschriebenem:

  • Einfache Konfiguration
  • Behandlung von Nachrichten soll ebenfalls einfach zu implementieren sein
  • Bestmögliche Performance
  • Möglichst wenig Abhängigkeiten
  • Simples Deployment über typischerweise verwendete Deployment-Prozesse

Eckdaten

Die aktuellste Umsetzung (zu diesem Zeitpunkt Version 0.2.0) ist auf GitHub zu finden, eine ausführliche Dokumentation auf der Projektseite. Die durch die Ziele beschriebenen Punkte wurde bestmöglich erfüllt.

Zur Verfügung stehe eine Core-Bibliothek, welche die gesamte Funktionalität abdeckt. Zusätzlich wurde eine Konsolen-Applikation dazugepackt. Diese zieht einen self-hosted Container hoch und ermöglicht den Zugriff via einer JSON REST API.

Erweiterungsmöglichkeiten sind an einigen (wichtigen) Stellen gegeben:

  • Datenbank. Damit sichergestellt wird, dass alle Messages auch wirklich ausgeführt werden, müssen diese (inklusive des entsprechenden Status) in einer Datenbank abgelegt werden. Hierfür existiert das Interface IRepository, welches für eigene Bedürfnisse implementiert werden kann. Im Standard wird eine Implementierung für den SQL Server ausgeliefert. Dabei werden alle notwendigen Tabellen und Indizes automatisch angelegt (lediglich die gewünschte Datenbank muss vorhanden sein und konfiguriert werden).
  • Executoren. Klingt nach einer bösen Sache, ist es aber nicht. Nachrichten für FireAndForget müssen einen bestimmten Nachrichtentyp bekannt geben. Für diesen Typ muss ein entsprechender Executor vorhanden sein. Durch das Implementieren der Schnittstelle ITaskExecutor und der Konfiguration desselben kann dies erreicht werden.

Um Nachrichtentypen aufzuteilen und asynchron ausführen zu können, besteht die Möglichkeit, sogenannte Worker zu konfigurieren. Dabei handelt es sich um – voneinander unabhängigen – Queues, in die Messages eines bestimmten Nachrichtentyps (per Konfiguration festgelegt) fließen und ausgeführt werden.

Wie funktioniert es?

Jeder konfigurierte Worker prüft alle 200 Millisekunden, ob Nachrichten zur Abarbeitung vorliegen. Ist dies der Fall, wird das Abfrageintervall aufgehoben und alle bekannten Nachrichten behandelt. Erst danach fällt der Worker wieder in seinen “Überpüfungsmodus” zurück.  Abgearbeitete Nachrichten werden mit einem entsprechenden Status in die Datenbank geschrieben. Wird die Behandlung einer Message durch einen Fehler abgebrochen, erhält diese sowohl einen eigenen Status, als auch eine entsprechende Fehlermeldung, welche ebenfalls in der Datenbank nachgelesen werden kann.

Fehlerhafte Nachrichten können durch einen Retry-API-Aufruf erneut ausgeführt werden (wenn das Problem – beispielsweise ein ausgefallener Mailserver – behoben wurde). Auch bei Neustart von FireAndForget werden alle nicht abgeschlossenen Nachrichten erneut behandelt.

Zeitverzögerte Nachrichten

In der Praxis hat sich herausgestellt, dass es gewünscht ist, Nachrichten nicht sofort auszuführen. Deshalb besteht die Möglichkeit, ein Durchführungsdatum(-uhrzeit) zu setzen. Ein entsprechender Worker stellt diese Möglichkeit bereit und schickt die entsprechenden Nachrichten zu gegebener Zeit durch die dafür vorgesehene Pipeline. Gerade für zeitverzögerte Nachrichten bietet es sich auch an, diese als “Bulk-Operation” zu behandeln. Dies kann ebenfalls bei der entsprechenden Nachricht angegeben werden. Findet sich ein Executor, der Bulk-Nachrichten für diesen Typ behandeln kann, werden alle zeitverzögerten Nachrichten hierfür zusammengefasst und zu gegebener Zeit als eine Nachricht behandelt. In der Datenbank wird jede Nachricht dennoch separat behandelt.

Fazit

FireAndForget ist in einigen meiner Projekte im Einsatz. Die Lösung wird immer dann verwendet, wenn Aufgaben nicht innerhalb des eigentlichen Prozesses durchgeführt werden müssen und keine Antwort notwendig ist. Durch die einfache Konfiguration und den wenigen Abhängigkeiten lässt sich FireAndForget sehr einfach in die eigenen Infrastruktur integrieren. Mir schweben noch zahlreiche Erweiterungsmöglichkeiten vor, Hauptziel ist aber, dass sich der Verwender möglichst schnell mit seiner eigentlichen Aufgabe beschäftigen kann und keine 100 Seiten Dokumentation lesen muss.

Veröffentlicht von Norbert Eder

Ich bin ein leidenschaftlicher Softwareentwickler. Mein Wissen und meine Gedanken teile ich nicht nur hier im Blog, sondern auch in Fachartikeln und Büchern.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Cookie-Einstellungen
Auf dieser Website werden Cookie verwendet. Diese werden für den Betrieb der Website benötigt oder helfen uns dabei, die Website zu verbessern.
Alle Cookies zulassen
Auswahl speichern
Individuelle Einstellungen
Individuelle Einstellungen
Dies ist eine Übersicht aller Cookies, die auf der Website verwendet werden. Sie haben die Möglichkeit, individuelle Cookie-Einstellungen vorzunehmen. Geben Sie einzelnen Cookies oder ganzen Gruppen Ihre Einwilligung. Essentielle Cookies lassen sich nicht deaktivieren.
Speichern
Abbrechen
Essenziell (1)
Essenzielle Cookies werden für die grundlegende Funktionalität der Website benötigt.
Cookies anzeigen