
# Компенсация workflow

Компенсация позволяет назначить обработчик отката для завершенного шага workflow. Queuety сохраняет снимок публичного состояния после завершения этого шага, а затем запускает обработчики компенсации в обратном порядке, когда workflow отменяют или, по желанию, при его сбое.

## Метод `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 ] );
```

Если позже workflow отменят, будет запущен 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'] );
    }
}
```

Обработчик получает публичное состояние workflow в том виде, в каком оно было сразу после завершения компенсируемого шага. Зарезервированные внутренние ключи не включаются.

## Отмена и сбой

При явной отмене компенсации завершенных шагов всегда запускаются перед необязательным обработчиком `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()` в завершенной истории workflow после сбоя остаются все шаги, и его можно повторить с шага, на котором произошел сбой.

С `compensate_on_failure()` компенсации завершенных шагов запускаются после того, как workflow отмечен как завершившийся сбоем. После этого вызов `retry_workflow()` выбрасывает исключение, поскольку компенсация workflow уже выполнена.

## Поддерживаемые типы шагов

Компенсацию можно назначить для:

- последовательных шагов `then()`
- групп `parallel()`
- шагов `for_each()`
- шагов `run_workflow()`
- шагов задержки `delay()`
- шагов ожидания сигнала `wait_for_signal()`
- шагов зависимости workflow `wait_for_workflow()`

Для `run_workflow()` и `for_each()` снимок для компенсации записывается после полного завершения вложенной или разветвленной работы.

## `on_cancel()` и компенсация

Используйте `compensate_with()`, чтобы отменять побочные эффекты завершенных шагов в обратном порядке.

Используйте `on_cancel()` для одной точки очистки на уровне workflow, которая должна запускаться при каждой явной отмене workflow.
