0Pricing
PHP Academy · Lektion

Ereignisgesteuerte Workflows erstellen

Dienste durch Ereignisse und Idempotenz koordinieren

Ereignisgesteuerte Workflows erstellen ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des PHP Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Ereignisgesteuerte Workflows

Eine einzelne Nachricht ist einfach. Ein Workflow – „Bestellung aufgegeben → Bestand reservieren → Karte belasten → versenden → benachrichtigen“, der sich über mehrere Services erstreckt – ist der Punkt, an dem sich ereignisgesteuertes Design bewährt und bei naiver Umsetzung zum Problem wird.

In dieser Lektion geht es um Choreografie und Orchestrierung, das Outbox-Pattern für atomisches Publizieren, Sagas für verteiltes Rollback und die Idempotenz, die alles zusammenhält.

Choreografie vs. Orchestrierung

Zwei Möglichkeiten, mehrstufige Abläufe zu koordinieren:

  • Choreografie – jeder Service reagiert auf Ereignisse und gibt eigene Ereignisse aus; es gibt keine zentrale Steuerung. Die Kopplung ist lose, aber der Gesamtablauf bleibt implizit und lässt sich nur schwer nachvollziehen.
  • Orchestrierung – ein zentraler Koordinator weist jedem Service an, was als Nächstes zu tun ist. Der Ablauf ist klar ersichtlich und beobachtbar, aber der Orchestrator bildet einen Kopplungspunkt.

Faustregel: Choreografie für einfaches Fan-out, Orchestrierung für Abläufe mit vielen geordneten Schritten und sichtbarem Zustand.

Ereignisse vs. Befehle

Benennen Sie Ihre Nachrichten bewusst:

  • Ein Ereignis beschreibt eine Tatsache aus der Vergangenheit: OrderPlaced. Dem Publisher ist egal, wer zuhört.
  • Ein Befehl fordert eine künftige Aktion bei einem bestimmten Handler an: ChargeCard.

Ereignisse steuern die Choreografie, Befehle die Orchestrierung. Eine vermischte Terminologie (ein „Ereignis“, das insgeheim genau einen Handler erwartet) ist eine häufige Ursache für versteckte Kopplung.

<?php
final class OrderPlaced {
    public function __construct(
        public readonly string $orderId,
        public readonly string $customerId,
        public readonly int $amountCents,
        public readonly string $occurredAt,
    ) {}
}

$e = new OrderPlaced('o-42', 'c-7', 1990, gmdate('c'));
echo json_encode($e), "\n";

Das Problem des doppelten Schreibens

Der klassische Fehler: Ein Handler aktualisiert die Datenbank und veröffentlicht eine Nachricht als zwei getrennte Operationen. Stirbt der Prozess dazwischen, entsteht eine Inkonsistenz – die Datenbankzeile wurde geändert, aber kein Ereignis gesendet, oder umgekehrt.

<?php
// BROKEN: not atomic. A crash between the two lines corrupts state.
function placeOrder(PDO $db, $broker, array $o): void {
    $db->prepare('INSERT INTO orders ...')->execute($o);
    // <-- crash here = row exists but no event ever published
    $broker->publish('OrderPlaced', json_encode($o));
}

Die transaktionale Outbox

Die Lösung ist das Outbox-Pattern: Fügen Sie innerhalb derselben DB-Transaktion, in der Sie Ihre Daten ändern, das Ereignis in eine outbox-Tabelle ein. Ein separater Relay-Prozess liest unveröffentlichte Zeilen und sendet sie an den Broker. Ein atomarer Commit, kein doppeltes Schreiben.

<?php
function placeOrder(PDO $db, array $o): void {
    $db->beginTransaction();
    $db->prepare('INSERT INTO orders (id, total) VALUES (?, ?)')
       ->execute([$o['id'], $o['total']]);
    // Same transaction -> atomic with the business write
    $db->prepare('INSERT INTO outbox (id, type, payload) VALUES (?, ?, ?)')
       ->execute([bin2hex(random_bytes(8)), 'OrderPlaced', json_encode($o)]);
    $db->commit();
}

Das Relay (Publisher)

Ein Worker fragt die Outbox ab (oder verfolgt das Änderungsprotokoll der DB per CDC), veröffentlicht jede Zeile und markiert sie anschließend als gesendet. Da das Relay nach dem Publizieren, aber vor der Markierung abstürzen kann, arbeitet es selbst mindestens einmal (at-least-once) – das ist in Ordnung, weil Consumer idempotent sind.

<?php
function relayOutbox(PDO $db, $broker): void {
    $rows = $db->query(
        'SELECT id, type, payload FROM outbox
         WHERE published_at IS NULL ORDER BY created_at LIMIT 100'
    )->fetchAll(PDO::FETCH_ASSOC);

    foreach ($rows as $r) {
        $broker->publish($r['type'], $r['payload'], messageId: $r['id']);
        $db->prepare('UPDATE outbox SET published_at = now() WHERE id = ?')
           ->execute([$r['id']]);
    }
}

Idempotente Consumer (erneut)

Da sowohl das Relay als auch der Broker mindestens einmal zustellen, werden nachgelagerte Handler Duplikate sehen. Jeder Consumer speichert die ID der bereits verarbeiteten Nachricht und überspringt Wiederholungen – dieselbe Dedup-Schranke, die Sie zuvor kennengelernt haben, jetzt pro Service angewendet.

<?php
function onOrderPlaced(PDO $db, string $messageId, array $data): void {
    $db->beginTransaction();
    try {
        $db->prepare('INSERT INTO inbox (message_id) VALUES (?)')
           ->execute([$messageId]); // unique index = dedup
    } catch (PDOException $e) {
        $db->rollBack();
        return; // already handled this message
    }
    reserveStock($data['orderId']);
    $db->commit();
}
function reserveStock(string $id): void {}

Sagas: Verteiltes Rollback

Sie können keine einzelne ACID-Transaktion über mehrere Services hinweg öffnen. Eine Saga bildet einen lang laufenden Ablauf als Folge lokaler Transaktionen ab, von denen jede eine kompensierende Aktion besitzt, die sie rückgängig macht. Schlägt Schritt 3 fehl, führen Sie die Kompensationen für die Schritte 2 und 1 in umgekehrter Reihenfolge aus.

Beispiel: Die Zahlung schlägt fehl, nachdem der Bestand reserviert wurde → geben Sie zur Kompensation ReleaseStock aus. Es gibt kein automatisches Rollback – Sie müssen für jeden Schritt die Rückgängigmachung entwerfen.

Eine orchestrierte Saga

Ein Orchestrator steuert die Saga: Bei Erfolg führt er sie weiter und löst bei Fehlern die Kompensationen aus. Speichern Sie den Zustand der Saga, damit sie nach einem Absturz fortgesetzt werden kann.

<?php
function handleStepResult(array $saga, string $step, bool $ok, $bus): array {
    if ($ok) {
        $next = ['reserveStock' => 'chargeCard', 'chargeCard' => 'ship'][$step] ?? null;
        if ($next) { $bus->send($next, $saga['orderId']); $saga['state'] = $next; }
        else { $saga['state'] = 'completed'; }
    } else {
        // Run compensations in reverse for whatever already succeeded
        foreach (array_reverse($saga['done']) as $s) {
            $bus->send('compensate.' . $s, $saga['orderId']);
        }
        $saga['state'] = 'compensating';
    }
    return $saga;
}

Timeouts in langen Abläufen

Ein Saga-Schritt kann schlicht nie zurückmelden – der Zahlungsdienst ist nicht verfügbar oder eine menschliche Genehmigung bleibt aus. Ohne Timeout hängt die Saga für immer und hält Reservierungen. Speichern Sie pro Schritt eine Deadline; ein Scheduler sucht nach überfälligen Sagas und löst den Fehler- bzw. Kompensationspfad aus.

<?php
function reapTimedOutSagas(PDO $db, $bus): void {
    $rows = $db->query(
        "SELECT order_id, state FROM sagas
         WHERE state NOT IN ('completed','compensating')
           AND deadline_at < now()"
    )->fetchAll(PDO::FETCH_ASSOC);

    foreach ($rows as $r) {
        echo "Saga {$r['order_id']} timed out at step {$r['state']}\n";
        $bus->send('saga.compensate', $r['order_id']); // trigger rollback
    }
}

Versionierung und Observability

Workflows laufen über Jahre; Ereignisse müssen sicher weiterentwickelt werden:

  • Fügen Sie jedem Ereignis eine version (oder ein Schema) hinzu; Konsumenten müssen unbekannte neue Felder tolerieren und dürfen niemals das Vorhandensein eines Feldes voraussetzen.
  • Bevorzugen Sie additive Änderungen; verwenden Sie die Bedeutung eines vorhandenen Feldes niemals für einen anderen Zweck.
  • Führen Sie eine Korrelations-ID durch jede Nachricht, damit Sie eine Geschäftstransaktion über alle Services hinweg in Ihren Logs und Traces verfolgen können.

Ohne Korrelations-IDs ist das Debuggen eines choreografierten Ablaufs über fünf Services hinweg nahezu unmöglich.

<?php
$envelope = [
    'type'          => 'OrderPlaced',
    'version'       => 2,
    'correlationId' => $incoming['correlationId'] ?? bin2hex(random_bytes(8)),
    'occurredAt'    => gmdate('c'),
    'data'          => ['orderId' => 'o-42'],
];
echo json_encode($envelope, JSON_PRETTY_PRINT), "\n";

Kurztest

Vermeidung des Dual-Write-Problems.

Zusammenfassung

Sie können nun zuverlässige ereignisgesteuerte Workflows entwerfen:

  • Wählen Sie je nach Komplexität des Ablaufs Choreografie (Ereignisse) oder Orchestrierung (Befehle).
  • Lösen Sie das Dual-Write-Problem mit der transaktionalen Outbox und einem Relay.
  • Machen Sie jeden Konsumenten mithilfe eines Inbox-/Deduplizierungsschlüssels idempotent.
  • Verwenden Sie Sagas mit kompensierenden Aktionen für verteilte Rollbacks.
  • Versionieren Sie Ereignisse additiv und führen Sie eine Korrelations-ID mit, um die Nachverfolgbarkeit sicherzustellen.

Diese Muster machen aus lose gekoppelten Nachrichten zuverlässige und beobachtbare Geschäftsprozesse.

Häufig gestellte Fragen

Ist die Lektion „Ereignisgesteuerte Workflows erstellen“ kostenlos?

Ja — der vollständige Text von „Ereignisgesteuerte Workflows erstellen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des PHP Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Ereignisgesteuerte Workflows erstellen“?

Dienste durch Ereignisse und Idempotenz koordinieren Du übst PHP Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um PHP Academy zu starten?

Keine Vorkenntnisse erforderlich. PHP Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „Ereignisgesteuerte Workflows erstellen“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser PHP Academy-Lektion Code schreiben und ausführen?

Ja. Jede PHP Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Warum asynchrone Nachrichtenübermittlung
  2. Mit RabbitMQ in PHP arbeiten
  3. Apache Kafka mit PHP
  4. Ereignisgesteuerte Workflows erstellen
← Zurück zu PHP Academy