Outbox-Muster: Datenbank und Nachricht zuverlässig koppeln
Kurz: Beim Outbox-Muster schreibt man die zu verschickende Nachricht in dieselbe Datenbank-Transaktion wie die Änderung. Ein getrennter Prozess liest die Outbox-Tabelle und verschickt sie. So geht nie eine Änderung ohne Nachricht (oder umgekehrt) verloren.
Das Problem: zwei Systeme, eine Wahrheit
Eine Bestellung soll in der Datenbank gespeichert und als Ereignis an eine Message Queue (Nachrichtenwarteschlange) gesendet werden. Zwei getrennte Schritte können halb scheitern:
- Datenbank geschrieben, Nachricht nicht gesendet (Absturz dazwischen): niemand erfährt von der Bestellung.
- Nachricht gesendet, Speichern schlägt fehl: das Ereignis beschreibt etwas, das es nicht gibt.
Eine echte gemeinsame Transaktion über beide Systeme ist teuer (Verteilte Transaktionen: Zwei-Phasen-Commit und Saga).
Die Lösung
- In einer Transaktion: Zeile in
ordersund Zeile inoutbox(Ereignis als Text/JSON) schreiben. - Ein Relay liest neue Outbox-Zeilen (Abfrage in kurzen Abständen oder über das Änderungsprotokoll der Datenbank) und sendet sie.
- Nach erfolgreichem Senden wird die Zeile als erledigt markiert oder gelöscht.
Wichtig: Doppelte Nachrichten
Stürzt das Relay nach dem Senden, aber vor dem Markieren ab, geht die Nachricht ein zweites Mal raus. Das Muster garantiert also mindestens einmal. Empfänger müssen deshalb idempotent sein, zum Beispiel mit einer Ereignis-ID, die sie schon verarbeitete Nachrichten wiedererkennen lässt.
Wann sinnvoll
Immer dann, wenn eine Änderung zuverlässig ein Ereignis auslösen muss, etwa bei Microservices oder Event Sourcing und CQRS. Für eine einzelne Anwendung mit einer Datenbank reicht oft eine einfache Tabelle als Warteschlange.