Cloud allgemein

Event-Driven Data Architecture mit Kafka und CQRS

Event-Driven Datenarchitektur mit Kafka, Event Sourcing und CQRS — Konzepte, Code-Beispiele in PySpark und ehrliche Trade-offs gegenüber Batch-Pipelines.

Harbinger Team14. Mai 20267 Min. LesezeitAktualisiert 14.5.2026
  • event-driven
  • kafka
  • event-sourcing
  • cqrs
  • streaming
  • data-architecture
  • real-time
Inhaltsverzeichnis19 Abschnitte

Batch-Jobs laufen um Mitternacht und produzieren Daten, die 12 Stunden alt sind, sobald sie jemand liest. Deine Kunden treffen Entscheidungen aber in Echtzeit — sie kaufen, kündigen, klicken. Event-Driven Data Architecture ist das Pattern, das diese Lücke schließt: Daten fließen, wenn Events passieren, nicht nach Zeitplan.

TL;DR

  • Event-Driven bedeutet: jede Zustandsänderung wird als unveränderbares Event protokolliert
  • Event Sourcing leitet aktuellen Zustand aus Replay des Event-Logs ab — perfekter Audit-Trail
  • CQRS trennt Write-Path (Commands) von Read-Path (vorberechnete Read-Modelle)
  • Kafka ist das Standard-Backbone für persistente, replayable Event-Logs
  • Trade-off: real-time hat hohe Komplexität — wenn Batch ausreicht, bleib bei Batch

Was ist Event-Driven Data Architecture?

In einer Event-Driven-Architektur wird jede relevante Zustandsänderung als unveränderbares Event aufgezeichnet: OrderPlaced, UserSignedUp, PaymentFailed, InventoryUpdated. Diese Events fließen durch einen Message Broker (meist Apache Kafka) und werden von mehreren Downstream-Systemen unabhängig konsumiert.

Konkret für Data-Plattformen heißt das:

  • Dein Warehouse konsumiert Events und aktualisiert Tabellen near-real-time
  • ML-Modelle reagieren auf Events, ohne eine Datenbank zu pollen
  • Operational- und Analytics-Systeme teilen sich denselben Event-Stream
  • Historischer Zustand lässt sich durch Replay des Event-Logs rekonstruieren
Application Services
       │ emits events
       ▼
  Apache Kafka (Event Log)
       │
  ┌────┴─────────────────────────────────┐
  │             │                │       │
  ▼             ▼                ▼       ▼
Warehouse   ML Feature        Search   Notification
(Flink/     Store             Index    Service
 Spark)     (Redis/Feast)     (ES)     (SendGrid)

Kernkonzepte

Event Sourcing

Event Sourcing heißt: der Zustand deiner Anwendung wird aus einem append-only Log von Events abgeleitet — nicht aus einer veränderbaren Datenbank-Tabelle. Statt eine Zeile zu aktualisieren (UPDATE orders SET status = 'shipped'), hängst du ein Event an (OrderShipped { order_id, shipped_at, carrier }).

Der aktuelle Zustand einer Entität wird durch Replay aller Events für diese Entität berechnet.

Vorteile für Daten-Teams:

  • Perfekter Audit-Trail — jede Zustandsänderung mit Timestamp und Actor
  • Time Travel ist natürlich — Replay bis zu jedem Zeitpunkt
  • Schema-Evolution ist explizit — neue Event-Typen brechen alte Consumer nicht
  • Derived Tables von Grund auf neu aufbauen ist immer möglich

Der Preis: Millionen Events zu replayen, um aktuellen Zustand zu bekommen, ist langsam. Du brauchst Snapshots (periodische Zustands-Snapshots), damit Reads praktikabel bleiben.

CQRS — Command Query Responsibility Segregation

CQRS trennt den Write-Path (Commands ändern Zustand und emittieren Events) vom Read-Path (Queries lesen aus vorberechneten Read-Modellen).

Command Side                    Query Side
(Write)                         (Read)

User Action                     User Query
    │                               │
    ▼                               ▼
Command Handler              Read Model / View
    │                         (denormalisierte Tabelle,
    │ emits event              optimiert für Query)
    ▼                               ▲
Event Store (Kafka)                 │
    │                               │
    └────────── Projector ──────────┘
                (baut und updated
                 Read-Modelle aus Events)

Für Data-Plattformen mappt CQRS direkt auf die Medallion Architecture:

  • Bronze Layer = Raw Event Log (Event Store)
  • Silver Layer = bereinigte, gejointe Projektionen (Read Models)
  • Gold Layer = business-ready Aggregationen (denormalisierte Views)

Apache Kafka als Backbone

Kafka ist der häufigste Event-Broker für Data-Plattformen bei größeren Volumen. Die wichtigsten Eigenschaften:

EigenschaftWas es für Daten-Teams bedeutet
Persistent LogConsumer können von jedem Offset replayen — ermöglicht Backfill
Consumer GroupsMehrere Teams konsumieren denselben Event unabhängig
PartitioningParallelismus nach Key (z. B. user_id) für geordnete Verarbeitung
RetentionRetention nach Zeit oder Größe — Tage bis unendlich
Schema RegistryAvro/Protobuf-Schemas auf Producern erzwingen
Exactly-once SemanticsKonfigurierbare Garantien für kritische Finanz-Pipelines

Eine Event-Driven Data-Pipeline bauen

1. Event-Schema-Design

Designe Events mit Daten-Consumern im Kopf. Schlechte Event-Schemas sind die Hauptursache für Schmerz in Event-Driven-Systemen.

// Gut: selbsterklärend, versioniert, keine domain-spezifischen Abkürzungen
{
  "event_type": "order.placed",
  "event_id": "evt_01HXK3M7N8P2Q4R5T6V7W8X9Y0",
  "schema_version": "2.1",
  "occurred_at": "2026-04-03T09:15:00Z",
  "producer": "order-service",
  "payload": {
    "order_id": "ord_abc123",
    "customer_id": "cust_xyz789",
    "items": [
      {"sku": "WIDGET-42", "quantity": 2, "unit_price_cents": 1999}
    ],
    "total_cents": 3998,
    "currency": "EUR"
  }
}
// Schlecht: abgekürzt, keine Version, keine Schema-Info
{
  "t": "ord_plcd",
  "oid": "abc123",
  "cid": "xyz",
  "amt": 39.98
}

2. Kafka Producer (Python)

# Python — Kafka Producer mit Avro-Schema
from confluent_kafka import Producer
from confluent_kafka.schema_registry import SchemaRegistryClient
from confluent_kafka.schema_registry.avro import AvroSerializer
from confluent_kafka.serialization import SerializationContext, MessageField
import json
from datetime import datetime, timezone

KAFKA_BOOTSTRAP = "kafka:9092"
SCHEMA_REGISTRY_URL = "http://schema-registry:8081"

ORDER_PLACED_SCHEMA = '''
{
  "type": "record",
  "name": "OrderPlaced",
  "namespace": "com.harbinger.orders",
  "fields": [
    {"name": "event_id", "type": "string"},
    {"name": "order_id", "type": "string"},
    {"name": "customer_id", "type": "string"},
    {"name": "total_cents", "type": "long"},
    {"name": "occurred_at", "type": "string"}
  ]
}
'''

schema_registry = SchemaRegistryClient({"url": SCHEMA_REGISTRY_URL})
avro_serializer = AvroSerializer(schema_registry, ORDER_PLACED_SCHEMA)
producer = Producer({"bootstrap.servers": KAFKA_BOOTSTRAP})

def publish_order_placed(order_id: str, customer_id: str, total_cents: int):
    event = {
        "event_id": f"evt_{order_id}_{int(datetime.now().timestamp())}",
        "order_id": order_id,
        "customer_id": customer_id,
        "total_cents": total_cents,
        "occurred_at": datetime.now(timezone.utc).isoformat(),
    }
    producer.produce(
        topic="orders.placed",
        key=customer_id,          # partition by customer for ordering
        value=avro_serializer(event, SerializationContext("orders.placed", MessageField.VALUE)),
    )
    producer.flush()
# PySpark Structured Streaming — konsumiert Kafka, schreibt in Delta Lake
from pyspark.sql import SparkSession
from pyspark.sql.functions import from_json, col, to_timestamp
from pyspark.sql.types import StructType, StructField, StringType, LongType

spark = SparkSession.builder     .appName("OrderEventsConsumer")     .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension")     .getOrCreate()

event_schema = StructType([
    StructField("event_id", StringType()),
    StructField("order_id", StringType()),
    StructField("customer_id", StringType()),
    StructField("total_cents", LongType()),
    StructField("occurred_at", StringType()),
])

raw_stream = spark.readStream     .format("kafka")     .option("kafka.bootstrap.servers", "kafka:9092")     .option("subscribe", "orders.placed")     .option("startingOffsets", "latest")     .load()

parsed = raw_stream     .select(from_json(col("value").cast("string"), event_schema).alias("data"))     .select("data.*")     .withColumn("occurred_at", to_timestamp("occurred_at"))

query = parsed.writeStream     .format("delta")     .outputMode("append")     .option("checkpointLocation", "s3://checkpoints/orders-placed/")     .start("s3://lakehouse/silver/orders_placed/")

query.awaitTermination()

Event-Driven Patterns für Data-Plattformen

Outbox Pattern

Das Outbox Pattern löst ein häufiges Reliability-Problem: du schreibst in deine Datenbank und willst ein Event atomar in Kafka publizieren. Wenn du beides unabhängig schreibst, kann das eine gelingen während das andere fehlschlägt.

Lösung: Schreibe das Event in eine outbox-Tabelle in derselben Datenbank-Transaktion wie dein Business-Write. Ein separater Prozess (Debezium + Change Data Capture) liest die Outbox und publiziert nach Kafka.

Event-Driven Feature Store

ML-Features aus Streaming-Events:

  1. UserSignedUp-Event → Kafka → Flink aggregiert 7-Tage-Session-Counts → Redis Feature Store
  2. Model Inference liest aus Redis (low-latency, frische Features)
  3. Dieselben Events landen in Delta Lake für Batch-Training

Dieses Pattern vermeidet Training/Serving-Skew — beide Pfade lesen aus derselben Event-Quelle.

Saga Pattern für verteilte Transaktionen

Wenn eine Operation mehrere Services umspannt (Order anlegen → Inventar reservieren → Payment durchführen), koordiniert ein Saga über Events statt einer verteilten Transaktion:

  • Jeder Service publiziert ein Success- oder Failure-Event
  • Compensating Events rollen vorherige Schritte bei Fehlern zurück
  • Die Data-Plattform beobachtet alle Saga-Events für einen kompletten Audit-Trail

Trade-offs ehrlich gesagt

Event-Driven ist nicht universell besser. Die operative Komplexität ist real.

AspektEvent-DrivenBatch
LatenzSekundenMinuten bis Stunden
KomplexitätHochNiedrig
DebuggingSchwer (verteilt)Einfacher (Logs)
Ordering-GarantienNur pro PartitionNatürlich
Exactly-onceKonfigurierbar, schwerEinfacher
Schema-ÄnderungenErfordert KoordinationSpaltenzugabe einfach
Team-Skill nötigKafka-ExpertiseSQL/Python
KostenKafka-Cluster-OverheadCompute nach Zeitplan

Fang mit Batch an, wenn dein Team klein ist und dein Business kein real-time braucht. Event-Driven-Komplexität für einen Batch-Use-Case ist Over-Engineering.

Wann Event-Driven für Daten einsetzen

Starke Signale:

  • Du brauchst Sub-Minuten-Freshness in Dashboards oder operativen Systemen
  • Mehrere Systeme brauchen dieselben Events unabhängig (keine enge Kopplung)
  • Du baust ML-Features, die frische Verhaltenssignale brauchen
  • Audit-Trail und Replay-Fähigkeit sind Requirements

Schwache Signale (lieber Batch):

  • Daily Reporting reicht aus
  • Einzelner Downstream-Consumer
  • Team hat keine Kafka-Erfahrung
  • Datenvolumen ist klein genug, dass Scheduled Queries funktionieren

FAQ

Brauche ich Kafka für Event-Driven? Nicht zwingend. Für kleinere Setups reichen NATS, Redis Streams oder cloud-native Optionen wie Google Pub/Sub. Kafka ist Standard bei größeren Volumen und mehreren Consumer-Gruppen.

Was ist der Unterschied zwischen Event Sourcing und Event-Driven? Event-Driven ist eine generelle Architektur-Pattern (Services kommunizieren über Events). Event Sourcing ist spezifischer: der Zustand der Anwendung wird ausschließlich aus dem Event-Log abgeleitet.

Wie skaliert Exactly-once in der Praxis? Exactly-once Kafka + idempotente Sinks (Delta Lake, Iceberg) funktionieren robust. Kosten: höhere Latenz und mehr Koordinations-Overhead. Reserviere es für Financial-Transaktionen und Audit-Trails.

Lohnt sich Event-Driven für deutsche Mittelständler? Wenn du < 5 Datenquellen, Tagesreporting und ein kleines Team hast: nein. Wenn du Multi-Channel-Commerce, Echtzeit-Inventory oder ML-Features brauchst: ja. Datenschutz/DSGVO ist bei Event-Logs ein eigenes Kapitel — Personenbezug muss explizit behandelt werden.

Fazit

Event-Driven Data Architecture ist kraftvoll, wenn Freshness wichtig ist und mehrere Consumer denselben Datenstrom brauchen. Event Sourcing gibt dir komplette, replayable History. CQRS trennt Write- von Read-Optimierung. Kafka liefert das dauerhafte, skalierbare Backbone.

Das Pattern hat reale Kosten: Kafka-Cluster, Schema-Management, Exactly-once-Semantik und verteiltes Debugging. Setze es ein, wo das Business genuin real-time braucht — nicht weil es modern klingt.

Nächster Schritt: Identifiziere eine Batch-Pipeline, bei der die 24-Stunden-Verzögerung ein konkretes Business-Problem verursacht. Das ist dein erster Kandidat für eine Event-Driven-Migration.


Weiterlesen

Stand: 14. Mai 2026.

H

Geschrieben von

Harbinger Team

Cloud-, Data- und AI-Engineer in DACH. Schreibt seit 2018 über infrastruktur­kritische Tech-Entscheidungen — keine Marketing- Folien, sondern echte Trade-offs aus Production-Workloads.

Hat dir das geholfen?

Jede Woche ein neuer Artikel über DACH-Cloud, Data und AI — direkt in dein Postfach. Kein Spam, kein Marketing-Sprech.

Kein Spam. 1-Klick-Abmeldung. Datenschutz bei Loops.so.