Event Sourcing

Wie Ereignisse als historische Fakten gespeichert werden und daraus nachvollziehbare Zustände und Ansichten entstehen.

Ein Architektur-Pattern, bei dem Zustand nicht nur als aktueller Snapshot gespeichert wird, sondern aus einer Sequenz von Events rekonstruiert werden kann.

Experte2 Min Lesezeit

Erklärung starten

Auf einen Blick

3 Punkte

  1. Speichert Events (was passiert ist) statt aktuellen Zustand
  2. Starke Audit-Historie und Nachvollziehbarkeit
  3. Zustand kann durch Replay von Events rekonstruiert werden

Sofortantwort

Event Sourcing einfach erklärt

Zustand als Sequenz von Events speichern und bei Bedarf rekonstruieren.

Kurz gesagt
Speichert Events (was passiert ist) statt aktuellen Zustand
Typischer Einsatz
Finanzanwendungen, Marketing, Collaboration Tools
Wichtig zu wissen
Zustand kann durch Replay von Events rekonstruiert werden

Event Sourcing im Überblick

Bei Event Sourcing speicherst du Ereignisse, die zu einem Zustand geführt haben. Der aktuelle Zustand wird durch Abspielen relevanter Events oder durch Kombination aus Snapshot und neuen Events rekonstruiert.

Traditionell (State-basiert):

Datenbank: { balance: current_balance }

→ Du weißt: aktueller Kontostand
→ Ohne zusätzliche Historie weißt du nicht vollständig, wie es dazu kam

Event Sourcing:

Event Store:
1. AccountCreated { balance: initial_balance }
2. MoneyDeposited { amount: deposit_amount }
3. MoneyWithdrawn { amount: withdrawal_amount }

→ Replay: Zustand aus Events rekonstruieren
→ Du siehst, welche Änderungen passiert sind

Vorteile:

AspektTraditionellEvent Sourcing
Auditoft separat implementiertdurch Event-Historie gut unterstützt
Debugging“Wie kam es dazu?” oft schwererReplay und Nachvollziehen möglich
Undoje nach Modell schwierigKompensations-Events möglich
Zeitreisennur mit Historie möglichfrühere Zustände rekonstruierbar, wenn Events vollständig sind
Analyticshäufig separates SystemEvents können direkt ausgewertet werden

Technisch betrachtet

Event Store Struktur

┌─────────────────────────────────────────────────────┐
│                    Event Store                       │
├──────────┬──────────┬───────────────────────────────┤
│ Event ID │ Stream   │ Event Data                    │
├──────────┼──────────┼───────────────────────────────┤
│ event-1  │ order-1  │ OrderCreated { items: [...] } │
│ event-2  │ order-1  │ ItemAdded { sku: "SKU_A" }    │
│ event-3  │ order-1  │ ItemRemoved { sku: "SKU_B" }  │
│ event-4  │ order-1  │ OrderSubmitted { total: ... } │
│ event-5  │ order-2  │ OrderCreated { items: [...] } │
└──────────┴──────────┴───────────────────────────────┘

Streams: Gruppieren Events zu einer Entität (z.B. eine Bestellung).

Event-Klassen

from dataclasses import dataclass
from datetime import datetime
from typing import List

@dataclass
class Event:
    event_id: str
    timestamp: datetime
    
@dataclass
class OrderCreated(Event):
    order_id: str
    customer_id: str
    items: List[dict]

@dataclass
class ItemAdded(Event):
    order_id: str
    sku: str
    quantity: int
    price: float

@dataclass
class OrderSubmitted(Event):
    order_id: str
    total: float

Aggregate und Replay

class Order:
    def __init__(self, order_id: str):
        self.order_id = order_id
        self.items = []
        self.status = "draft"
        self.total = 0
    
    def apply(self, event: Event):
        """Event auf Zustand anwenden"""
        if isinstance(event, OrderCreated):
            self.items = event.items
        elif isinstance(event, ItemAdded):
            self.items.append({
                "sku": event.sku,
                "quantity": event.quantity,
                "price": event.price
            })
        elif isinstance(event, OrderSubmitted):
            self.status = "submitted"
            self.total = event.total
    
    @classmethod
    def from_events(cls, order_id: str, events: List[Event]):
        """Zustand aus Events rekonstruieren"""
        order = cls(order_id)
        for event in events:
            order.apply(event)
        return order

Commands und Events

class OrderService:
    def __init__(self, event_store):
        self.event_store = event_store
    
    def add_item(self, order_id: str, sku: str, quantity: int, price: float):
        # 1. Aktuelle Events laden
        events = self.event_store.get_events(f"order-{order_id}")
        
        # 2. Zustand rekonstruieren
        order = Order.from_events(order_id, events)
        
        # 3. Business Logic prüfen
        if order.status != "draft":
            raise Exception("Cannot modify submitted order")
        
        # 4. Neues Event erstellen und speichern
        event = ItemAdded(
            event_id=str(uuid4()),
            timestamp=clock.now(),
            order_id=order_id,
            sku=sku,
            quantity=quantity,
            price=price
        )
        self.event_store.append(f"order-{order_id}", event)

Snapshots

Bei vielen Events kann Replay langsam werden. Eine häufige Lösung sind Snapshots.

class SnapshotStore:
    def save_snapshot(self, stream_id: str, state: dict, version: int):
        # Aktuellen Zustand speichern
        ...
    
    def get_snapshot(self, stream_id: str):
        # Letzten Snapshot laden
        ...

def load_order(order_id: str):
    # 1. Snapshot laden (falls vorhanden)
    snapshot = snapshot_store.get_snapshot(f"order-{order_id}")
    
    if snapshot:
        order = Order.from_snapshot(snapshot.state)
        # Nur Events seit Snapshot laden
        events = event_store.get_events_since(
            f"order-{order_id}", 
            snapshot.version
        )
    else:
        order = Order(order_id)
        events = event_store.get_events(f"order-{order_id}")
    
    # 2. Restliche Events anwenden
    for event in events:
        order.apply(event)
    
    return order

CQRS + Event Sourcing

Oft kombiniert:

Commands → Event Store → Events

           Projections

         Read Models (optimiert für Queries)

Projections: Transformieren Events in lesbare oder abfrageoptimierte Views.

class OrderListProjection:
    def __init__(self, read_db):
        self.read_db = read_db
    
    def handle(self, event: Event):
        if isinstance(event, OrderCreated):
            self.read_db.insert("orders", {
                "id": event.order_id,
                "customer": event.customer_id,
                "status": "draft"
            })
        elif isinstance(event, OrderSubmitted):
            self.read_db.update("orders", 
                {"id": event.order_id}, 
                {"status": "submitted", "total": event.total}
            )

Schritt für Schritt

Event Sourcing mit belastbaren Ereignissen aufbauen

Events sind langlebige Fakten. Ihre Namen, Inhalte und Versionen müssen deshalb fachlich verständlich und dauerhaft behandelbar sein.

  1. Fachliche Ereignisse benennen

    01

    Beschreibe, was geschehen ist, in der Vergangenheit und ohne technische Implementierungsdetails.

    BestellungErstellt | ZahlungBestätigt | VersandAusgelöst
  2. Ereignisse validiert anhängen

    02

    Fachregeln prüfen, ob ein neues Ereignis zulässig ist, bevor es unveränderlich im passenden Stream gespeichert wird.

    Command → Regelprüfung → neues Event im Stream
  3. Zustand und Ansichten ableiten

    03

    Projektionen lesen Ereignisse und bauen daraus den aktuellen Fachzustand oder schnelle Abfrageansichten.

    Event Stream → Projektion → Read Model
  4. Wiederholbarkeit absichern

    04

    Projektionen müssen Ereignisse erneut verarbeiten können, ohne falsche Doppelwirkungen zu erzeugen.

    Replay → idempotente Projektion → konsistente Ansicht
  5. Versionen und Betrieb planen

    05

    Änderungen an Ereignisschemata, Snapshots und Fehlerbehandlung werden vor dem Rollout bewusst getestet.

    Event-Version → Migrationsstrategie → Rebuild-Test

Konkretes Beispiel

Beispiel: Bestellverlauf im Handel

Ein Team muss nachvollziehen können, wie eine Bestellung ihren aktuellen Zustand erreicht hat.

Historische Frage

Welche Änderungen und Freigaben führten zu einer Stornierung, und wie sah der Vorgang zu einem früheren Zeitpunkt aus?

Ereignisbasierte Antwort

Ein geordneter Stream fachlicher Ereignisse und Projektionen für den aktuellen Status, das Support-Interface und Auswertungen.

Event Sourcing ist wertvoll, wenn die Geschichte einer Entscheidung selbst ein wichtiger Teil des Fachmodells ist.

Einordnung

Vorteile und Grenzen

Das spricht dafür

  • Nachvollziehbare Historie: Der Weg zum aktuellen Zustand bleibt als Folge fachlicher Ereignisse sichtbar.
  • Flexible Ansichten: Neue Auswertungen können aus bestehenden Ereignissen abgeleitet werden.
  • Gute Domänenmodellierung: Teams müssen Zustandsänderungen präzise und fachlich benennen.

Das solltest du beachten

  • Höhere Komplexität: Projektionen, Versionierung und Wiederholbarkeit brauchen mehr Architekturarbeit.
  • Keine automatische Audit-Lösung: Ereignisse benötigen weiterhin Datenschutz-, Zugriffs- und Aufbewahrungsregeln.
  • Nicht für jeden Fall: Einfache CRUD-Prozesse profitieren oft nicht genug vom zusätzlichen Aufwand.
Vertiefung · für Fortgeschrittene

Vom Ereignis zur nutzbaren Ansicht

Der Event Store bewahrt Fakten; Projektionen übersetzen sie in Zustände und Abfragen für verschiedene Zwecke.

Wissenskarte

Zeitlich geordnete, unveränderliche Fachereignisse pro Kontext oder Entität.

Die Architektur trennt historische Fakten von den Ansichten, die Anwendungen im Alltag schnell benötigen. Event Stream gliedert sich in: Command, Fachregel, Event Store, Snapshot, Projektion, Read Model.

01Einsatzbereiche

Wann ist Event Sourcing sinnvoll?

Geeignet für

  • FinanzanwendungenNachvollziehbare Transaktionshistorie für Audit und Compliance
  • MarketingBestellhistorie, Warenkorbänderungen nachvollziehen
  • Collaboration ToolsDokumenten-Historie, Undo/Redo-Funktionalität
  • GamingSpielstände rekonstruieren, Replays ermöglichen

↑ Inhalt

02Werkzeuge

Womit Event Sourcing umgesetzt wird

↑ Inhalt

Merksatz

Event Sourcing ist wie ein Kontoauszug

Statt nur den aktuellen Kontostand zu speichern, hast du alle Transaktionen. Du kannst jederzeit nachvollziehen, wie der Stand zustande kam – und den Zustand zu jedem Zeitpunkt rekonstruieren.

  1. Speichert Events (was passiert ist) statt aktuellen Zustand
  2. Starke Audit-Historie und Nachvollziehbarkeit
  3. Zustand kann durch Replay von Events rekonstruiert werden

04Direkt anwenden

Mit diesem Prompt weiterarbeiten

Eine fertige Vorlage, die Event Sourcing in eine konkrete Aufgabe übersetzt.
CodeExperte

Event Sourcing implementieren

Event-Sourcing-Architektur für auditierbare, zeitreisefähige Datenhaltung.

3 Variablen · 4 Min

↑ Inhalt

05Redaktion

Herkunft und Stand

Redaktion und Aktualität

Ebenex RedaktionRedaktion

Veröffentlicht
Aktualisiert

Dieses Feld entwickelt sich schnell. Oben stehen Veröffentlichung und letzte Änderung; ein Prüfdatum kommt dazu, sobald die Erklärung nach ihrer letzten Änderung geprüft wurde.

↑ Inhalt

06FAQ

Häufige Fragen zu Event Sourcing

Wann sollte ich Event Sourcing nutzen?

Wenn Audit-Trail, Nachvollziehbarkeit, Rekonstruktion früherer Zustände oder komplexe Domain-Logik wichtig sind. Für einfache CRUD-Anwendungen ist es häufig zu aufwändig.

Wird die Event-Liste nicht zu lang?

Sie kann sehr lang werden. Deshalb werden oft Snapshots genutzt: Der aktuelle Zustand wird periodisch gespeichert, und nur Events seit dem letzten Snapshot werden erneut abgespielt. Alte Events können je nach Anforderungen archiviert werden.

Was ist der Unterschied zu Event-Driven Architecture?

Event-Driven: Events zur Kommunikation zwischen Services. Event Sourcing: Events als primäre Datenquelle. Oft kombiniert, aber unterschiedliche Konzepte.

Wie handle ich Schema-Änderungen bei Events?

Event Versioning: Alte Events bleiben erhalten, neue Events nutzen ein neues Schema. Upcasting kann alte Events beim Lesen in ein neueres Format transformieren. Das erfordert sorgfältige Planung und Tests.

↑ Inhalt

07Weiterlernen

Was möchtest du als Nächstes verstehen?

  • Grundlage nachholen

    CQRS

    Trennung von Lese- und Schreibmodellen für gezielte Optimierung.

  • Technisch vertiefen

    Event-Driven Architecture

    Komponenten kommunizieren über Ereignisse statt direkte Aufrufe – lose Kopplung.

↑ Inhalt