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:
| Aspekt | Traditionell | Event Sourcing |
|---|---|---|
| Audit | oft separat implementiert | durch Event-Historie gut unterstützt |
| Debugging | “Wie kam es dazu?” oft schwerer | Replay und Nachvollziehen möglich |
| Undo | je nach Modell schwierig | Kompensations-Events möglich |
| Zeitreisen | nur mit Historie möglich | frühere Zustände rekonstruierbar, wenn Events vollständig sind |
| Analytics | häufig separates System | Events 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.
Fachliche Ereignisse benennen
01
Beschreibe, was geschehen ist, in der Vergangenheit und ohne technische Implementierungsdetails.
BestellungErstellt | ZahlungBestätigt | VersandAusgelöstEreignisse 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 StreamZustand und Ansichten ableiten
03
Projektionen lesen Ereignisse und bauen daraus den aktuellen Fachzustand oder schnelle Abfrageansichten.
Event Stream → Projektion → Read ModelWiederholbarkeit absichern
04
Projektionen müssen Ereignisse erneut verarbeiten können, ohne falsche Doppelwirkungen zu erzeugen.
Replay → idempotente Projektion → konsistente AnsichtVersionen 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 FortgeschritteneVom Ereignis zur nutzbaren Ansicht
Der Event Store bewahrt Fakten; Projektionen übersetzen sie in Zustände und Abfragen für verschiedene Zwecke.
Zeitlich geordnete, unveränderliche Fachereignisse pro Kontext oder Entität.