Event-Driven Architecture in der Praxis System-Design

Event-Driven Architecture in der Praxis: Wann sie funktioniert – und wann nicht

Carola Schulte
Carola Schulte 1. April 2025 18 min Lesezeit

Event-Driven Architecture ist der Liebling der Konferenz-Slides: lose Kopplung, unendliche Skalierbarkeit, Echtzeit-Reaktionen. In der Realität sehe ich Teams, die nach einem Jahr im Event-Chaos versinken – weil niemand mehr weiß, welcher Service auf welches Event reagiert, und Debugging zum Detektivspiel wird. EDA ist mächtig. Aber sie ist kein Allheilmittel.

Kurz gesagt: Event-Driven Architecture entkoppelt Services durch asynchrone Kommunikation über Events. Das ermöglicht Skalierbarkeit und Flexibilität – bringt aber Komplexität bei Konsistenz, Debugging und Operations mit sich.

Wenn Sie nicht bereit sind, Observability und Event-Governance wie Produktfeatures zu behandeln, lassen Sie EDA.

Was ist Event-Driven Architecture?

In einer Event-Driven Architecture kommunizieren Services nicht direkt miteinander, sondern über Events – Nachrichten, die beschreiben, dass etwas passiert ist:

// Synchron (Request/Response)
OrderService → UserService.getUser(userId)
              ← User

// Event-Driven (Asynchron)
OrderService → publishes: OrderCreated { orderId, userId, items }

UserService ← subscribes: OrderCreated
InventoryService ← subscribes: OrderCreated
NotificationService ← subscribes: OrderCreated

Der entscheidende Unterschied: Der OrderService weiß nicht, wer auf seine Events reagiert. Er feuert und vergisst.

Event-Typen

Typ Beschreibung Beispiel
Domain Event Geschäftlich relevantes Ereignis OrderPlaced, PaymentReceived
Integration Event Kommunikation zwischen Bounded Contexts CustomerCreated (für andere Services)
Event Notification Signal ohne viele Daten InventoryLow { productId }
Event-Carried State Transfer Event mit vollständigem State CustomerUpdated { ...alle Felder }
Der Haken: In der Praxis sehe ich oft eine Vermischung dieser Typen ohne klare Strategie. Das führt zu Events, die mal zu viel, mal zu wenig Daten enthalten – und zu Consumern, die ständig nachfragen müssen.

Jedes Event ist eine öffentliche API. Wenn Sie Events ohne Versionierung und Contracts publishen, bauen Sie Abhängigkeiten, die Sie später nicht mehr brechen können.

Wann EDA sinnvoll ist

Gute Use Cases

  • Lose Kopplung zwischen Teams: Team A muss nicht auf Team B warten
  • Unterschiedliche Skalierungsanforderungen: Order-Ingestion vs. Reporting
  • Asynchrone Workflows: Bestellung → Zahlung → Versand → Benachrichtigung
  • Audit-Trail: Events als unveränderliche Historie
  • Event Sourcing: State aus Event-Historie rekonstruierbar
  • Real-time Reactions: Fraud Detection, Recommendations

Schlechte Use Cases

  • Synchrone Abhängigkeiten: „Zeige Bestellung mit Kundendaten" braucht beides jetzt
  • Starke Konsistenzanforderungen: Banktransaktionen, Inventar-Reservierung. Wenn Sie starke Konsistenz brauchen, ist EDA nicht „schwierig" – dann ist es das falsche Default. Planen Sie explizit Sagas + Kompensation oder bleiben Sie synchron.
  • Einfache CRUD-Anwendungen: Blog, Kontaktformular – Events sind Overkill
  • Kleine Teams: EDA-Overhead lohnt sich erst ab gewisser Größe

Architektur-Patterns

Event Broker

Der zentrale Baustein: Ein Message Broker, der Events entgegennimmt und an Subscriber verteilt:

┌─────────────┐     ┌─────────────────┐     ┌─────────────┐
│ Order       │────▶│                 │────▶│ Inventory   │
│ Service     │     │   Kafka/        │     │ Service     │
└─────────────┘     │   RabbitMQ/     │     └─────────────┘
                    │   AWS SNS+SQS   │
┌─────────────┐     │                 │     ┌─────────────┐
│ Payment     │────▶│                 │────▶│ Notification│
│ Service     │     └─────────────────┘     │ Service     │
└─────────────┘                             └─────────────┘
Broker Stärken Wann einsetzen
Apache Kafka Hoher Durchsatz, Event-Log, Replay High-Volume, Event Sourcing
RabbitMQ Flexible Routing, einfaches Setup Klassische Message Queues
AWS SNS/SQS Managed, Pay-per-Use AWS-Umgebung, Serverless
Redis Streams Schnell, einfach Einfache Use Cases, bereits Redis im Stack
In der Praxis: Die Tool-Wahl ist selten das Problem. Ich sehe mehr Projekte an falschem Event-Design scheitern als an der Broker-Auswahl.

Event Sourcing

Statt den aktuellen State zu speichern, speichern Sie alle Events, die zum State geführt haben:

// Klassisch: State speichern
UPDATE orders SET status = 'shipped' WHERE id = 123;

// Event Sourcing: Events speichern
INSERT INTO events (aggregate_id, type, data) VALUES
  (123, 'OrderCreated', '{"items": [...]}'),
  (123, 'PaymentReceived', '{"amount": 99.00}'),
  (123, 'OrderShipped', '{"trackingId": "DHL123"}');

// State wird durch Replay rekonstruiert
function getOrderState(orderId) {
  const events = getEventsForOrder(orderId);
  return events.reduce(applyEvent, initialState);
}
Vorteile: Vollständiger Audit-Trail, Zeitreisen möglich („Wie sah die Bestellung am 1.3. aus?"), keine Daten gehen verloren.
Wenn's knallt: Event Sourcing ist komplex. Schema-Evolution („Wie migriere ich alte Events?"), Performance bei langer Event-Historie, GDPR-Compliance („Wie lösche ich Kundendaten aus unveränderlichen Events?"). Ich empfehle es nur für Domains, wo die Vorteile den Aufwand rechtfertigen – Finanztransaktionen, Audit-kritische Systeme.

CQRS (Command Query Responsibility Segregation)

Trennung von Schreib- und Lesemodell:

// Command Side (Schreiben)
POST /orders → OrderService → Events → Event Store

// Query Side (Lesen)
GET /orders → Read Model (optimiert für Queries)
                  ↑
            Projections (aus Events aufgebaut)

Das ermöglicht unterschiedliche Optimierungen: Schreibseite normalisiert für Konsistenz, Leseseite denormalisiert für Performance.

Der Haken: CQRS bedeutet Eventual Consistency zwischen Schreib- und Lesemodell. Nach einem Schreibvorgang kann es Millisekunden bis Sekunden dauern, bis das Lesemodell aktualisiert ist. Das müssen UI und Prozesse berücksichtigen.

Der Elefant im Raum: Eventual Consistency

In verteilten Event-Driven Systemen gibt es keine sofortige Konsistenz. Events brauchen Zeit, bis sie verarbeitet sind. Das führt zu Situationen, die in synchronen Systemen nicht existieren:

// User erstellt Bestellung
POST /orders → 201 Created

// User ruft sofort Bestellübersicht auf
GET /orders → Bestellung fehlt noch (Event noch nicht verarbeitet)

// 500ms später
GET /orders → Bestellung ist da

Strategien für Eventual Consistency

  • Optimistic UI: Zeige sofort lokalen State, aktualisiere bei Event-Bestätigung
  • Polling/WebSocket: Client wartet auf Bestätigung
  • Read-your-writes: Nach Schreibvorgang aus Write-Modell lesen
  • Akzeptieren: Manche Inkonsistenzen sind geschäftlich akzeptabel
Praxis-Tipp: Eventual Consistency ist kein technisches Problem, sondern ein Business-Problem. Klären Sie mit Stakeholdern: Wie lange darf die Verzögerung maximal sein? Was passiert bei Inkonsistenzen? Diese Antworten bestimmen die Architektur.

Die häufigsten Fallstricke

1. Event-Chaos

Nach zwei Jahren hat niemand mehr den Überblick: 200 Event-Typen, keine Dokumentation, Services, die auf Events reagieren, von denen keiner weiß.

Lösung: Event Catalog von Tag 1. Jedes Event dokumentiert: Schema, Producer, Consumer, Business-Zweck. Tools wie AsyncAPI oder Event Catalog helfen.

2. Distributed Monolith

Services, die synchron aufeinander warten oder feste Reihenfolgen erwarten – die Nachteile von Microservices ohne die Vorteile.

// Anti-Pattern: Synchrone Kette über Events
OrderService → OrderCreated
  → InventoryService prüft → InventoryReserved
    → PaymentService wartet auf InventoryReserved → PaymentProcessed
      → OrderService wartet auf PaymentProcessed → OrderConfirmed

// Ergebnis: Latenz addiert sich, ein Ausfall blockiert alles

Das passiert fast immer, wenn Teams Sequenzen über Events modellieren, die eigentlich ein synchrones Geschäfts-Transaktionsthema sind.

Lösung: Choreographie statt Orchestrierung – oder explizite Saga mit Kompensation.

3. Fehlende Idempotenz

Events können doppelt ankommen (at-least-once delivery). Ohne Idempotenz führt das zu Chaos:

// Event kommt zweimal an
PaymentReceived { orderId: 123, amount: 99.00 }
PaymentReceived { orderId: 123, amount: 99.00 }

// Ohne Idempotenz: Kunde zahlt doppelt
// Mit Idempotenz: Zweites Event wird ignoriert

Lösung: Event-IDs speichern, bereits verarbeitete Events überspringen. Idempotente Operationen designen. Praxis: Outbox (Producer) + Inbox/Processed-Events (Consumer) ist der Standard, wenn Sie at-least-once sauber beherrschen wollen.

4. Schema-Evolution

Events ändern sich. Neue Felder, geänderte Strukturen, deprecated Fields. Alte Consumer müssen mit neuen Events umgehen können – und umgekehrt.

// Version 1
OrderCreated { orderId, items }

// Version 2 (neues Feld)
OrderCreated { orderId, items, customerId }

// Version 3 (breaking change)
OrderCreated { orderId, lineItems, customer: { id, name } }

Breaking Changes in Events sind Produktionsausfälle mit Ansage. Wenn Sie sie brauchen, müssen Sie parallel Versionen fahren oder eine neue Event-Name-Generation publishen.

Lösung: Schema Registry (Confluent, AWS Glue), Versionierung, Forward/Backward Compatibility als Regel.

5. Debugging-Hölle

Etwas geht schief. Wo? Ein Request wird zu Events, Events werden zu mehr Events, verteilt über 10 Services. Viel Spaß beim Debuggen.

Die harte Wahrheit: Ohne Distributed Tracing ist EDA nicht betreibbar. Correlation-IDs, Trace-IDs in jedem Event, Tools wie Jaeger oder Zipkin sind Pflicht, nicht Kür. Ohne Tracing sind Sie blind. Und blinde Systeme sind nicht skalierbar, egal wie gut Kafka läuft.

Observability ist nicht optional

In Event-Driven Systemen brauchen Sie mehr als Logs:

Must-haves

  • Distributed Tracing: Request über alle Services verfolgen
  • Event Flow Visualization: Welches Event triggert was?
  • Consumer Lag: Wie weit hinkt ein Consumer hinterher?
  • Dead Letter Queues: Wohin gehen fehlgeschlagene Events?
  • Schema Registry: Welche Event-Versionen sind im Umlauf?
// Jedes Event braucht:
{
  "eventId": "uuid",           // Einzigartig
  "correlationId": "uuid",     // Request-übergreifend
  "causationId": "uuid",       // Welches Event hat dieses ausgelöst?
  "timestamp": "ISO8601",
  "version": "1.2.0",
  "source": "order-service",
  "type": "OrderCreated",
  "data": { ... }
}

Migration zu EDA

Sie haben einen Monolithen und wollen Event-Driven werden? Nicht alles auf einmal.

Strangler Fig mit Events

  1. Event-Emission im Monolith: Beginnen Sie, Events zu publishen – auch wenn noch niemand zuhört
  2. Neue Features Event-Driven: Alles Neue als separate Services, die auf Events reagieren
  3. Schrittweise Extraktion: Bestehende Funktionalität in Services migrieren
  4. Anti-Corruption Layer: Übersetzer zwischen alter und neuer Welt
Praxis-Tipp: Starten Sie mit einem Outbox Pattern. Der Monolith schreibt Events in eine Tabelle, ein separater Prozess published sie. So bekommen Sie Event-Semantik ohne große Änderungen am bestehenden Code.

Fazit

Event-Driven Architecture ist kein Upgrade – sie ist ein Trade-off. Sie tauschen synchrone Komplexität gegen asynchrone Komplexität. Manchmal ist das der richtige Trade. Oft nicht.

EDA funktioniert, wenn:

  • Sie echte Entkopplung brauchen (Teams, Skalierung, Deployment)
  • Eventual Consistency für Ihr Business akzeptabel ist
  • Sie in Observability investieren
  • Sie Event-Design als Architektur-Disziplin behandeln

EDA scheitert, wenn:

  • Sie sie einführen, weil Kafka cool ist
  • Sie synchrone Probleme mit asynchronen Tools lösen wollen
  • Niemand für Event-Governance verantwortlich ist
  • Debugging und Operations nachgedacht werden
Nächster Schritt: Bevor Sie Kafka installieren, zeichnen Sie Ihre wichtigsten Geschäftsprozesse als Event-Flows. Wo entstehen Events? Wer reagiert? Was passiert bei Fehlern? Diese Übung zeigt, ob EDA für Sie passt – und wo die Komplexität liegt. In Architektur-Reviews mache ich genau das: Event-Flows modellieren, bevor eine Zeile Code geschrieben wird. Das spart Monate an Korrekturen.

Häufige Fragen

Kafka oder RabbitMQ?

Kafka für High-Volume, Event Sourcing, Replay-Anforderungen. RabbitMQ für klassische Message-Queues, einfachere Setups, flexible Routing-Patterns. Wenn Sie fragen müssen, starten Sie mit RabbitMQ – es ist einfacher zu betreiben.

Wie viele Events sind zu viele?

Keine feste Zahl, aber eine Faustregel: Wenn niemand im Team alle Event-Typen kennt, haben Sie zu viele. 50-100 Events sind bei größeren Systemen normal, aber jedes sollte dokumentiert und begründet sein.

Event Sourcing – ja oder nein?

Nein, außer Sie haben einen klaren Grund (Audit-Anforderungen, Zeitreisen, komplexe Aggregate). Event Sourcing ist eine Architektur-Entscheidung mit weitreichenden Konsequenzen. Starten Sie ohne, führen Sie es später ein, wenn nötig.

Wie teste ich Event-Driven Systeme?

Unit-Tests für Event-Handler, Contract-Tests für Event-Schemas, Integration-Tests mit embedded Broker, End-to-End-Tests für kritische Flows. Chaos Engineering für Resilience. Testbarkeit muss von Anfang an eingeplant werden.


Carola Schulte

Carola Schulte

Software-Architektin mit 25+ Jahren Erfahrung in System-Design, Legacy-Modernisierung und Enterprise-Architektur.

Beratung anfragen

Architektur-Review für Ihr System?

Ich bewerte, ob Event-Driven Architecture für Ihren Use Case passt – und wie Sie sie ohne Chaos einführen.

Architektur-Review anfragen