跳转到正文

状态机

状态机和工作流解决的是不同的问题。

工作流回答“接下来运行什么任务?”,状态机回答“这个对象处于什么状态?从当前状态可以接收哪些事件?”。当你构建的对象不只是按顺序执行一系列步骤,而是会随着时间推移响应外部输入、长期存在时,这一区别就很重要,例如代理会话、聊天线程、审核生命周期、工单或审批流程。

快速示例

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

使用 facade 检查正在运行的状态机:

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 会提供当前状态名称、生命周期状态、公共状态、可用事件、定义元数据、完整的转换时间线,以及供调试器 UI 使用的标准化跟踪数据包。状态机列表行包含定义版本、定义哈希和幂等键,因此列表页面无需逐个加载状态机,也能显示源代码/运行时版本。跟踪数据包包含分组后的状态、原始事件、关联的操作任务、日志、构件引用、分块引用、处理器输入/输出、转换前后的状态、上下文、计时信息和结构化错误。

代理会话

对于行为更像会话而非流水线的代理系统,状态机尤其有用。依次经过 awaiting_user、planning、running_agents、awaiting_review 和 completed 的会话,通常用明确的生命周期来描述,更容易理解;若将其写成一个包含大量等待和回跳的超长工作流定义,则不易理解。

另请参阅