Diese Seite richtet sich an Laravel 13 und prüft die Implementierung von Framework
v13.42.0. Sie setzt die Grundlagen der Queues und das Testen von Paketen voraus. Das folgende Kompatibilitätsdesign ist eine Richtlinie auf Paketseite. Laravel bietet keine Funktion, die eigene Payload-Formate automatisch migriert.Der Code ändert sich zwischen Speichern und Ausführen
Bei Objekt-Jobs führtQueue::createObjectPayload() serialize(clone $job) aus und legt das Ergebnis in data.command der Payload ab. Bei aktivierter Verschlüsselung findet davor dieselbe Serialisierung statt.
Auf Worker-Seite führt CallQueuedHandler::getCommand() unserialize() auf die gespeicherten Daten aus und übergibt den wiederhergestellten Job an den Dispatcher. Ein normales handle() läuft mit dem Code und dem Service-Container, die der Worker zu diesem Zeitpunkt hat.
Wenn Sie langlebige Worker nicht neu starten, läuft nach dem Update weiterhin der alte Code. Bei Rolling Deployments berücksichtigen Sie nicht nur „alte Dispatch-Seite mit neuem Worker“, sondern auch „neue Dispatch-Seite mit altem Worker“.
Beurteilen Sie das Umbenennen oder Entfernen einer Klasse nicht nur danach, ob Nutzer die Klasse direkt aufrufen. Wartende, verzögerte und auf Wiederholung wartende Jobs sowie die Aufbewahrungsdauer fehlgeschlagener Jobs gehören ebenfalls zur Kompatibilität.
Was SerializesModels speichert
Illuminate\Foundation\Queue\Queueable verwendet intern SerializesModels. Neben dem Ersetzen von Modellen stellt es __serialize() bereit, das die Properties des Jobs selbst auflistet, und __unserialize(), das die Werte zurückschreibt.
Werte, die dem deklarierten Standardwert entsprechen, werden nicht gespeichert
__serialize() schließt statische und nicht initialisierte Properties aus und lässt das Speichern auch aus, wenn der aktuelle Wert strikt gleich dem deklarierten Standardwert der Property ist.
'csv', enthält das gespeicherte Array kein format. Ändert ein späteres Release den deklarierten Standardwert auf 'json', kann 'json' auch für alte Jobs gelten. Das ist keine kosmetische Änderung des Standardwerts, sondern eine Änderung, wie alte Daten gelesen werden.
Der Konstruktor wird beim Wiederherstellen nicht erneut aufgerufen
__unserialize() weist Properties Werte zu, die in der aktuellen Klasse existieren und für die das gespeicherte Array einen Schlüssel hat. Eine Ergänzung oder Validierung von Eingaben über den Konstruktor findet nicht statt.
'csv' in diesem Beispiel ist der Standardwert eines Konstruktorarguments. Es ist nicht der deklarierte Standardwert der Property selbst und ergänzt den Wert daher nicht, wenn Sie eine alte Payload ohne format wiederherstellen. Bleibt eine neu hinzugefügte typisierte Property nicht initialisiert, schlägt ein späterer Zugriff fehl.
Außerdem ist der Schlüssel einer public Property ihr Name, der einer protected Property trägt ein Präfix für die Sichtbarkeit, und der einer private Property enthält zusätzlich den Klassennamen. Prüfen Sie auch eine Änderung von public zu private mit alten Payloads.
Bezeichner und ausführende Services trennen
Speichern Sie die für die Ausführung nötigen Bezeichner und Optionen im Job und injizieren Sie ausführende Services wie API-Clients oder Exporter inhandle(). Übergeben Sie einen Service an den Konstruktor und behalten ihn, kann auch sein interner Zustand gespeichert werden.
Das folgende Beispiel ergänzt einen gleichnamigen Job, der in der alten Version nur reportId hatte, um eine Formatwahl und eine Payload-Version. ReportExporter ist ein Contract, den das Paket bereitstellt und im Container bindet, mit export(string $reportId, string $format): void.
- Fehlt
formatin einer alten Payload, wird der deklarierte Standardwert'csv'verwendet. - Fehlt
payloadVersionin einer alten Payload, wird sie als deklarierter Standardwert1behandelt. - Bei neu erzeugten Jobs setzt der Konstruktor
payloadVersionauf2. Da der Wert vom deklarierten Standardwert abweicht, wird er gespeichert. - Ist
formateines neuen Jobs'json', wird auch dieser Wert gespeichert.'csv'wird ausgelassen, behalten Sie daher den deklarierten Standardwert'csv'künftig bei.
Übergebene Modelle von Snapshots unterscheiden
Speichern Sie ein Modell direkt in einer Property, ersetztSerializesAndRestoresModelIdentifiers es durch einen ModelIdentifier mit Klasse, ID, Verbindungsname, Namen geladener Relationen usw. Beim Wiederherstellen eines einzelnen Modells lädt es dieses mit firstOrFail() erneut aus der Datenbank und lädt die betreffenden Relationen.
Dies ist kein Mechanismus, der Attribute oder verknüpfte Daten zum Zeitpunkt des Dispatchens festhält.
- Wird der Datensatz vor der Ausführung aktualisiert, sieht das wiederhergestellte Modell den aktualisierten Inhalt.
- Vorab angewendete Einschränkungen auf Relationen werden nicht gespeichert, wenden Sie benötigte Bedingungen daher zur Ausführungszeit erneut an.
- Schließen Sie unnötige Relationen mit
withoutRelations()oder#[WithoutRelations]vom Speichern aus. - Wurde das Modell gelöscht, kann der Job bereits beim Wiederherstellen fehlschlagen, vor
handle().
#[DeleteWhenMissingModels] in Betracht ziehen. Das Paket sollte das Verwerfen jedoch nicht eigenmächtig zum Standard machen, sondern die Fehlerrichtlinie für Nutzer offenlegen. Übergeben Sie nur eine ID und suchen in handle(), steuern Sie Fehlschlag oder Überspringen dieser Suche selbst.
Benötigen Sie den Inhalt zum Zeitpunkt des Dispatchens, speichern Sie die nötigen Werte explizit oder legen Sie etwa eine unveränderliche Report-Anfrage in der Datenbank ab und übergeben deren ID. Unterscheiden Sie das bloße Übergeben eines Modells vom Speichern eines Snapshots von Geschäftsdaten.
Dispatchen nach dem Commit ist ein separates Problem
Wenn Sie Datensätze verarbeiten, die in einer Transaktion erstellt wurden, verhindern Sie, dass Worker sie vor dem Commit lesen.afterCommit() wartet mit dem Dispatchen bis zum Commit der offenen übergeordneten Transaktion und verwirft das Dispatchen bei einem Rollback. Es löst weder die Kompatibilität von Klassennamen und Properties noch die doppelte Ausführung externer Verarbeitung.
Fakes und Tests alter Payloads trennen
Ein normalesQueue::fake() zeichnet Dispatches auf. Ein erfolgreiches assertPushed() allein bestätigt weder Speichern und Wiederherstellen noch, dass die neue Version alte Payloads verarbeitet.
Einen Roundtrip mit der aktuellen Klasse können Sie so prüfen.
serializeAndRestore() führt serialize() und unserialize() innerhalb des Fakes aus. Es prüft jedoch weder das Dispatchen an ein echtes Backend noch die Ausführung durch einen Worker oder die Erzeugung durch alten Code.
Mit der alten Version erzeugte Fixtures aufbewahren
Solange das alteGenerateReport nur reportId hat, erzeugen Sie einen festen Job ohne echte Daten. Der folgende Vorbereitungscode wird einmal in der Testumgebung der alten Version ausgeführt. Legen Sie tests/fixtures/ vorher an.
TestCase in diesem Beispiel ist die Basisklasse, die Sie in der Testumgebung für Pakete einrichten. Übergeben Sie unserialize() nur selbst verwaltete Fixtures und niemals externe Eingaben.
Führen Sie außerdem handle() des wiederhergestellten alten Jobs mit einem Test-Exporter aus und prüfen Sie Ausgabe und gespeicherte Ergebnisse. Wenn Sie Modelle übergeben, prüfen Sie Aktualisierung, Löschung, Verbindung und erneutes Laden von Relationen separat, und auf der tatsächlich genutzten Queue-Verbindung auch das Dispatchen nach dem Commit und Wiederholungen.
Die lesende Seite vor dem Dispatchen des neuen Formats aktualisieren
1
Ein Release vorbereiten, das das alte Format lesen kann
Verteilen Sie zuerst Code, der den alten Klassennamen und die Interpretation alter Werte beibehält und zusätzlich das neue Format lesen kann. Prüfen Sie mit Fixtures nicht nur das Wiederherstellen, sondern auch das Verarbeitungsergebnis.
2
Sicherstellen, dass keine alten Worker übrig bleiben
Nehmen Sie den Austausch der Worker in das Deployment-Verfahren der nutzenden Anwendung auf. Bei einem normalen
queue:work fordert php artisan queue:restart das Beenden nach dem aktuellen Job an, und Sie prüfen, dass der Prozessmanager die neue Version startet. Für das Neustartsignal wird der Cache verwendet.3
Das Dispatchen des neuen Formats aktivieren
Schalten Sie erst auf das Dispatchen des neuen Formats um, wenn die lesende Seite es unterstützt, etwa per Feature-Flag. Beginnt schon die Verteilung desselben Codes mit dem Erzeugen des neuen Formats, brauchen Sie ein Verfahren, das es von alten Workern fernhält, etwa das Dispatchen pausieren oder alte und neue Queues mit passenden Workern trennen.
4
Den Zeitraum für Wiederholungen und Rollbacks weiter unterstützen
Prüfen Sie auch Wiederholungen fehlgeschlagener Jobs und Jobs, die später innerhalb von Chains dispatcht werden. Kehren Sie nach dem Speichern des neuen Formats zu altem Code zurück, prüfen Sie, ob der alte Code es lesen kann. Entfernen Sie die Unterstützung alter Klassen oder Formate nicht, nur weil die Queue einmal leer war.
queue:restart ist kein Befehl, der gespeicherte Payloads umschreibt. Nennen Sie in den Update-Hinweisen des Pakets die unterstützten Formate, den nötigen Austausch der Worker und die Bedingungen für das Abschaffen des alten Formats. Halten Sie dieselbe Änderung auch im Versionskompatibilitätsmanagement und im Changelog-Betrieb fest.
Herangezogene Primärquellen
- Offizielle Laravel-Dokumentation: Aufbau von Jobs und Dependency Injection
- Offizielle Laravel-Dokumentation: Dispatchen nach dem Commit
- Offizielle Laravel-Dokumentation: Worker und Deployment
- Offizielle Laravel-Dokumentation: Umgang mit gelöschten Modellen
- Queue: Erstellen von Objekt-Payloads
- Queueable: Aufbau des Job-Traits
- SerializesModels: Speichern und Wiederherstellen von Properties
- SerializesAndRestoresModelIdentifiers: Ersetzen und erneutes Laden von Modellen
- CallQueuedHandler: Wiederherstellen und Ausführen
- QueueFake: Speichern und Wiederherstellen im Fake
- Offizielle Laravel-Tests: Serialisierung von Modellen