Wenn ein Backend „den Zustand“ speichert, wirkt das zunächst ausreichend: eine Zeile pro Bestellung, ein Dokument pro Konto, fertig. In der Praxis entstehen jedoch Fragen, die mit reinen Zustands-Tabellen schwer zu beantworten sind: Wann wurde eine Adresse geändert? Welche Schritte führten zur Stornierung? Warum sieht der Warenkorb heute anders aus als gestern? Genau hier setzt Event Sourcing an: Statt nur den aktuellen Zustand zu persistieren, wird eine geordnete Folge fachlicher Ereignisse gespeichert, aus der sich der Zustand jederzeit ableiten lässt.
Der Ansatz ist kein Selbstzweck. Er lohnt sich vor allem dort, wo Nachvollziehbarkeit, revisionsähnliche Anforderungen, komplexe Geschäftsprozesse oder mehrere Sichten auf dieselben Daten wichtig sind (z.B. Kundensicht vs. Buchhaltungssicht). Gleichzeitig bringt er neue Design- und Betriebsaufgaben mit, die im klassischen CRUD-Stil kaum auftreten.
Wann Event Sourcing sinnvoll ist – und wann nicht
Typische Auslöser aus realen Systemen
Event Sourcing passt gut, wenn ein System fachlich „zeitlich“ denkt: Bestellungen durchlaufen Statuswechsel, Konten werden belastet und gutgeschrieben, Berechtigungen ändern sich, Verträge werden angepasst. In solchen Domänen ist das Ereignis häufig der natürliche Kern: „Bestellung bezahlt“, „Artikel reserviert“, „Zahlung fehlgeschlagen“.
Zusätzliche Signale, dass der Ansatz hilfreich sein kann:
- Viele abgeleitete Sichten werden benötigt (z.B. Dashboard, Export, Suche, Fraud-Analyse), die sich aus denselben Ereignissen speisen.
- Fehleranalyse braucht eine belastbare Historie: nicht nur „was ist“, sondern „wie kam es dazu“.
- Geschäftsregeln hängen von Sequenzen ab (z.B. „nur einmal pro Tag“, „nach X Fehlversuchen sperren“).
Klare Grenzen: kleine CRUD-Features und reine Stammdaten
Für einfache Stammdatenverwaltung (z.B. Länderlisten, statische Produktkataloge ohne Prozess) ist Event Sourcing oft unnötig. Der Overhead lohnt sich ebenfalls selten, wenn die Historie fachlich egal ist und nur wenige Abfragen auf den aktuellen Stand existieren. In solchen Fällen bleibt ein klassisches Modell mit guter Audit-Log-Tabelle meist die pragmatischere Wahl.
Events schneiden: fachlich, stabil, minimal
Events sind Fakten, keine Befehle
Ein häufiges Missverständnis: Ein Event beschreibt nicht, was passieren soll, sondern was passiert ist. „ReserveItem“ wäre ein Befehl (Command), „ItemReserved“ das Event. Das klingt nach Wortklauberei, entscheidet aber über Stabilität: Fakten bleiben bestehen, Befehle ändern sich mit Prozessen und UI-Flows.
Event-Design: was in ein Ereignis gehört
Ein Event sollte die fachliche Änderung tragen, die später rekonstruiert werden muss. Gleichzeitig sollte es nicht zu viel „Viewspezifisches“ enthalten, damit die Ereignisse langlebig bleiben. Bewährt hat sich:
- Eine eindeutige Aggregate-ID (z.B. orderId) und eine Event-ID.
- Ein fachlicher Typ (z.B. OrderPaid) und eine Version (für Evolution).
- Nutzlast mit den relevanten Feldern für die Zustandsableitung.
- Metadaten wie Timestamp, Correlation-ID (zur Request-Kette) und Actor (System/User), falls fachlich notwendig.
Zu vermeiden sind Events, die nur technische Artefakte abbilden („RowUpdated“) oder aus UI-Details geboren werden („Step3Completed“), wenn das fachlich nicht stabil ist.
Aggregate, Invarianten und Concurrency sauber halten
Aggregate bestimmen den Konsistenzrahmen
Im Event-Sourcing-Stil wird häufig ein Aggregate als Einheit definiert, die Invarianten schützt (z.B. „Bestellung darf nicht bezahlt werden, wenn sie storniert ist“). Innerhalb dieses Aggregats wird sequenziell entschieden, welche Events entstehen dürfen. Außerhalb kann asynchron reagiert werden.
Optimistic Concurrency über Versionsnummern
Da mehrere Requests dasselbe Aggregate gleichzeitig verändern können, ist eine konsistente Schreibstrategie entscheidend. In der Praxis wird dafür meist eine erwartete Version mitgeschickt: „Append events only if currentVersion == expectedVersion“. Das verhindert verlorene Updates ohne globale Locks. Konflikte werden dann als Domain-Fehler behandelt (z.B. „Bestellung wurde bereits bezahlt“).
In klassischen REST-Backends lässt sich diese Idee gut mit idempotenten Writes kombinieren. Für robuste Endpoints lohnt sich auch ein Blick auf idempotente APIs, weil doppelte Requests sonst schnell doppelte Events erzeugen.
Lesemodelle bauen: Projektionen statt „SELECT *“
Warum der Event-Stream nicht das Query-API ist
Der Event-Stream ist optimiert fürs Schreiben und fürs Nachvollziehen, nicht fürs Abfragen. Anwendungen brauchen jedoch konkrete Sichten: eine Bestellübersicht, einen Suchindex, einen Kontostand, eine Liste offener Positionen. Diese Sichten werden als Projektionen (Read Models) aufgebaut, indem Events nacheinander verarbeitet und in eine query-freundliche Struktur geschrieben werden.
Projektionen robust machen: Wiederholbarkeit und Idempotenz
Projektionen müssen Wiederholungen aushalten, weil Event-Verarbeitung in der Praxis „at least once“ erfolgen kann (z.B. nach einem Crash, bei Rebalancing oder beim Neuaufbau). Jede Event-Verarbeitung sollte daher idempotent sein: ein Event mit derselben Event-ID darf nicht zweimal zählen. Typische Maßnahmen:
- Projektion speichert die zuletzt verarbeitete Stream-Position pro Aggregate oder pro Partition.
- Deduplication-Tabelle/Key-Set für Event-IDs, falls die Zustandslogik nicht von selbst idempotent ist.
- Transaktionale Updates im Projektion-Store: „apply + checkpoint“ zusammen committen.
Für APIs, die Listen liefern, spielt die Wahl der Pagination ebenfalls in diese Architektur hinein. Cursor-basierte Verfahren harmonieren besser mit fortlaufenden Streams als Offset-Listen; Details dazu passen thematisch zu Cursor-Pagination.
Snapshots, Rebuilds und Evolution im Betrieb
Snapshots gezielt einsetzen
Bei langen Event-Streams kann das Rekonstruieren des Aggregat-Zustands teuer werden (z.B. tausende Events pro Kunde). Ein Snapshot speichert den abgeleiteten Zustand zu einer bestimmten Version, sodass beim Laden nur die Events danach angewendet werden. Snapshots sind ein Performance-Werkzeug, kein Ersatz für Events. Sie dürfen jederzeit neu erzeugt werden.
Praktische Leitplanken:
- Snapshots nur für Aggregate mit langen Historien und hoher Zugriffslast.
- Snapshot-Format strikt versionieren; sonst wird Migration unangenehm.
- Snapshots niemals als alleinige Quelle der Wahrheit behandeln.
Rebuilds planen: Projektionen als Wegwerfprodukte behandeln
Der große Vorteil: Projektionen lassen sich neu aufbauen, wenn die Sicht geändert werden muss. Damit das im Alltag funktioniert, sollte der Neuaufbau von Anfang an als normaler Vorgang gelten: separater Consumer, definierter Startpunkt, kontrollierte Geschwindigkeit (Backpressure) und messbare Fortschritte. Monitoring ist hier wichtiger als „einmalig richtig“.
Event-Versionierung ohne Wildwuchs
Events sind dauerhaft. Deshalb braucht es eine Strategie, wie sich Event-Formate entwickeln. Häufig funktioniert: neue Felder optional hinzufügen, alte Felder nicht entfernen und Default-Verhalten klar definieren. Wenn ein Breaking Change nötig ist, ist ein neuer Event-Typ oft sauberer als „Version 7“ desselben Typs mit komplett anderer Semantik.
Wer ohnehin API-Versionierung betreibt, sollte diese Denke nicht 1:1 auf Events übertragen. APIs sind Verträge nach außen, Events sind interne Fakten. Trotzdem hilft konzeptionelle Klarheit, wie auch bei API-Versionierung.
Fehlerfälle: Duplikate, Reihenfolge, Nebenwirkungen
Duplikate sind normal, Side Effects müssen kontrolliert sein
In verteilten Systemen können Events doppelt verarbeitet werden. Das ist weniger problematisch, wenn Nebenwirkungen (z.B. E-Mail senden, Payment auslösen) sauber entkoppelt und abgesichert sind. Üblich ist: Nebenwirkungen werden durch dedizierte Handler ausgelöst, die selbst eine Idempotenz-Strategie besitzen (z.B. Provider-Idempotency-Key, interne „already sent“-Flags).
Reihenfolge pro Aggregate sicherstellen
Für korrekte Zustandsableitung ist die Reihenfolge von Events innerhalb eines Aggregats entscheidend. Viele Stores garantieren Ordnung pro Stream; wenn parallelisiert wird, sollte die Partitionierung nach Aggregate-ID erfolgen. Globale Ordnung über alle Aggregates ist meist unnötig und teuer.
Konkreter Einstieg in ein bestehendes Backend
Ein pragmatischer Weg in kleinen Schritten
Event Sourcing muss nicht als „Big Bang“ starten. Häufig ist es risikoärmer, mit einem klar abgegrenzten Teilprozess zu beginnen, z.B. „Zahlungsstatus“ oder „Versandworkflow“. Die vorhandene Datenbank kann zunächst als Projektion genutzt werden, während Events als zusätzliche Wahrheitsschicht eingeführt werden.
Bewährte Schritte, die sich in vielen Teams umsetzen lassen:
- Ein Aggregat auswählen, dessen Historie fachlich wertvoll ist (z.B. Order).
- Commands und erlaubte Events definieren, inklusive Invarianten.
- Einen Event-Store einführen (oder eine Event-Log-Tabelle), Append-only mit erwarteter Version.
- Eine erste Projektion für ein konkretes Query-Feature bauen (z.B. „Bestellung anzeigen“).
- Projektion-Consumer so bauen, dass Rebuild möglich ist (Checkpoints, Idempotenz).
- Tests: Domain-Tests auf Event-Sequenzen, plus Integrationstests für Projektionen.
Testen mit Event-Sequenzen statt Datenbank-Fixtures
Ein angenehmer Nebeneffekt: Fachlogik lässt sich über „Given-When-Then“ testen, ohne komplexe DB-Setups. Beispiel: Given [OrderCreated], When PayOrder, Then [OrderPaid]. Damit werden Invarianten sichtbar, und Regressionen zeigen sich als veränderte Event-Ausgaben. Für Integrationen mit Fremdsystemen bleibt dennoch klassische Testarbeit nötig (z.B. Stubs, Contract-Tests).
Vergleich: Event Sourcing vs. Audit-Log vs. CDC
| Ansatz | Stärke | Typische Grenze |
|---|---|---|
| Event Sourcing | Fachliche Historie als Quelle der Wahrheit, flexible Projektionen | Mehr Komplexität bei Queries, Rebuilds, Evolution |
| Audit-Log zur Zustandsdatenbank | Einfach nachzurüsten, gut für Compliance-Logs | Oft unvollständige Fachsemantik, schwer für Rebuild/Derived Views |
| CDC (Change Data Capture) | Technische Änderungserkennung auf DB-Ebene, gut für Sync/Analytics | Änderungen sind technisch, nicht fachlich geschnitten |
Begriffe, die in Teams oft durcheinandergehen
Command, Event, Projection, Saga kurz eingeordnet
Saubere Sprache reduziert Fehlentscheidungen in Architektur-Diskussionen:
- Command: Aufforderung, etwas zu tun (kann scheitern).
- Event: Eingetretene Tatsache (ist passiert, bleibt wahr).
- Projektion: abgeleitete Sicht für Queries.
- Prozess-Manager/Saga: koordiniert mehrere Schritte über Zeit (z.B. reservieren → bezahlen → versenden) und reagiert auf Events.
Für verteilte Prozessketten ist außerdem wichtig, Zeitouts und Retries bewusst zu modellieren. Bei längeren Workflows helfen Muster wie Queues und Worker, wie sie auch bei Async Jobs im Backend üblich sind.
In der Praxis zeigt sich: Event Sourcing funktioniert am besten, wenn Domain-Logik klar gekapselt ist, Projektionen als eigenständige Komponenten betrachtet werden und Betrieb (Rebuild, Monitoring, Backpressure) von Anfang an mitgedacht wird. Dann entsteht ein Backend, das nicht nur „Daten speichert“, sondern Veränderungen als erstklassiges Konzept behandelt.
