Vai al contenuto

Macchine a stati

Le macchine a stati e i workflow risolvono problemi diversi.

Un workflow risponde alla domanda «quale attività viene eseguita dopo?». Una macchina a stati risponde a «in quale stato si trova questo elemento e quali eventi sono validi a partire da qui?». La differenza conta quando ciò che state sviluppando non è solo una sequenza di passaggi, ma un'entità di lunga durata che reagisce nel tempo a input esterni, come una sessione di un agente, una conversazione, il ciclo di revisione, un ticket o un flusso di approvazione.

Esempio rapido

PHP
use Queuety\Enums\StateMachineStatus;
use Queuety\Queuety;

$machine_id = Queuety::machine( 'agent_session' )
    ->version( 'agent-session.v1' )
    ->state( 'awaiting_user' )
    ->on( 'user_message', 'planning' )
    ->state( 'planning' )
    ->action( PlanSessionAction::class )
    ->on( 'planned', 'awaiting_review' )
    ->state( 'awaiting_review' )
    ->on( 'approve', 'completed', ReviewApprovedGuard::class )
    ->on( 'reject', 'awaiting_user' )
    ->state( 'completed', StateMachineStatus::Completed )
    ->dispatch( [ 'thread_id' => 42 ] );

Queuety::machine_event(
    $machine_id,
    'user_message',
    [ 'message' => 'Find competitors for this product' ]
);

Queuety::machine_event(
    $machine_id,
    'approve',
    [ 'approved' => true, 'reviewer' => 'editor@example.com' ]
);

Questa macchina inizia in awaiting_user, passa a planning quando arriva un messaggio dell'utente, consente all'azione accodata di emettere planned e poi resta in attesa in awaiting_review finché un'approvazione esterna non completa la sessione. Il punto fondamentale non è la sintassi del codice in sé: è che il ciclo di vita è esplicito, ispezionabile e basato su eventi.

Esecuzione di una macchina

Quando viene inviato un dispatch per un'istanza della macchina, Queuety rende persistenti il nome dello stato corrente, il payload pubblico dello stato, la versione e l'hash della definizione, gli eventi in ingresso validi per lo stato corrente e una cronologia persistente degli eventi.

Se lo stato in cui si entra ha un action(), Queuety accoda un job di ingresso nello stato. L'azione può limitarsi ad aggiornare lo stato pubblico e lasciare la macchina in attesa del prossimo evento, oppure può emettere _event e, facoltativamente, _event_payload per eseguire subito la transizione successiva. Se lo stato non ha un'azione e non è terminale, la macchina attende semplicemente eventi esterni inviati con Queuety::machine_event().

Scegliere tra workflow e macchine a stati

Usate un workflow quando l'esecuzione è fondamentalmente organizzata in passaggi: recuperare dati, attendere per ogni attività, sintetizzare e terminare. Questo modello è adatto quando c'è un inizio e una fine chiari e la domanda principale è quale attività eseguire dopo.

Usate una macchina a stati quando il comportamento valido dipende dalla fase corrente del ciclo di vita. È la scelta migliore quando gli eventi possono arrivare in ordini diversi, quando l'entità può esistere a lungo o quando avete bisogno di ispezionare esplicitamente il ciclo di vita, con stati come awaiting_user, planning, running_agents, awaiting_review e completed.

Come collaborano i due modelli

Le due primitive funzionano meglio insieme che in competizione. Un'azione di stato può avviare un workflow quando una transizione richiede un'esecuzione più impegnativa, mentre un workflow può inviare un evento alla macchina quando l'attività è terminata. In pratica, la macchina a stati governa il ciclo di vita e i workflow eseguono le attività più costose o strutturate.

Struttura del builder

StateMachineBuilder resta volutamente essenziale. initial() e state() definiscono il grafo del ciclo di vita. action() associa il lavoro accodato a uno stato. on() dichiara quali eventi possono portare la macchina allo stato successivo. on_queue(), with_priority() e max_attempts() controllano l'esecuzione delle azioni di stato, mentre version() e idempotency_key() associano metadati persistenti al dispatch. dispatch() crea l'istanza della macchina.

Le azioni di stato possono esporre anche un metodo facoltativo config(), che usa le stesse chiavi delle politiche delle risorse impiegate nei passaggi e negli handler dei workflow. In questo modo potete inserire una transizione di stato costosa in un gruppo di concorrenza condiviso o assegnarle un peso di costo maggiore quando consuma più risorse di un passaggio normale.

Il modello più ampio di gestione delle risorse è descritto in Gestione delle risorse.

Contratti

Le azioni di ingresso nello stato implementano Queuety\Contracts\StateAction:

PHP
namespace Queuety\Contracts;

interface StateAction {
    public function handle( array $state, ?string $event = null, array $event_payload = [], array $payload = [] ): array|string;
}

$payload è il payload strutturato della definizione dell'azione; è vuoto quando l'azione è indicata come semplice nome di classe. Queuety crea le azioni con new $class(), quindi la classe dell'azione deve avere un costruttore senza argomenti obbligatori.

Le azioni di stato possono definire anche un metodo facoltativo config() con concurrency_group, concurrency_limit e cost_units, quando l'azione di ingresso accodata deve partecipare all'ammissione nei worker, ai limiti delle code ponderate o dei provider e ad altre politiche delle risorse.

Le guardie di transizione implementano Queuety\Contracts\StateGuard:

PHP
namespace Queuety\Contracts;

interface StateGuard {
    public function allows( array $state, array $event_payload, string $event, array $payload = [] ): bool;
}

API di ispezione

Usate la facade per ispezionare le macchine in esecuzione:

PHP
$status   = Queuety::machine_status( $machine_id );
$machines = Queuety::list_machines();
$events   = Queuety::machine_timeline( $machine_id );
$trace    = Queuety::machine_trace( $machine_id );
$state    = Queuety::machine_state_at( $machine_id, $event_id );

Le macchine a stati non hanno comandi WP-CLI: wp queuety workflow ... riguarda solo i workflow. Per ispezionare le macchine dalla riga di comando, usate queste API PHP, ad esempio tramite wp eval.

Queste API espongono il nome dello stato corrente, lo stato del ciclo di vita, lo stato pubblico, gli eventi disponibili, i metadati della definizione, la cronologia completa delle transizioni e un bundle di tracce normalizzato per le interfacce del debugger. Le righe degli elenchi di macchine includono la versione della definizione, il suo hash e la chiave di idempotenza, così le schermate degli elenchi possono mostrare la versione di origine o di runtime senza caricare ogni macchina singolarmente. Il bundle di tracce include stati raggruppati, eventi grezzi, job di azione correlati, log, riferimenti agli artefatti, riferimenti ai blocchi, input e output degli handler, stato precedente e successivo, contesto, tempi ed errori strutturati.

Sessioni degli agenti

Le macchine a stati sono particolarmente utili per i sistemi di agenti che si comportano più come sessioni che come pipeline. Una sessione che passa per awaiting_user, planning, running_agents, awaiting_review e completed è di solito più facile da comprendere come ciclo di vita esplicito che come un'unica definizione di workflow molto lunga, con molte attese e diramazioni all'indietro.

Vedi anche