Vai al contenuto principale
Fai in modo che i job accodati prima di un aggiornamento del pacchetto possano essere elaborati anche dopo. Ciò che la coda salva non è il codice da eseguire, ma il nome della classe del job e il suo stato. Oltre ai test unitari del nuovo codice, servono test che ripristinano con il nuovo codice lo stato salvato dal vecchio codice.
Questa pagina riguarda Laravel 13 e verifica l’implementazione di Framework v13.42.0. Presuppone le basi delle code e i test dei pacchetti. La progettazione della compatibilità descritta di seguito è una politica del pacchetto. Laravel non offre una funzione che migri automaticamente i tuoi formati di payload.

Il codice cambia tra salvataggio ed esecuzione

Per i job oggetto, Queue::createObjectPayload() esegue serialize(clone $job) e inserisce il risultato in data.command del payload. Con la cifratura attiva, la stessa serializzazione avviene prima. Lato worker, CallQueuedHandler::getCommand() applica unserialize() ai dati salvati e passa il job ripristinato al Dispatcher. Un normale handle() viene eseguito con il codice e il service container del worker in quel momento. Se non riavvii i worker di lunga durata, il vecchio codice continua a girare dopo l’aggiornamento. Con i rolling deploy, considera non solo “vecchio lato che accoda con nuovo worker”, ma anche “nuovo lato che accoda con vecchio worker”. Non valutare la ridenominazione o la rimozione di una classe solo in base al fatto che gli utenti la chiamino direttamente. Anche i job in attesa, ritardati o in attesa di nuovo tentativo, e il periodo di conservazione dei job falliti, fanno parte della compatibilità.

Cosa salva SerializesModels

Illuminate\Foundation\Queue\Queueable usa internamente SerializesModels. Oltre a sostituire i modelli, fornisce __serialize(), che elenca le proprietà del job stesso, e __unserialize(), che reimposta i valori.

I valori uguali al valore predefinito dichiarato non vengono salvati

__serialize() esclude le proprietà statiche e non inizializzate, e omette il salvataggio anche quando il valore attuale è strettamente uguale al valore predefinito dichiarato della proprietà.
Se questa proprietà resta 'csv', l’array salvato non contiene format. Se una release successiva cambia il valore predefinito dichiarato in 'json', 'json' può applicarsi anche ai vecchi job. Non è una modifica estetica del valore predefinito, ma un cambiamento nel modo in cui si leggono i vecchi dati.

Il costruttore non viene richiamato durante il ripristino

__unserialize() assegna i valori alle proprietà che esistono nella classe attuale e che hanno una chiave nell’array salvato. Non esegue alcun completamento o validazione dell’input tramite il costruttore.
Il 'csv' di questo esempio è il valore predefinito di un argomento del costruttore. Non è il valore predefinito dichiarato della proprietà stessa, quindi non completa il valore quando ripristini un vecchio payload senza format. Se una proprietà tipizzata aggiunta resta non inizializzata, un accesso successivo fallisce. Inoltre, la chiave di una proprietà public è il suo nome, quella di una protected ha un prefisso di visibilità e quella di una private include anche il nome della classe. Verifica con i vecchi payload anche un passaggio da public a private.
SerializesModels elenca le proprietà del job. Non dare per scontato che la stessa conversione avvenga ricorsivamente all’interno di DTO arbitrari o sui modelli annidati negli array. Se passi oggetti diversi dai modelli, anche le loro classi e i loro formati di salvataggio fanno parte della compatibilità.

Separare identificatori e servizi di esecuzione

Salva nel job gli identificatori e le opzioni necessari all’esecuzione, e inietta in handle() i servizi di esecuzione come client API o exporter. Se passi un servizio al costruttore e lo conservi, anche il suo stato interno potrebbe essere salvato. L’esempio seguente aggiunge la scelta del formato e una versione del payload a un job con lo stesso nome che nella vecchia versione aveva solo reportId. ReportExporter è un contratto che il pacchetto fornisce e registra nel container, con export(string $reportId, string $format): void.
Questo design separa il significato dei valori mancanti dai valori accodati di nuovo.
  • Se un vecchio payload non ha format, si usa il valore predefinito dichiarato 'csv'.
  • Se un vecchio payload non ha payloadVersion, viene trattato come il valore predefinito dichiarato 1.
  • Per i nuovi job, il costruttore imposta payloadVersion a 2. Poiché differisce dal valore predefinito dichiarato, questo valore viene salvato.
  • Se il format di un nuovo job è 'json', anche quel valore viene salvato. 'csv' viene omesso, quindi mantieni anche in futuro il valore predefinito dichiarato 'csv'.
Se in futuro vuoi cambiare solo il formato predefinito dei nuovi job, puoi modificare il valore predefinito dell’argomento del costruttore e l’assegnazione, valutandoli separatamente dal valore predefinito della proprietà usato per i vecchi dati. Gestisci la versione del payload non come numero di release del pacchetto, ma come generazione del formato di salvataggio e della sua interpretazione.
Aggiungere un numero di versione non basta a garantire la compatibilità. La nuova versione di questo esempio sa elaborare anche il vecchio formato, ma nulla garantisce che i vecchi worker comprendano il nuovo format o payloadVersion. Serve anche l’ordine di aggiornamento del lato che accoda e dei worker descritto più avanti.

Distinguere il passaggio di modelli dagli snapshot

Se salvi un modello direttamente in una proprietà, SerializesAndRestoresModelIdentifiers lo sostituisce con un ModelIdentifier contenente classe, ID, nome della connessione, nomi delle relazioni caricate e così via. Nel ripristino di un singolo modello, lo recupera di nuovo dal database con firstOrFail() e carica le relazioni interessate. Non è un meccanismo che fissa gli attributi o i dati correlati al momento dell’accodamento.
  • Se il record viene aggiornato prima dell’esecuzione, il modello ripristinato riflette il contenuto aggiornato.
  • I vincoli applicati in anticipo alle relazioni non vengono salvati, quindi riapplica le condizioni necessarie al momento dell’esecuzione.
  • Escludi dal salvataggio le relazioni non necessarie con withoutRelations() o #[WithoutRelations].
  • Se il modello è stato eliminato, il job può fallire nella fase di ripristino, prima del suo handle().
Se è accettabile scartare i job il cui modello è stato eliminato, puoi valutare l’attributo ufficiale #[DeleteWhenMissingModels]. Tuttavia il pacchetto non dovrebbe rendere lo scarto il comportamento predefinito di sua iniziativa, ma esporre la politica di errore agli utenti. Se passi solo un ID e lo cerchi in handle(), sei tu a controllare il fallimento o il salto di quella ricerca. Se ti serve il contenuto al momento dell’accodamento, salva esplicitamente i valori necessari, oppure registra nel database qualcosa come una richiesta di report immutabile e passa il suo ID. Distingui il semplice passaggio di un modello dal salvataggio di uno snapshot dei dati di business.

L’accodamento dopo il commit è un problema diverso

Quando elabori record creati all’interno di una transazione, impedisci ai worker di iniziare a leggerli prima del commit.
afterCommit() attende il commit della transazione padre aperta prima di accodare e scarta l’accodamento in caso di rollback. Non risolve la compatibilità di nomi di classe e proprietà né l’esecuzione duplicata di elaborazioni esterne.

Separare i fake dai test dei vecchi payload

Un normale Queue::fake() registra gli accodamenti. Un assertPushed() che passa non conferma da solo il salvataggio e il ripristino, né che la nuova versione elabori i vecchi payload. Puoi verificare un ciclo completo con la classe attuale così.
serializeAndRestore() esegue serialize() e unserialize() all’interno del fake. Tuttavia non verifica l’accodamento su un backend reale, l’esecuzione da parte di un worker né la generazione con il vecchio codice.

Conservare le fixture generate con la vecchia versione

Finché il vecchio GenerateReport ha solo reportId, genera un job fisso che non contiene dati reali. Il codice di preparazione seguente va eseguito una volta nell’ambiente di test della vecchia versione. Crea prima tests/fixtures/.
Metti la fixture sotto controllo di versione e caricala dai test della nuova versione. Rigenerarla con la nuova versione a ogni esecuzione non verificherebbe più la retrocompatibilità, quindi mantienila fissa.
Il TestCase di questo esempio è la classe base che prepari nell’ambiente di test del pacchetto. Passa a unserialize() solo fixture che gestisci tu, mai input esterni. Esegui inoltre l’handle() del vecchio job ripristinato con un exporter di test e verifica l’output e i risultati salvati. Se passi modelli, verifica separatamente aggiornamento, eliminazione, connessione e nuovo recupero delle relazioni e, sulla connessione di coda effettivamente usata, l’accodamento dopo il commit e i nuovi tentativi.

Aggiornare il lato di lettura prima di accodare il nuovo formato

1

Preparare una release in grado di leggere il vecchio formato

Distribuisci prima un codice che mantiene il vecchio nome di classe e l’interpretazione dei vecchi valori e che sa leggere anche il nuovo formato. Con le fixture verifica non solo il ripristino ma anche il risultato dell’elaborazione.
2

Verificare che non restino vecchi worker

Includi la sostituzione dei worker nella procedura di deploy dell’applicazione che usa il pacchetto. Con un normale queue:work, php artisan queue:restart chiede l’uscita dopo il job in corso, e verifichi che il process manager avvii la nuova versione. Il segnale di riavvio usa la cache.
3

Attivare l'accodamento del nuovo formato

Quando il lato di lettura lo supporta, passa all’accodamento del nuovo formato, ad esempio con un feature flag. Se la sola distribuzione dello stesso codice inizia già a generare il nuovo formato, serve una procedura che non lo consegni ai vecchi worker, come sospendere l’accodamento o separare code vecchie e nuove con i worker corrispondenti.
4

Mantenere il supporto per il periodo di nuovi tentativi e rollback

Controlla anche i nuovi tentativi dei job falliti e i job accodati in seguito all’interno delle chain. Se torni al vecchio codice dopo aver salvato il nuovo formato, verifica che il vecchio codice riesca a leggerlo. Non rimuovere il supporto di vecchie classi o vecchi formati solo perché la coda si è svuotata una volta.
queue:restart non è un comando che riscrive i payload salvati. Nelle note di aggiornamento del pacchetto indica i formati supportati, la sostituzione dei worker necessaria e le condizioni per dismettere il vecchio formato. Registra la stessa modifica anche nella gestione della compatibilità delle versioni e nella gestione del changelog.

Fonti primarie consultate

Ultima modifica il 12 novembre 2026