Eine Bestellung berührt drei Services: Das Lager reserviert Bestand, der Zahlungsservice autorisiert die Karte, der Versand legt eine Sendung an. In einem Monolithen ist das eine einzige Datenbanktransaktion. Aufgeteilt in Services mit eigenen Datenbanken gibt es keine Transaktion, die alle drei umfasst, und Two-Phase-Commit über Services hinweg ist in der Praxis selten eine Option.
Das Saga-Pattern ist die Standardantwort darauf. Dieser Artikel erklärt, wie eine Saga funktioniert, worin sich Orchestrierung und Choreografie unterscheiden, wie Kompensationen und Pivot-Schritte zusammenpassen und welche Isolationsprobleme Sagas nicht für dich lösen. Das Diagramm zeigt eine orchestrierte Bestell-Saga.
Was eine Saga ist
Eine Saga ist eine Folge lokaler Transaktionen. Jeder Schritt committet in der Datenbank seines eigenen Service und stößt den nächsten an. Scheitert ein Schritt, führt die Saga kompensierende Transaktionen aus, die die Effekte der bereits committeten Schritte rückgängig machen.
Die Idee ist älter als Microservices: Hector Garcia-Molina und Kenneth Salem beschrieben Sagas 1987 in einem Paper über langlebige Datenbanktransaktionen. Das Pattern kam zurück, weil Service-Grenzen dasselbe Problem neu geschaffen haben.
Zwei Konsequenzen prägen alles Weitere:
- Eventual Consistency. Zwischen dem ersten und dem letzten Schritt befindet sich das System in einem Zwischenzustand, den andere Anfragen sehen können. Die Bestellung ist PENDING, der Bestand reserviert, die Karte autorisiert, aber noch nicht belastet.
- Kompensation ist kein Rollback. Eine verschickte E-Mail lässt sich nicht zurückholen und eine belastete Karte nicht stillschweigend entlasten. Eine Kompensation ist eine neue fachliche Aktion, etwa das Stornieren einer Autorisierung oder eine Erstattung, die die frühere Aktion semantisch umkehrt.
Kompensierbare, Pivot- und wiederholbare Schritte
Klassifiziere jeden Schritt, bevor du irgendetwas zeichnest. Der Leitfaden zum Saga-Pattern im Azure Architecture Center unterscheidet drei Arten:
| Art | Bedeutung | Beispiel |
|---|---|---|
| Kompensierbar | Lässt sich durch eine andere Transaktion rückgängig machen | Bestand reservieren; Zahlung autorisieren |
| Pivot | Der Punkt ohne Rückkehr: Ist er erfolgreich, muss die Saga zu Ende laufen | Sendung anlegen |
| Wiederholbar | Folgt nach dem Pivot und muss irgendwann gelingen; idempotent | Bestätigungs-E-Mail senden |
Die Reihenfolge zählt. Setze kompensierbare Schritte an den Anfang, danach den Pivot und dahinter nur wiederholbare Schritte. Scheitert der Pivot, wird kompensiert; ist er erfolgreich, wird alles danach so lange wiederholt, bis es gelingt. Eine Saga mit einem kompensierbaren Schritt nach dem Pivot kann keinen konsistenten Zustand erreichen, wenn dieser Schritt scheitert.
Orchestrierung vs. Choreografie
Es gibt zwei Wege, die Schritte zu koordinieren.
Orchestrierung: Ein Saga-Orchestrator, oft eine Zustandsmaschine mit dauerhaft gespeichertem Zustand, sagt jedem Service, was zu tun ist, und wartet auf die Antwort. Ihm gehören die Abfolge, die Wiederholungen, die Timeouts und die Kompensationen.
Choreografie: Es gibt keinen Koordinator. Jeder Service hört auf Events und reagiert: Das Lager hört OrderCreated und reserviert Bestand, der Zahlungsservice hört StockReserved und autorisiert, und so weiter. Fehler sind ebenfalls Events, und jeder Service weiß, welche Kompensation er ausführen muss.
| Orchestrierung | Choreografie | |
|---|---|---|
| Wo der Ablauf lebt | An einer Stelle: im Orchestrator | Verteilt auf die Event-Handler der Services |
| Einen Schritt hinzufügen | Den Orchestrator ändern | Ändern, wer auf welches Event hört |
| Den Zustand einer Bestellung sehen | Das Saga-Log abfragen | Aus den Events rekonstruieren |
| Kopplung | Services kennen die Commands des Orchestrators | Services kennen die Events der anderen |
| Passt gut zu | Vielen Schritten, Verzweigungen, strikter Reihenfolge | Zwei oder drei Schritten, einfachen Abläufen |
Choreografie ist angenehm bei kurzen Abläufen und mühsam, sobald jemand fragt: „Warum hängt Bestellung 8812?“ Orchestrierung gibt dieser Frage genau eine Stelle, an der man nachsieht. Workflow-Engines wie Temporal, AWS Step Functions oder Camunda bieten dauerhafte Orchestrierung, sodass du die Zustandsmaschine nicht selbst bauen musst. Chris Richardsons Seite zum Saga-Pattern enthält ausgearbeitete Beispiele für beide Stile.
Isolation: der Teil, den Sagas dir überlassen
Eine Datenbanktransaktion verbirgt Zwischenzustände vor anderen Transaktionen. Eine Saga tut das nicht. Während eine Bestell-Saga läuft, kann eine andere Anfrage den reservierten Bestand lesen, oder eine zweite Saga kann denselben Datensatz ändern. Die üblichen Gegenmaßnahmen:
- Semantische Sperre. Markiere Datensätze als in Bearbeitung (eine Bestellung in PENDING, Bestand in RESERVED), damit andere Operationen wissen, dass sie warten oder ablehnen sollen.
- Kommutative Updates. Gestalte Operationen so, dass sie in jeder Reihenfolge dasselbe Ergebnis liefern, etwa indem du Zähler erhöhst und verringerst, statt sie zu setzen.
- Vor dem Schreiben erneut lesen. Prüfe, ob sich ein Wert geändert hat, seit die Saga ihn gelesen hat, und brich ab oder starte neu, falls ja.
Mach jeden Schritt und jede Kompensation idempotent, mit der Saga-ID und dem Schritt als Schlüssel. Nachrichten werden doppelt zugestellt, und ein wiederholtes „Autorisierung stornieren“ darf keine andere Zahlung stornieren.
Timeouts und hängende Sagas
Jeder Schritt braucht eine Frist. Ein Zahlungsservice, der nie antwortet, lässt die Saga ewig warten und den Bestand reserviert. Gib jedem Schritt ein Timeout, behandle ein Timeout vor dem Pivot als Fehler (kompensieren) und nach dem Pivot als Grund für einen neuen Versuch, und alarmiere bei Sagas, die länger als erwartet in einem Zustand bleiben. Ein kleines Dashboard der Sagas nach aktuellem Schritt beantwortet die meisten Support-Fragen von allein. Halte außerdem fest, warum jede Saga endete.
Das Architekturdiagramm
Diagramminhalt
- Order service
- Saga orchestrator
- Saga log
- Inventory service
- Payment service
- Shipping service
- All steps succeeded?
- Order APPROVED
- Compensate in reverse order
- Order REJECTED
- start
- persist each step
- 1. reserve stock
- 2. authorize payment
- 3. create shipment
- yes
- no
- void, release
So liest du es:
- Links: Der Bestellservice legt die Bestellung in PENDING an und startet die Saga.
- Mitte: Der Orchestrator führt die Schritte der Reihe nach aus und speichert jeden im Saga-Log, sodass er nach einem Neustart dort weitermacht, wo er aufgehört hat.
- Rechts: die drei Services, jeweils mit Schrittnummer und Art beschriftet. Lager und Zahlung sind kompensierbar, der Versand ist der Pivot.
- Unten: Gelingen alle Schritte, wird die Bestellung APPROVED. Scheitert ein Schritt vor dem Pivot, kompensiert der Orchestrator in umgekehrter Reihenfolge (erst die Autorisierung stornieren, dann den Bestand freigeben), und die Bestellung wird REJECTED.
- Die Markdown-Tabelle ordnet jeder Aktion ihre Kompensation zu. Genau das sollte ein Reviewer zuerst prüfen.
Was in ein Saga-Diagramm gehört
- Die Reihenfolge der Schritte und die Art jedes Schritts. Kompensierbar, Pivot oder wiederholbar gehört auf den Kasten.
- Jede Kompensation. Ein Schritt ohne Kompensation ist entweder der Pivot, wiederholbar oder ein Bug.
- Wo der Saga-Zustand liegt. Eine Saga, die einen Absturz auf halber Strecke nicht übersteht, ist nicht fertig.
- Die sichtbaren Zwischenzustände. PENDING und RESERVED gehören zum Design; andere Features werden sie sehen.
Sagas passen natürlich zum Transactional-Outbox-Pattern, weil jeder Schritt seine Datenbank aktualisieren und eine Nachricht senden muss, ohne eins von beiden zu verlieren. Das Microservices-Architekturdiagramm zeigt, wo diese Services sitzen, und das Diagramm zur Payment-Gateway-Integration behandelt Autorisierung und Capture im Detail.
In Archboard nachbauen
- Melde dich mit Google oder GitHub an und leg ein Diagramm an.
- Füge aus dem C4-Modell-Pack Container-Nodes für den Bestellservice, den Saga-Orchestrator und die drei beteiligten Services hinzu, außerdem eine Datenbank für das Saga-Log.
- Trag in das Technologie-Feld jedes Service die Schrittnummer und die Art (kompensierbar oder Pivot) ein.
- Füge aus dem Flussdiagramm-Pack eine Entscheidung, einen Prozess und zwei Start / Ende-Nodes für den Erfolgs- und den Kompensationspfad hinzu.
- Füge einen Markdown-Block aus dem Basis-Pack mit einer Tabelle der Aktionen und Kompensationen hinzu.
Das Beispiel nutzt 11 Diagramm-Nodes und bleibt damit im Free-Plan.
FAQ
Was ist der Unterschied zwischen einer Saga und einer verteilten Transaktion? Eine verteilte Transaktion (Two-Phase-Commit) sperrt Ressourcen über Datenbanken hinweg, bis alle committet haben. Eine Saga committet jeden Schritt lokal und macht frühere Schritte mit Kompensationen rückgängig, wenn ein späterer scheitert. Sie tauscht Isolation gegen Verfügbarkeit.
Soll ich Orchestrierung oder Choreografie verwenden? Orchestrierung für Abläufe mit vielen Schritten, Verzweigungen oder strikter Reihenfolge, weil der Zustand jeder Saga an einer Stelle liegt. Choreografie für kurze Abläufe, in denen zwei oder drei Services auf die Events der anderen reagieren.
Was ist eine Pivot-Transaktion? Der Schritt, nach dem die Saga nicht mehr kompensiert werden kann. Alles davor muss kompensierbar sein, alles danach wiederholbar, bis es gelingt.
Wie gehe ich mit Fehlern in einer Kompensation um? Wiederhole sie. Kompensationen müssen idempotent sein und irgendwann gelingen; wenn eine das nicht kann, braucht die Saga einen Weg zur manuellen Klärung und einen Alarm.
Geben mir Sagas Isolation? Nein. Andere Anfragen können Zwischenzustände sehen. Nutze semantische Sperren wie PENDING-Zustände, kommutative Updates und erneutes Lesen, um die Anomalien zu verhindern, die für dein Geschäft zählen.