
# 工作流补偿

补偿机制允许你为已完成的工作流步骤添加回滚处理程序。Queuety 会在该步骤完成时存储公开状态快照，然后在工作流取消时运行补偿处理程序，并可选择在工作流失败时运行。

## `compensate_with()` 方法

应撤销某个步骤时，请紧接着该步骤调用 `compensate_with()`：

```php
use Queuety\Queuety;

Queuety::workflow( 'provision_account' )
    ->then( CreateRemoteUserStep::class )
    ->compensate_with( DeleteRemoteUserCompensation::class )
    ->then( CreateSubscriptionStep::class )
    ->compensate_with( CancelSubscriptionCompensation::class )
    ->then( SendWelcomeEmailStep::class )
    ->dispatch( [ 'user_id' => 42 ] );
```

如果工作流之后被取消，Queuety 会运行：

1. `CancelSubscriptionCompensation`
2. `DeleteRemoteUserCompensation`

## 补偿处理程序

补偿处理程序实现 `Contracts\Compensation`：

```php
use Queuety\Contracts\Compensation;

final class DeleteRemoteUserCompensation implements Compensation {
    public function handle( array $state ): void {
        remote_api_delete_user( $state['remote_user_id'] );
    }
}
```

处理程序接收的是被补偿步骤完成后立即保存的公开工作流状态。不会包含保留的内部键。

## 取消与失败

显式取消时，已完成步骤的补偿处理程序始终会在可选的 `on_cancel()` 处理程序之前运行。

失败时，只有选择启用后才会运行补偿处理程序：

```php
Queuety::workflow( 'provision_account' )
    ->then( CreateRemoteUserStep::class )
    ->compensate_with( DeleteRemoteUserCompensation::class )
    ->then( CreateSubscriptionStep::class )
    ->compensate_with( CancelSubscriptionCompensation::class )
    ->then( ChargeCardStep::class )
    ->compensate_on_failure()
    ->dispatch( $payload );
```

如果没有 `compensate_on_failure()`，失败的工作流会保留已完成步骤的历史记录，并可从失败的步骤重试。

启用 `compensate_on_failure()` 后，工作流标记为失败之后会运行已完成步骤的补偿处理程序。随后，`retry_workflow()` 会抛出错误，因为工作流已经完成补偿。

## 支持的步骤类型

补偿处理程序可以添加到以下步骤：

- 顺序 `then()` 步骤
- `parallel()` 组
- `for_each()` 步骤
- `run_workflow()` 步骤
- `delay()` 延迟步骤
- `wait_for_signal()` 信号步骤
- `wait_for_workflow()` 工作流依赖步骤

对于 `run_workflow()` 和 `for_each()`，补偿快照会在嵌套工作或分支工作全部完成后记录。

## `on_cancel()` 与补偿的区别

使用 `compensate_with()` 按逆序撤销已完成步骤产生的副作用。

使用 `on_cancel()` 添加一个工作流级清理钩子，使其在工作流被显式取消时运行。
