Engineering
Intro zu Sagas - Koordinatoren eines langlaufenden Prozesses
Szenario
Verwenden wir ein Beispiel zur Veranschaulichung. Ein Logistikunternehmen möchte Touristen anbieten, ihr Gepäck von daheim abzuholen und an den Zielort ihres Vertrauens zu bringen. Daran sind folgendes System beteiligt:
- Ein Webshop, der die Bestellung der Kund*innen entgegennimmt.
- Das Auftragsmanagementsystem kümmert sich um die interne Abwicklung des Transportes
- Das ERP ist für die Verrechnung verantwortlich
Welche Funktionen muss das System koordinieren?
Die Bestellung wird vom Webshop an das Auftragsmanagement übermittelt. Sie beinhaltet außerdem einen Token für die reservierte Bezahlung auf der Kreditkarte der Kund*innen, die an das ERP weitergegeben werden muss.
Während der Abwicklung des Auftrages sammelt das Auftragsmanagement den Status des Transportes und übermittelt ihn an den Webshop, damit Kund*innen immer wissen, wo sich ihr Gepäck gerade befindet.
Nachdem das Gepäck am Zielort angekommen ist, übermittelt das Auftragsmanagement den abgeschlossenen Status an den Webshop und informiert das ERP, dass es die Rechnung ausstellen und die Zahlung von der Kreditkarte abrechnen soll. Die Rechung wird in den Webshop hochgeladen, damit der Kunde dort darauf zugreifen kann.
Zur Übersicht würde der Prozess damit vereinfacht so wie in diesem Diagram aussehen:
Welche Objekte sind beteiligt?
- Im Webshop werden Bestellungen angelegt
- Für jede Bestellung entsteht im Auftragsmanagement ein Auftrag
- Nach Abschluss entsteht eine Rechnung und dann eine Zahlung im ERP
Welche Nachrichten werden ausgetauscht?
Die Systeme tauschen untereinander Nachrichten aus, um die entsprechenden Funktionen zu implementieren.
Oft wird das Saga Pattern mit Message Queues in Verbindung gebracht.
Der Austausch der Nachrichten ist aber unabhängig von asynchronen/synchronen Medium zum Transport der Nachrichten.
Das Medium kann somit auch eine REST oder andere HTTP oder gRPC basierte Schnittstelle sein.
Schauen wir uns also kurz an, wie die Nachrichten in diesem Beispiel aussehen könnten. Wir beleuchten nur die Nachrichten, die zwischen System ausgetauscht werden.
(2) Auftrag erstellen
|
|
(5) Status updates
|
|
(7) Auftrag abgeschlossen
|
|
(8) Auftrag verrechnen
Hier gehen wir davon aus, dass das ERP eine HTTP-Schnittstelle hat. Der Request gibt die Details der Bestellung an.
Das ERP antwortet mit der ID der Rechnung.
|
|
|
|
Unser System muss damit die Rechnung abrufen und dem Webshop weitergeben.
Saga Grundlagen
Wir sehen, dass es sich dabei um einen durchgängigen Flow mit einem klar definiertem Start (der Bestellung des Auftrags) und einem klaren Ende (dem Upload der Rechnung im Webshop) handelt.
Die Koordination mehrerer Systeme sollte nicht in allen Fällen zentral implementiert werden, aber in diesem vereinfachten Beispiel kann das Auftragsmanagementsystem die Koordination übernehmen.
Nachdem es sich um einen langlaufenden Prozess handelt, wird eine Datenbanktabelle für die Speicherung des Zustandes der Saga verwendet.
Trifft eine neue Nachricht ein, wird überprüft, ob die Nachricht zu einer bestehen Saga zugeordnet werden kann (Korrelation), und dann die Daten der Saga aus der Tabelle geladen.
Mit den Sagadaten und der eingegangenen Nachricht wird eine lokale Transaktion ausgeführt. Dabei werden die Daten der lokalen Applikation geändert, neue Nachrichten versendet und der Zustand der Saga aktualisiert.
Durch die aufeinander folgende Verarbeitung aller Nachrichten und die Abarbeitung der verschiedenen lokalen Transaktionen wird der Workflow in den unterschiedlichen Systemen abgearbeitet.
Wie funktioniert der Ablauf einer solchen Saga?
Die Ablauf kann als Pseudocode dargestellt in etwas so aussehen:
Nachricht: (2) Auftrag erstellen (BestellungID)
- Prüfe, ob Saga für BestellungID vorhanden
- Erstelle Saga für BestellungID
- Erstelle Auftrag
- Ergänze AuftragID zu Saga
Nachricht: (5) Statusupdate Auftrag (AuftragID):
- Lade Saga für AuftragID
- Sende neuen Status an Webshop mit BestellungID von Saga
Nachricht: (7.1) Auftrag abgeschlossen (AuftragID)
- Lade Saga für AuftragID
- Sende neuen Status an Webshop mit BestellungID von Saga
- Sende notwendige Daten an ERP zur Verrechnung
- Speichere RechnungID von Response in Saga
- Sende Nachricht zum Abruf der Rechnung an dich selbst (AuftragID)
Nachricht: (7.2) Rechnung von ERP abrufen (AuftragID)
- Lade Saga für AuftragID
- Lade Rechnung von ERP, wenn bereits verfügbar
- Wenn Rechnung noch nicht verfügbar, verzögere Nachricht
- Sende Rechnung an Webshop, sobald heruntergeladen
- Schließe Saga ab
Wie bei Schritten 7.1 und 7.2 zu sehen ist, kann die Saga auch dazu verwendet werden, die Abarbeitung eines asynchrones Prozessschrittes, der den Abruf mehrerer APIs benötigt, sicher in mehrere Schritte aufzuteilen.
Was passiert in Fehlerfällen?
Erweitern wir unser Szenario um einen Ausnahmefall:
Es kann sein, dass die Logistik bei der Prüfung des Auftrags feststellt, dass das bestellte Produkt nicht an den gewünschten Bestellort zugestellt werden kann.
In diesem Fall muss der Auftrag storniert werden. Das Logistiksystem übermittelt statt Nachricht (4) einen Fehler.
Der Prozess muss jetzt anders ablaufen. Der Status im Webshop wird auf storniert gesetzt und der Besteller erhält eine E-Mail, in der er über das Storno und den Grund informiert wird.
Im Gegensatz zu einem System mit einem transaktionalen Store, in dem die Transaktion zurückgerollt wird, muss jedes bereits involvierte System informiert und in den korrekten Zustand gebracht werden. Diese Aktionen werden als “Compensating Actions” bezeichnet.
Wann sollte man Sagas verwenden?
Verwende Sagas, wenn:
- Mehrere Systeme koordiniert werden müssen
- oder ein Prozess über einen größeren Zeitraum koordiniert werden muss
- als Alternative zu einer Distributed Transaction
Verwende Sagas nicht, wenn:
- Mehrere Objekte in einem System koordiniert werden müssen (verwende Transaktionen)
- Ein Prozess im selben System länger läuft (zB weil Berechnungen aufwendig sind oder viele Objekte gespeichert werden müssen - verwende asychrone Hintegrundprozesse)
Wie können Sagas in Dotnet implementiert werden?
In Dotnet haben sich über Jahre sehr gute Libraries für Messaging oder leichtgewichtige Servicebus-Systeme entwickelt. Viele davon haben Implementierungen für Sagas:
- Rebus - Einfacher und flexibler Servicebus mit Saga Implementierung und guten Samples.
- MassTransit - Flexible, weit verbreitete und manchmal komplizierte Implementierung eines Servicebus. Enthält zwei unterschiedliche Saga Implementierungen, eine Message-basierte Implementierung und eine State-Machine-basierte Implementierung.
- NServiceBus - Kommerzielle Implementierung eines Servicebus mit Saga.
- Wolverine - Convention-basierte Servicebus Implementierung mit Sagas.