Zum Inhalt springen
DedicatedPHP Kontakt

Webhooks in falscher Reihenfolge in PHP ohne den Zustand zu korrumpieren

Entwerfen Sie eine PHP-Integration, die durch Validierung, Auditierung und Idempotenz gegen doppelte, verspätete und parallele Ereignisse robust ist.

Redaktionelles Diagramm doppelter und verspäteter Webhook-Ereignisse, die von einer PHP-Anwendung mit Zustandskontrolle verarbeitet werden

Webhooks in falscher Reihenfolge in PHP sind ein Konsistenzproblem, nicht nur ein Verbindungsproblem. Ein Anbieter kann eine Zustellung erneut senden, weil er keine gültige Antwort erhalten hat, eine Warteschlange kann eine Nachricht verzögern, oder zwei Ereignisse derselben Entität können unterschiedliche Wege nehmen. Wenn die Anwendung davon ausgeht, dass jedes Ereignis genau einmal und in Reihenfolge eintrifft, kann eine alte Bestätigung eine spätere Stornierung überschreiben oder eine Wiederholung eine irreversible Operation zweimal ausführen.

Die Grundregel ist einfach: Ein Webhook ist eine Benachrichtigung, dass sich in einem anderen System etwas geändert haben könnte. Er ist für sich genommen keine zuverlässige Anweisung, den lokalen Zustand ohne Prüfungen zu verändern. Das Design muss die empfangenen Nachweise bewahren, entscheiden, welche Ereignisse zulässig sind, und Änderungen idempotent sowie gemäß den Domänenregeln geordnet anwenden.

Empfang, Validierung und Anwendung auf die Domäne trennen

Empfang, Validierung und Anwendung auf die Domäne trennen — guía visual de DedicatedPHP

Der HTTP-Endpunkt sollte wenig und dies vorhersehbar tun. Seine Aufgabe besteht darin, die Anfrage entgegenzunehmen, sie zu prüfen, einen unveränderlichen Datensatz zu persistieren und innerhalb der vom Sender erwarteten Frist zu antworten. Die Verarbeitung, die Bestellungen, Abonnements, Bestand oder andere Geschäftsentitäten ändert, sollte danach erfolgen, üblicherweise durch einen asynchronen Prozess.

Die Trennung der Phasen verhindert, dass ein vorübergehender Ausfall einer internen API eine gültige Zustellung in einen mehrdeutigen Wiederholungsversuch verwandelt. Sie ermöglicht außerdem, die Verarbeitung fortzusetzen, ohne den Anbieter bitten zu müssen, alte Ereignisse erneut zu senden.

  1. Empfang: Header, unveränderten Body, Empfangszeitpunkt und identifizierte Herkunft erfassen.
  2. Eingabevalidierung: Signatur, Format, Größe, Inhaltstyp und Mindestfelder prüfen.
  3. Persistenz: Das Ereignis und seinen Anfangszustand in einer kurzen Transaktion speichern.
  4. Einreihung: Kennzeichnen, dass Arbeit ansteht, ohne von ihrer Verarbeitung innerhalb der HTTP-Antwort abhängig zu sein.
  5. Anwendung: Ein Worker interpretiert das Ereignis, ermittelt den erforderlichen Zustand und führt einen kontrollierten Geschäftsübergang aus.

Es ist wichtig, eine Zustellung von einem Ereignis zu unterscheiden. Dieselbe Zustellung kann wiederholt werden, und einige Anbieter vergeben für jeden Zustellversuch eine andere Kennung. Wenn eine stabile Ereigniskennung vorhanden ist, ist sie in der Regel die beste Grundlage für die Deduplizierung. Wenn keine vorhanden ist, muss ein Schlüssel aus Herkunft, externer Entität, Typ und einer Version oder einem Zeitstempel mit bekannter Bedeutung definiert werden.

Was für Auditierung und Wiederverarbeitung zu speichern ist

Eine Ereignistabelle sollte nicht nur das interpretierte JSON speichern. Bewahren Sie den ursprünglichen Body auf, denn eine Normalisierung vor dem Speichern kann Informationen entfernen, die zur Überprüfung einer Signatur, zur Untersuchung eines Vorfalls oder zur Anpassung eines späteren Parsers erforderlich sind.

Mindestens sollte der Datensatz Folgendes enthalten:

  • Herkunft oder Anbieter und Integrationsumgebung.
  • Externe Ereigniskennung und, falls vorhanden, Zustellkennung.
  • Ereignistyp, Kennung der externen Entität und Version, Sequenz oder Wirksamkeitsdatum.
  • Relevante Header und gegen Änderungen geschützte ursprüngliche Nutzlast.
  • Lokalen Empfangszeitpunkt und getrennt davon den vom Sender angegebenen Zeitstempel.
  • Kryptografischen Fingerabdruck der Nutzlast für Diagnose und ergänzende Deduplizierung.
  • Verarbeitungsstatus: empfangen, validiert, ausstehend, angewendet, ignoriert, fehlgeschlagen oder in Prüfung.
  • Anzahl der Versuche, zusammengefassten Fehler, Zeitpunkt des letzten Versuchs und Verweis auf die betroffene lokale Entität.

Eine Unique-Constraint auf (origen, external_event_id) löst Wiederholungen, wenn der Anbieter eine stabile ID bereitstellt. Führen Sie zuerst das Insert aus und behandeln Sie den Konflikt als bereits bekannte Zustellung, nicht als Geschäftsfehler. Die Antwort kann weiterhin erfolgreich sein, um Wiederholungsversuche zu stoppen.

Die Deduplizierung der Nachricht reicht jedoch nicht aus, um Idempotenz zu gewährleisten. Beispielsweise können zwei unterschiedliche Ereignisse dieselbe Bestätigung ausdrücken und beide versuchen, eine Buchung zu erstellen. Die Geschäftsoperation muss einen eigenen Schutz haben: einen Idempotenzschlüssel, eine Unique-Constraint auf die Wirkung oder einen Übergang, der prüft, ob das Ergebnis bereits vorhanden ist.

Authentizität validieren und die Eingabefläche begrenzen

Akzeptieren Sie einen Webhook nicht, weil er von einer erwarteten IP-Adresse kommt oder weil er ein Feld enthält, das wie ein Geheimnis aussieht. Wenn der Anbieter es ermöglicht, validieren Sie eine über den Roh-Body und einen Zeitstempel berechnete Signatur. Der Vergleich muss zeitkonstant erfolgen, und das Zeitfenster muss Replay-Angriffe begrenzen, wobei eine angemessene Zeitabweichung berücksichtigt wird.

Setzen Sie vor dem Persistieren operative Grenzen: maximale Body-Größe, Lesezeit, akzeptierte Formate und Mindestschema. Gültiges JSON ist nicht zwangsläufig ein gültiges Ereignis. Lehnen Sie unbekannte Typen ab, wenn es keine explizite Richtlinie gibt, sie zu archivieren, ohne Wirkungen auszulösen.

Signaturgeheimnisse müssen rotiert werden. Während einer Umstellung kann es notwendig sein, für einen begrenzten Zeitraum einen alten und einen neuen Schlüssel zu akzeptieren und zu protokollieren, welcher die Zustellung validiert hat. Nehmen Sie keine vollständigen Bodies, Tokens oder unnötigen personenbezogenen Daten in Anwendungslogs auf. Der Auditdatensatz muss Zugriffskontrollen und eine der Sensibilität der Daten entsprechende Aufbewahrungsrichtlinie haben.

Die logische Reihenfolge bestimmen, nicht auf die Netzreihenfolge vertrauen

Der Empfangszeitpunkt bestimmt nicht, was zuerst passiert ist. Auch ein in der Nutzlast enthaltenes Datum reicht nicht immer aus: Es kann ungenau sein, zur Erstellung des Ereignisses statt zum Übergang gehören oder von nicht synchronisierten Uhren beeinflusst werden. Das beste Signal ist eine monotone Version oder eine Sequenznummer pro Entität, die vom Quellsystem bereitgestellt wird.

Wenn eine Version vorhanden ist, speichern Sie die zuletzt angewendete Version in der lokalen Entität. Ein Worker kann ein Ereignis nur anwenden, wenn seine Version höher als die gespeicherte ist; eine gleiche Version zeigt eine Wiederholung an, und eine niedrigere ist ein verspätetes Ereignis. Bei Sequenzlücken erfinden Sie keinen Zwischenzustand: Markieren Sie die Entität zur Abstimmung oder fragen Sie die Quell-API ab, wenn diese API das führende System ist.

Wenn weder Sequenz noch Version vorhanden sind, müssen die Regeln zur Domäne gehören. Eine explizite Zustandsmaschine ist sicherer als das direkte Zuweisen eines empfangenen Textes. Beispielsweise könnte eine stornierte Entität verhindern, wieder in den Status bestätigt zu wechseln, außer über einen dokumentierten und autorisierten Übergang. Das Modell muss festlegen, was bei jeder Kombination aus aktuellem Zustand und eingehendem Ereignis zu tun ist.

if ($eventVersion <= $entity->lastExternalVersion) {
    markIgnored($event, 'version_no_mas_reciente');
    return;
}

applyAllowedTransition($entity, $event);
$entity->lastExternalVersion = $eventVersion;

Der Code veranschaulicht das Kriterium, ersetzt jedoch weder die Transaktion noch die Übergangsregeln. Bei Ereignissen ohne Version ist ein Datumsvergleich nur akzeptabel, wenn der Vertrag des Senders dessen Semantik und Genauigkeit garantiert.

Verspätete Ereignisse nach den Kosten einer Fehlentscheidung behandeln

Nicht alle verzögerten Ereignisse verdienen dieselbe Reaktion. Die Wahl zwischen Ignorieren, Protokollieren, Neuberechnen oder Kompensieren hängt davon ab, ob das Ereignis eine tatsächliche Verpflichtung ändern kann und welches System die Quelle der Wahrheit ist.

  • Ignorieren: geeignet für eine alte Version, deren Wirkung bereits in einem überprüfbaren späteren Zustand enthalten ist.
  • Protokollieren und alarmieren: nützlich, wenn die Sequenz inkonsistent ist oder Informationen fehlen, um ohne Eingriff zu entscheiden.
  • Neuberechnen: den aktuellen Zustand im externen System abfragen und das lokale Abbild aktualisieren, wenn die externe Quelle maßgeblich ist.
  • Kompensieren: eine nachvollziehbare Korrekturmaßnahme erstellen, wenn eine frühere Wirkung bereits Folgen hatte und nicht sicher gelöscht werden kann.

Betrachten Sie den hypothetischen Fall einer externen Operation. Eine Bestätigung mit Version 12 trifft ein, danach eine Stornierung mit Version 13 und später wird die Bestätigung 12 erneut versucht. Mit Versionskontrolle belebt die Wiederholung die Operation nicht wieder. Wenn die Stornierung zuerst eintrifft und das System weiß, dass Version 12 fehlt, kann es die Stornierung anwenden, wenn die Zustandsmaschine dies erlaubt, oder vor einer sensiblen Wirkung eine Abstimmung anfordern.

Interne Parallelität, Warteschlangen und Sperren pro Entität

Die asynchrone Verarbeitung verbessert die Reaktionsfähigkeit, führt jedoch interne Nebenläufigkeitsprobleme ein: Zwei Worker können denselben Zustand lesen, bevor einer von ihnen schreibt. Die Deduplizierung des Ereignisses verhindert diese Bedingung nicht.

Serialisieren Sie bei sensiblen Entitäten nach Schlüssel der externen oder lokalen Entität. Dies kann durch auf diesem Schlüssel basierende Warteschlangenpartitionen, eine sorgfältig gestaltete verteilte Sperre mit Ablaufzeit oder eine Zeilensperre innerhalb einer kurzen Transaktion erreicht werden. Eine weitere Option ist optimistische Kontrolle: nur aktualisieren, wenn die gespeicherte Version weiterhin die erwartete ist, und bei erkanntem Konflikt erneut versuchen.

Vermeiden Sie es, eine Transaktion offen zu halten, während Sie Remote-Dienste aufrufen. Reservieren oder lesen Sie zuerst den Zustand konsistent; führen Sie dann, wenn möglich, den Aufruf mit einem idempotenten Schlüssel aus; und speichern Sie schließlich das Ergebnis. Wenn der Prozess zwischen den Schritten fehlschlägt, muss ein Wiederholungsversuch eine ausstehende von einer bereits abgeschlossenen Operation unterscheiden können.

Betrieb, Beobachtbarkeit und Tests vor dem Go-live

Betrieb, Beobachtbarkeit und Tests vor dem Go-live — guía visual de DedicatedPHP

Ein Betriebsdashboard sollte anzeigen, wie viele Ereignisse ausstehend bleiben, wiederholt fehlschlagen, wegen ihres Alters ignoriert, wegen der Signatur abgelehnt werden und Sequenzlücken aufweisen. Messen Sie auch das Alter der Warteschlange und die Zeit vom Empfang bis zur Anwendung. Diese Signale ermöglichen es, eine beeinträchtigte Integration zu erkennen, bevor der Verzug zu einem Geschäftsproblem wird.

Bewahren Sie Wiederverarbeitungsmechanismen, die vom ursprünglichen Ereignis und von einer expliziten Version des Parsers oder Handlers ausgehen. Wiederverarbeitung bedeutet nicht, blind auszuführen: Begrenzen Sie den Umfang, protokollieren Sie, wer sie angefordert hat, und halten Sie dieselben Idempotenzgarantien aktiv.

Checkliste

  • Dasselbe Ereignis mehrfach senden, auch parallel.
  • Eine Stornierung vor der zugehörigen Bestätigung zustellen.
  • Ein altes Ereignis bis nach einem Ereignis mit höherer Version verzögern.
  • Lücken, unbekannte Typen, abgeschnittene Nutzlasten und ungültige Signaturen einführen.
  • Den Ausfall des Workers simulieren, nachdem eine externe Wirkung erzeugt wurde und bevor das Ereignis als angewendet markiert wird.
  • Überprüfen, dass zwei Worker für dieselbe Entität keinen unmöglichen Übergang erzeugen.
  • Prüfen, dass die Wiederverarbeitung die Auditierung bewahrt und keine Wirkungen dupliziert.

Die robuste Integration versucht nicht, das Netzwerk zur Zustellung in Reihenfolge zu zwingen. Sie gestaltet eine zuverlässige Grenze: Sie speichert jede überprüfbare Eingabe, wendet idempotente Geschäftsregeln an, verwendet eine logische Reihenfolge, wenn diese vorhanden ist, und stimmt ab, wenn sie den Zustand nicht sicher kennen kann.

Möchten Sie diese Ideen in Ihrem Projekt anwenden?Lass uns über deine PHP-Plattform sprechen.
Verwandten Dienst anzeigen