Перейти до вмісту

Машини станів

Машини станів і робочі процеси розв’язують різні задачі.

Робочий процес відповідає на запитання «яка робота виконується далі?». Машина станів відповідає на запитання «у якому стані перебуває цей об’єкт і які події можливі з цього стану?». Ця різниця важлива, коли ви створюєте не просто послідовність кроків, а довготривалу сутність, яка з часом реагує на зовнішні дані, наприклад сеанс агента, чат, цикл перевірки, заявку чи процес погодження.

Короткий приклад

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' ]
);

Ця машина починає в стані awaiting_user, переходить до planning після надходження повідомлення користувача, дає поставленій у чергу дії згенерувати planned, а потім очікує в стані awaiting_review, доки зовнішнє погодження не завершить сеанс. Головне тут не сам синтаксис коду, а те, що життєвий цикл явний, доступний для перегляду й керується подіями.

Як працює машина

Коли екземпляр машини диспетчеризується, Queuety зберігає назву поточного стану, відкриті дані стану, версію та хеш визначення, допустимі вхідні події для поточного стану й надійну хронологію подій.

Якщо для стану, до якого виконано перехід, визначено action(), Queuety додає до черги завдання для входу в цей стан. Ця дія може просто оновити відкритий стан і залишити машину в очікуванні наступної події або згенерувати _event і, за потреби, _event_payload, щоб негайно виконати наступний перехід. Якщо для стану не визначено ні дії, ні кінцевого статусу, машина просто очікує на зовнішні події, надіслані через Queuety::machine_event().

Як вибрати між робочими процесами й машинами станів

Використовуйте робочий процес, якщо виконання побудоване передусім на кроках: отримати дані, опрацювати кожен елемент, зачекати, узагальнити, завершити. Така модель добре підходить, коли є чіткі початок і кінець, а головне запитання — яку роботу виконувати далі.

Використовуйте машину станів, якщо допустима поведінка залежить від поточного етапу життєвого циклу. Вона краще підходить, коли події можуть надходити в різному порядку, сутність може існувати довго або потрібно явно переглядати життєвий цикл зі станами awaiting_user, planning, running_agents, awaiting_review і completed.

Як вони поєднуються

Ці два механізми найкраще працюють разом, а не конкурують між собою. Дія стану може запустити робочий процес, якщо перехід потребує складнішого виконання, а робочий процес після завершення роботи може надіслати подію машині. Так машина станів керує життєвим циклом, а робочі процеси виконують ресурсомісткі або складно структуровані завдання.

Структура конструктора

StateMachineBuilder навмисно має невеликий інтерфейс. initial() і state() визначають граф життєвого циклу. action() прив’язує роботу в черзі до одного стану. on() оголошує, які події можуть перевести машину в наступний стан. on_queue(), with_priority() і max_attempts() керують виконанням дій стану, а version() та idempotency_key() додають метадані для надійної диспетчеризації. dispatch() створює екземпляр машини.

Дії стану також можуть мати необов’язковий метод config(), який використовує ті самі ключі політик ресурсів, що й кроки та обробники робочих процесів. Це дає змогу включити ресурсоємний перехід стану до спільної групи паралельного виконання або призначити йому більшу вагу витрат, якщо дія споживає більше бюджету, ніж звичайний крок.

Докладніше про загальну модель ресурсів дивіться в розділі Керування ресурсами.

Контракти

Дії входу в стан реалізують 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 — це структуровані дані з визначення дії; вони порожні, якщо дію задано простою назвою класу. Queuety створює дії за допомогою new $class(), тому клас дії має мати конструктор без обов’язкових аргументів.

Дії стану також можуть визначати необов’язковий метод config() із concurrency_group, concurrency_limit та cost_units, якщо поставлена в чергу дія входу має враховуватися під час допуску робочих процесів, формування зваженої черги чи бюджетів постачальників та застосування інших політик ресурсів.

Умови переходу реалізують Queuety\Contracts\StateGuard:

PHP
namespace Queuety\Contracts;

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

API для перегляду

Використовуйте фасад, щоб переглядати запущені машини:

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 );

У машин станів немає команд WP-CLI: wp queuety workflow ... стосується лише робочих процесів. Щоб переглядати машини з командного рядка, використовуйте ці PHP API, наприклад через wp eval.

Ці API надають назву поточного стану, статус життєвого циклу, відкритий стан, доступні події, метадані визначення, повну хронологію переходів і нормалізований набір даних трасування для інтерфейсів налагоджувача. У рядках списку машин наведено версію та хеш визначення, а також ключ ідемпотентності, щоб екрани списків могли показувати версію вихідного коду й середовища виконання, не завантажуючи кожну машину окремо. Набір даних трасування містить згруповані стани, необроблені події, пов’язані завдання дій, журнали, посилання на артефакти й фрагменти, вхідні та вихідні дані обробників, стан до й після, контекст, час виконання та структуровані помилки.

Сеанси агентів

Машини станів особливо корисні для систем агентів, які більше схожі на сеанси, ніж на конвеєри. Сеанс, що проходить стани awaiting_user, planning, running_agents, awaiting_review і completed, зазвичай легше зрозуміти як явний життєвий цикл, ніж як одне дуже довге визначення робочого процесу з численними очікуваннями й переходами назад.

Див. також