Veröffentlicht am

Event Sourcing auf Kubernetes mit Kafka und CQRS

Teilen:
Authors

TL;DR

  • Event Sourcing speichert nicht den aktuellen Zustand, sondern jede Zustandsaenderung als unveraenderliches Event -- vollstaendige Audit-Historie inklusive
  • Event Stores wie Kafka oder EventStoreDB laufen auf Kubernetes als StatefulSets mit persistentem Storage und garantierter Reihenfolge
  • Projections (Read Models) werden aus dem Event Stream materialisiert und koennen jederzeit neu aufgebaut werden
  • Snapshots verhindern, dass bei langen Event-Ketten die Wiederherstellung des aktuellen Zustands zu lange dauert
  • CQRS + Event Sourcing trennt Schreib- und Lese-Seite konsequent -- ideal fuer skalierbare Microservices auf Kubernetes

Kubernetes Event Sourcing: Event-basierte Architektur fuer Microservices

In traditionellen Microservices speichert jeder Service seinen aktuellen Zustand in einer Datenbank. Eine Bestellung hat den Status "bezahlt" -- aber wie sie dorthin gekommen ist, ist verloren.

Event Sourcing dreht dieses Modell um: Statt den aktuellen Zustand zu speichern, wird jede Zustandsaenderung als unveraenderliches Event persistiert. Der aktuelle Zustand ergibt sich durch Abspielen aller Events.

Traditionell (State-basiert):
  orders-db: { id: 42, status: "shipped", total: 99.90 }

Event Sourcing:
  Event 1: OrderCreated    { id: 42, items: [...], total: 109.90 }
  Event 2: DiscountApplied { id: 42, discount: 10.00 }
  Event 3: PaymentReceived { id: 42, amount: 99.90 }
  Event 4: OrderShipped    { id: 42, tracking: "DE123456" }

Der Vorteil: Jede Aenderung ist nachvollziehbar. Sie koennen den Zustand zu jedem beliebigen Zeitpunkt rekonstruieren und neue Auswertungen ueber historische Daten laufen lassen.

Wer sich mit event-basierten Mustern auf Kubernetes vertraut machen will: Kubernetes Saga Pattern behandelt verteilte Transaktionen ueber Microservices hinweg.


Kernkonzepte: Events, Streams und Aggregates

Ein Event beschreibt eine Zustandsaenderung, die bereits passiert ist. Events sind unveraenderlich (immutable), zeitlich geordnet und enthalten alle noetigem Informationen.

# Beispiel: OrderCreated Event
eventType: "OrderCreated"
eventId: "evt-2026-001"
streamId: "order-42"
timestamp: "2026-02-10T14:30:00Z"
version: 1
data:
  orderId: "42"
  customerId: "cust-789"
  items:
    - productId: "prod-100"
      quantity: 2
      price: 49.95
  total: 99.90
metadata:
  correlationId: "req-abc-123"
  userId: "user-456"

Ein Event Stream gruppiert alle Events eines Aggregates. Das Aggregate ist die Konsistenzgrenze -- im Bestellsystem die einzelne Bestellung. Jedes Event hat eine Version fuer Optimistic Concurrency.


Event Store auf Kubernetes: Kafka vs EventStoreDB

Option 1: Apache Kafka als Event Store

Kafka ist primaer ein verteiltes Log-System, eignet sich aber auch als Event Store mit unbegrenzter Retention.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
  namespace: event-sourcing
spec:
  serviceName: kafka-headless
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
        - name: kafka
          image: confluentinc/cp-kafka:7.6.0
          ports:
            - containerPort: 9092
              name: kafka
          env:
            - name: KAFKA_LOG_RETENTION_MS
              value: "-1"
            - name: KAFKA_LOG_RETENTION_BYTES
              value: "-1"
          volumeMounts:
            - name: kafka-data
              mountPath: /var/lib/kafka/data
          resources:
            requests:
              cpu: "500m"
              memory: "2Gi"
            limits:
              cpu: "2"
              memory: "4Gi"
  volumeClaimTemplates:
    - metadata:
        name: kafka-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: ssd-retain
        resources:
          requests:
            storage: 100Gi

Wichtig: Retention auf unbegrenzt setzen (KAFKA_LOG_RETENTION_MS: "-1") -- ohne das loescht Kafka alte Events.

Option 2: EventStoreDB

EventStoreDB ist speziell fuer Event Sourcing gebaut und bietet native Unterstuetzung fuer Streams, Projektionen und Subscriptions.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: eventstoredb
  namespace: event-sourcing
spec:
  serviceName: eventstoredb-headless
  replicas: 3
  selector:
    matchLabels:
      app: eventstoredb
  template:
    metadata:
      labels:
        app: eventstoredb
    spec:
      containers:
        - name: eventstoredb
          image: eventstore/eventstore:24.2
          ports:
            - containerPort: 2113
              name: http
          env:
            - name: EVENTSTORE_CLUSTER_SIZE
              value: "3"
            - name: EVENTSTORE_RUN_PROJECTIONS
              value: "All"
          volumeMounts:
            - name: esdb-data
              mountPath: /var/lib/eventstore
          resources:
            requests:
              cpu: "500m"
              memory: "1Gi"
  volumeClaimTemplates:
    - metadata:
        name: esdb-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: ssd-retain
        resources:
          requests:
            storage: 50Gi
AspektKafkaEventStoreDB
ProjektionenExtern (eigener Code)Eingebaut (JavaScript)
Concurrency ControlNicht nativOptimistic Concurrency
SkalierungHorizontal (Partitions)Cluster (3+ Nodes)
BetriebsaufwandHoch (ZooKeeper/KRaft)Moderat

Projections: Read Models aus Events aufbauen

Events sind optimal zum Schreiben, aber fuer Abfragen ungeeignet. Projections sind materialisierte Read Models, die aus dem Event Stream aufgebaut werden.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-projection
  namespace: event-sourcing
spec:
  replicas: 1
  selector:
    matchLabels:
      app: order-projection
  template:
    metadata:
      labels:
        app: order-projection
    spec:
      containers:
        - name: projector
          image: myregistry/order-projector:1.2.0
          env:
            - name: EVENT_STORE_URL
              value: "eventstoredb-headless.event-sourcing.svc:2113"
            - name: PROJECTION_DB_URL
              valueFrom:
                secretKeyRef:
                  name: projection-db-credentials
                  key: connection-string

Projections sind idempotent und wiederherstellbar. Wenn das Read Model korrupt ist, loeschen Sie es und bauen es aus dem Event Stream neu auf.

Fuer die zuverlaessige Event-Uebermittlung zwischen Services empfiehlt sich das Kubernetes Outbox Pattern.


Snapshots: Performance bei langen Event-Ketten

Ein Aggregate mit tausenden Events bei jedem Zugriff komplett neu aufzubauen ist ineffizient. Snapshots speichern den materialisierten Zustand zu einem bestimmten Zeitpunkt.

Ohne Snapshots:
  Event 1 -> Event 2 -> ... -> Event 5000 -> Aktueller Zustand
  (5000 Events abspielen = langsam)

Mit Snapshots:
  Snapshot bei Event 4900 -> Event 4901 -> ... -> Event 5000
  (Snapshot laden + 100 Events = schnell)

Wann Snapshots einsetzen: Aggregates mit mehr als 50-100 Events, latenz-sensitive Lese-Operationen, haeufig geladene Aggregates. Wann vermeiden: Aggregates mit wenigen Events (Overhead ueberwiegt).


CQRS + Event Sourcing: Die Kombination

CQRS trennt die Schreib-Seite (Commands) von der Lese-Seite (Queries). In Kombination mit Event Sourcing entsteht eine leistungsfaehige Architektur.

# Command-Seite: Nimmt Befehle entgegen, schreibt Events
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-command-service
  namespace: event-sourcing
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-command
  template:
    metadata:
      labels:
        app: order-command
    spec:
      containers:
        - name: command-handler
          image: myregistry/order-command:2.1.0
          ports:
            - containerPort: 8080
          env:
            - name: EVENT_STORE_URL
              value: "eventstoredb-headless.event-sourcing.svc:2113"
---
# Query-Seite: Liest aus dem materialisierten Read Model
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-query-service
  namespace: event-sourcing
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-query
  template:
    metadata:
      labels:
        app: order-query
    spec:
      containers:
        - name: query-handler
          image: myregistry/order-query:2.1.0
          ports:
            - containerPort: 8081
          env:
            - name: READ_DB_URL
              valueFrom:
                secretKeyRef:
                  name: read-db-credentials
                  key: connection-string

Vorteile: Schreib- und Lese-Seite unabhaengig skalieren, multiple Read Models fuer verschiedene Abfrage-Muster, Event Store als Single Source of Truth.


Event Versioning: Schema-Evolution beherrschen

Events sind unveraenderlich -- aber Ihr Code entwickelt sich weiter. Drei Strategien fuer Schema-Evolution:

Strategie 1: Weak Schema -- Neue Felder hinzufuegen, Consumer prueft Version und nutzt Defaults.

# Version 1
eventType: "OrderCreated"
version: 1
data:
  orderId: "42"
  total: 99.90

# Version 2 (neues optionales Feld)
eventType: "OrderCreated"
version: 2
data:
  orderId: "42"
  total: 99.90
  currency: "EUR"

Strategie 2: Upcasting -- Events werden beim Lesen in die aktuelle Version transformiert.

Strategie 3: Neue Event-Typen -- Bei grundlegenden Aenderungen einen neuen Typ einfuehren (OrderCreatedV2). Beide Typen koexistieren.

Best Practices: Felder nur hinzufuegen, nie entfernen. Schema Registry fuer Kafka-basierte Systeme nutzen. Kompatibilitaets-Tests in die CI/CD-Pipeline integrieren.


Monitoring fuer Event Sourcing

apiVersion: v1
kind: ConfigMap
metadata:
  name: event-sourcing-alerts
  namespace: event-sourcing
data:
  alerts.yaml: |
    groups:
      - name: event-sourcing
        rules:
          - alert: ProjectionLagHigh
            expr: event_sourcing_projection_lag_seconds > 30
            for: 5m
            labels:
              severity: warning
          - alert: EventStoreWriteLatencyHigh
            expr: histogram_quantile(0.99, event_store_write_duration_seconds_bucket) > 0.5
            for: 5m
            labels:
              severity: critical

Fuer die Trennung von Lese- und Schreibmodellen: CQRS Pattern auf Kubernetes.


Wann Event Sourcing einsetzen?

Gut geeignet: Systeme mit Audit-Anforderungen (Finanzen, Gesundheitswesen), Domains mit vielen Zustandsuebergaengen, temporale Queries, hohe Schreib-Raten mit verschiedenen Lese-Mustern.

Weniger geeignet: Einfache CRUD-Anwendungen, Prototypen und MVPs, Systeme ohne Audit-Anforderungen.

Fuer die Isolierung von Service-Daten: Database per Service Pattern.


Fazit

Event Sourcing auf Kubernetes kombiniert eine leistungsfaehige Architektur mit skalierbarer Infrastruktur. StatefulSets garantieren die Stabilitaet des Event Stores, Deployments skalieren Projections horizontal, und Kubernetes-native Monitoring-Tools ueberwachen Projection Lag und Schreib-Latenzen.

Beginnen Sie mit einem einzelnen Aggregate, implementieren Sie den Event Store (Kafka oder EventStoreDB), fuegen Sie eine Projection hinzu -- und erweitern Sie von dort. Fuer Resilience-Strategien in verteilten Systemen: Resilience Patterns auf Kubernetes.


Verwandte Artikel

Braucht ihr Unterstuetzung bei der Einfuehrung von Event Sourcing oder der Kafka-Architektur auf Kubernetes? Kontaktiert uns fuer eine individuelle Beratung.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen