Advanced (Aktualisiert: 2.6.2026)

Event-Driven Architecture mit Claude Code: Praxisleitfaden

Event-Driven Design mit Claude Code: Contracts, Idempotenz, Retries, DLQ, Observability und typische Fehler.

Event-Driven Architecture mit Claude Code: Praxisleitfaden

Event-Driven Architecture klingt nach sauberer Entkopplung, kann aber schnell zu schwer nachvollziehbaren Produktionsproblemen führen. Wenn Claude Code nur den Auftrag bekommt, “alles event-driven zu machen”, ohne Event-Namen, Payload-Vertrag, Idempotenz, Retry-Regeln, Dead-Letter Queue und Logging-Grenzen, funktioniert vielleicht die Demo, aber nicht der erste Incident.

In diesem Leitfaden ist Claude Code Reviewer und Implementierungsassistent, nicht der unkritisch akzeptierte Architekt. Menschen entscheiden die fachlichen Grenzen. Claude Code prüft, ob Event-Namen präzise sind, ob Schema-Änderungen bestehende Consumer brechen, ob doppelte Zustellung sicher ist, ob Ordering-Annahmen realistisch sind, ob Replay möglich ist und ob Observability vom Producer bis zum Consumer reicht. Die Beispiele behandeln SaaS-Signup, Payment Webhook bis Fulfillment, Audit-Log-Stream und Notification Pipeline.

Die wichtigsten Begriffe

Event-Driven Architecture bedeutet: Ein Service veröffentlicht eine Tatsache, die bereits passiert ist, und andere Services reagieren darauf. Ein Event ist kein Befehl. com.claudecodelab.user.created.v1 heißt “ein Benutzer wurde erstellt”, nicht “erstelle einen Benutzer”. So muss der Producer nicht wissen, welche Consumer später reagieren.

Vier Begriffe reichen für den Start. Producer ist der Service, der das Event sendet. Consumer ist der Service, der es verarbeitet. Event bus oder queue ist der Transportweg. Schema ist der Vertrag für den Payload, also etwa dass userId ein nicht leerer String ist und email wie eine E-Mail aussieht. Mit diesen Begriffen kann Claude Code konkreter prüfen.

Als offizielle Referenzen eignen sich CloudEvents und die CloudEvents spec für ein gemeinsames Event-Envelope. In AWS-Projekten ist Amazon EventBridge eine praktische Referenz für Bus, Filter und Routing. Für Observability bieten die OpenTelemetry docs die gemeinsame Sprache für traces, metrics und logs.

Bitte Claude Code nicht, die Architektur frei zu erfinden. Gib ihm bestehende APIs, Datenbanktabellen, Webhooks und Recovery-Anforderungen. Lass es dann prüfen: Ist der Name eindeutig, ist der Payload rückwärtskompatibel, ist Duplicate Delivery ungefährlich, gibt es einen Replay-Plan, und lässt sich das Event über correlation id verfolgen?

Event Contract zuerst

Der Contract kommt vor dem Handler-Code. Ohne Contract hängen Consumer stillschweigend an Feldern, die der Producer heute zufällig sendet. Eine kleine Änderung kann Onboarding, Billing, Audit Logging und Notifications gleichzeitig brechen.

Dieses CloudEvents-nahe YAML ist ein Startpunkt für ein SaaS-User-Created-Event. type enthält Domain, Fakt und Version. idempotencykey verhindert doppelte Nebenwirkungen bei Duplicate Delivery. correlationid verbindet Logs und Traces derselben ursprünglichen Anfrage.

specversion: "1.0"
id: "evt_01JZ0YV8Y9N3A7Z7K6Y1G9X2Q4"
type: "com.claudecodelab.user.created.v1"
source: "/services/identity"
subject: "users/usr_123"
time: "2026-06-02T09:30:00Z"
datacontenttype: "application/json"
dataschema: "https://example.com/schemas/user-created.v1.json"
idempotencykey: "user.created:usr_123:2026-06-02"
correlationid: "req_7fc42b"
data:
  userId: "usr_123"
  email: "masa@example.com"
  plan: "starter"
  locale: "de-DE"

Der Payload bekommt ein eigenes JSON Schema. Wenn Claude Code Producer oder Consumer schreibt, sage explizit: nicht von Feldern außerhalb des Schemas abhängen, optionale Felder nicht ohne neue Version verpflichtend machen, Breaking Changes als v2 behandeln.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/user-created.v1.json",
  "title": "UserCreatedV1",
  "type": "object",
  "additionalProperties": false,
  "required": ["userId", "email", "plan", "locale"],
  "properties": {
    "userId": { "type": "string", "minLength": 1 },
    "email": { "type": "string", "format": "email" },
    "plan": { "type": "string", "enum": ["free", "starter", "pro"] },
    "locale": { "type": "string", "pattern": "^[a-z]{2}-[A-Z]{2}$" }
  }
}

Event-Namen sollten vergangene Fakten sein. user.create und sendEmail klingen wie Befehle. user.created, payment.authorized und invoice.finalized sind Fakten. user.updated wirkt bequem, zwingt aber Consumer dazu, im Payload nachzusehen, ob E-Mail, Plan oder Profil betroffen sind. Für wichtige Änderungen sind Namen wie user.email_changed.v1 oder subscription.plan_changed.v1 sauberer.

Den Flow sichtbar machen

Vor der Implementierung sollte Claude Code ein Mermaid-Diagramm erzeugen. Es zeigt schneller als Text, wo Retry, DLQ und versteckte synchrone Abhängigkeiten liegen.

flowchart LR
  A["Identity API<br/>producer"] --> B["Event bus<br/>filter and route"]
  B --> C["Onboarding consumer<br/>workspace setup"]
  B --> D["Email consumer<br/>welcome message"]
  B --> E["Audit consumer<br/>append-only log"]
  C --> F["Idempotency store"]
  D --> F
  C --> G["Dead-letter queue"]
  D --> G
  B --> H["OpenTelemetry<br/>traces metrics logs"]

Der zentrale Review-Punkt: Der Producer wartet nicht auf alle Consumer. Wenn die Signup-API erst antwortet, nachdem die Willkommensmail gesendet wurde, ist das keine echte Asynchronität. Dann versteckt das Event eine synchrone Abhängigkeit. Entweder wird diese Abhängigkeit als API-Vertrag sichtbar gemacht, oder UX und Betrieb werden auf Eventual Consistency ausgelegt.

Minimaler Node.js Consumer

Der folgende Consumer verarbeitet das User-Created-Event, erstellt Onboarding-Zustand, stellt eine Willkommensmail in die Queue, ignoriert exakte Duplikate und speichert Fehler in einer Dead-Letter Queue. Map dient nur der Lesbarkeit; produktiv gehört der Idempotency Store in Redis, DynamoDB, PostgreSQL oder einen vergleichbaren gemeinsamen Speicher.

const crypto = require("node:crypto");

const processedEvents = new Map();
const deadLetterQueue = [];

function payloadHash(payload) {
  return crypto.createHash("sha256").update(JSON.stringify(payload)).digest("hex");
}

function eventKey(event) {
  return event.idempotencykey || `${event.type}:${event.id}`;
}

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function withRetry(operation, options = {}) {
  const attempts = options.attempts ?? 3;
  const delayMs = options.delayMs ?? 250;
  let lastError;

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      if (attempt === attempts) break;
      await wait(delayMs * attempt);
    }
  }

  throw lastError;
}

async function handleUserCreated(event, services) {
  if (event.specversion !== "1.0") {
    throw new Error(`Unsupported CloudEvents version: ${event.specversion}`);
  }

  if (event.type !== "com.claudecodelab.user.created.v1") {
    throw new Error(`Unexpected event type: ${event.type}`);
  }

  const key = eventKey(event);
  const currentHash = payloadHash(event.data);
  const existing = processedEvents.get(key);

  if (existing?.status === "succeeded" && existing.payloadHash === currentHash) {
    return { status: "duplicate_ignored", key };
  }

  if (existing && existing.payloadHash !== currentHash) {
    throw new Error("Idempotency key reused with a different payload");
  }

  processedEvents.set(key, {
    status: "processing",
    payloadHash: currentHash,
    updatedAt: new Date().toISOString(),
  });

  try {
    await withRetry(() => services.createOnboardingWorkspace(event.data.userId), {
      attempts: 3,
      delayMs: 200,
    });

    await withRetry(
      () =>
        services.enqueueWelcomeEmail({
          userId: event.data.userId,
          email: event.data.email,
          correlationId: event.correlationid,
        }),
      { attempts: 3, delayMs: 200 },
    );

    processedEvents.set(key, {
      status: "succeeded",
      payloadHash: currentHash,
      updatedAt: new Date().toISOString(),
    });

    return { status: "processed", key };
  } catch (error) {
    processedEvents.set(key, {
      status: "failed",
      payloadHash: currentHash,
      updatedAt: new Date().toISOString(),
      errorMessage: error.message,
    });

    deadLetterQueue.push({
      key,
      event,
      failedAt: new Date().toISOString(),
      errorMessage: error.message,
    });

    throw error;
  }
}

const services = {
  async createOnboardingWorkspace(userId) {
    console.log("workspace ready", { userId });
  },
  async enqueueWelcomeEmail(message) {
    console.log("email queued", {
      userId: message.userId,
      correlationId: message.correlationId,
    });
  },
};

const exampleEvent = {
  specversion: "1.0",
  id: "evt_01JZ0YV8Y9N3A7Z7K6Y1G9X2Q4",
  type: "com.claudecodelab.user.created.v1",
  source: "/services/identity",
  time: "2026-06-02T09:30:00Z",
  idempotencykey: "user.created:usr_123:2026-06-02",
  correlationid: "req_7fc42b",
  data: {
    userId: "usr_123",
    email: "masa@example.com",
    plan: "starter",
    locale: "de-DE",
  },
};

handleUserCreated(exampleEvent, services)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

module.exports = { handleUserCreated, withRetry, deadLetterQueue };

Der Prompt an Claude Code muss konkret sein: erfolgreiche Events nicht erneut ausführen, dieselbe idempotency key mit anderem Payload ablehnen, temporäre Fehler retryen und endgültige Fehler in der DLQ behalten. “Retry hinzufügen” ist zu ungenau und kann doppelte Mails oder doppelte Berechtigungen erzeugen.

Vier praktische Use Cases

Use CaseEventsConsumerHauptrisiko
SaaS-Signup und Onboardinguser.created.v1, workspace.created.v1Initiale Settings, Mail, CRMSignup-API wartet auf alle Consumer
Payment Webhook zu Fulfillmentpayment.succeeded.v1, subscription.activated.v1Entitlements, Rechnung, SlackFehlende Signaturprüfung oder Idempotenz
Audit Log und Event Streamrole.changed.v1, api_key.revoked.v1Append-only log, Suche, SIEMPII landet in Langzeitlogs
Notification Pipelinecomment.mentioned.v1, report.ready.v1E-Mail, In-App, PushPreferences und Opt-out werden ignoriert

Payment Webhooks passen gut, sind aber riskant. Kombiniere diesen Artikel mit Webhook implementation with Claude Code. Für API-Verträge hilft Production API development with Claude Code. Für Event-Versionen gelten die Prinzipien aus API versioning with Claude Code.

Bei Audit Logs ist Security entscheidend. Logge nicht automatisch den gesamten Payload. Nutze Claude Code security audit und Claude Code security best practices, um erlaubte Felder zu definieren. Fehlerformate und Exceptions sollten zu error handling patterns passen.

Typische Fehler

Der erste Fehler ist ein vager Name. user.updated zwingt jeden Consumer, den Payload zu prüfen und eigene Bedingungen zu bauen.

Der zweite Fehler sind Breaking Changes im Payload. email entfernen, einen String in ein Objekt ändern oder optionale Felder verpflichtend machen kann unabhängig deployte Consumer brechen. Hinzufügen ist meist sicherer; Entfernen, Typwechsel und Bedeutungswechsel brauchen eine neue Version.

Der dritte Fehler ist fehlende Duplicate-Behandlung. Viele Event-Systeme liefern at-least-once: mindestens einmal, manchmal mehrfach. E-Mail, Payment, Entitlements und Punkte brauchen idempotency key und persistente Verarbeitungshistorie.

Der vierte Fehler ist eine versteckte synchrone Abhängigkeit. Wenn der Producer ein Event sendet und danach eine Consumer-Tabelle liest, bleibt die Kopplung bestehen.

Der fünfte Fehler ist kein Replay-Plan. Bei drei Stunden Consumer-Ausfall müssen Retention, Replay-Filter, Duplikatverhalten und Unterdrückung irreversibler Nebenwirkungen klar sein.

Der sechste Fehler ist schwache Observability. Logs brauchen event id, type, correlation id, consumer, retry count und DLQ-Grund. Metrics brauchen backlog age, failure rate, duplicate count und replay count.

Der siebte Fehler ist PII im Log. PII sind personenbezogene Daten wie E-Mail, Name, Adresse, Zahlungsdaten oder Tokens. Arbeite mit event id und userId, maskiere sensible Felder und definiere Retention.

Claude Code Review Template

Lass Claude Code zuerst reviewen und erst danach implementieren.

# Claude Code EDA review checklist

Scope:
- event contract: schemas/user-created.v1.json
- producer: services/identity
- consumers: onboarding, email, audit-log

Please review:
- Is the event name a past-tense fact?
- Is the payload change backward compatible for existing consumers?
- Is there an idempotency key, and does duplicate delivery avoid double side effects?
- Does any consumer call back into the producer synchronously?
- Are retry count, backoff, and dead-letter rules explicit?
- Can replay run without duplicate email, payment, or irreversible effects?
- Do logs avoid PII and secrets?
- Can OpenTelemetry show event id, correlation id, and consumer name?

Output:
- P0/P1/P2 risks
- Files that should change
- Tests that should be added
- Open decisions a human must make

Wenn Claude Code eine gefährliche Annahme findet, korrigiere zuerst die Grenze. Danach folgen schema, handler, tests und runbook in kleinen Schritten.

Runbook für den Betrieb

Event-Driven Systeme sind nur nützlich, wenn sie im Fehlerfall bedienbar bleiben. Lege dieses Runbook mit dem ersten Consumer an.

# Runbook: event backlog or DLQ growth

## Symptoms
- Queue age is over 5 minutes
- Dead-letter queue has more than 10 messages
- Consumer error rate is over 2 percent for 10 minutes

## First checks
1. Identify event type, consumer name, and correlation id.
2. Check whether the failure is validation, downstream timeout, or permission.
3. Confirm whether the producer is still publishing new events.
4. Stop replay if the event triggers email, payment, or irreversible side effects.

## Recovery
1. Fix the consumer or downstream dependency.
2. Replay a small batch with idempotency enabled.
3. Compare processed count, duplicate count, and DLQ count.
4. Resume normal processing.
5. Write the incident note with event ids, time range, and customer impact.

## Never do
- Do not edit payloads manually without recording the reason.
- Do not replay payment or email events without suppression rules.
- Do not paste full payloads with PII into chat or issue trackers.

Vor dem Merge ist eine letzte Frage nützlich: “Welcher Fehler kann mit diesem Runbook nicht behoben werden?” Das deckt oft fehlende Rechte, schema drift oder externe API-Annahmen auf.

Fazit und Beratung

Event-Driven Architecture verbessert Erweiterbarkeit nur, wenn der Event Contract wie eine öffentliche API behandelt wird. Namen, schema, versioning, idempotency, ordering, retries, dead-letter handling, replay und observability brauchen klare Entscheidungen. Claude Code ist stark, wenn es diese Entscheidungen prüft und kleine Änderungen gegen einen festen Vertrag implementiert.

ClaudeCodeLab unterstützt bei Claude Code Training, Event-Driven Design Reviews, Webhook/API Contracts, Audit Logs, Incident Runbooks und Team-Workflows. Wenn dein Team Webhooks absichern, Notifications in asynchrone Worker verschieben oder Claude Code Review Prompts standardisieren will, starte mit Claude Code training and consulting. Für Selbststudium gibt es die free cheat sheet und product templates.

Masa hat diesen Ablauf in einem kleinen SaaS-Prototyp getestet. Wenn Event Contract und idempotency key zuerst geschrieben wurden, waren die von Claude Code erzeugten Änderungen kleiner und leichter zu prüfen. In einem früheren Versuch mit nur user.updated begannen Notification- und Audit-Consumer auf Payload-Details zu verzweigen, und Replay blieb unklar. Nach getrennten Event-Namen und DLQ-Runbook war klar, welche Events ab welchem Zeitraum erneut verarbeitet werden und wie viele Datensätze zu erwarten sind.

#Claude Code #event-driven #architecture #CQRS #design patterns
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.