
# ワークフローの補償処理

補償処理では、完了したワークフローステップにロールバックハンドラーを設定できます。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()`を使います。
