
# Compensazione del workflow

La compensazione consente di associare un gestore di rollback a un passaggio completato del workflow. Queuety memorizza uno snapshot dello stato pubblico al termine del passaggio, quindi esegue i gestori di compensazione in ordine inverso quando il workflow viene annullato o, facoltativamente, quando non riesce.

## Il metodo `compensate_with()`

Chiamate `compensate_with()` subito dopo il passaggio che deve annullare:

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

Se il workflow viene annullato in seguito, viene eseguito Queuety:

1. `CancelSubscriptionCompensation`
2. `DeleteRemoteUserCompensation`

## Gestori di compensazione

I gestori di compensazione implementano `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'] );
    }
}
```

Il gestore riceve lo stato pubblico del workflow così com'era subito dopo il completamento del passaggio compensato. Le chiavi interne riservate non sono incluse.

## Annullamento o errore

In caso di annullamento esplicito, le compensazioni dei passaggi completati vengono sempre eseguite prima del gestore facoltativo `on_cancel()`.

In caso di errore, le compensazioni vengono eseguite solo se le attivate esplicitamente:

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

Senza `compensate_on_failure()`, un workflow non riuscito conserva intatta la cronologia dei passaggi completati ed è possibile riprovare dal passaggio non riuscito.

Con `compensate_on_failure()`, le compensazioni dei passaggi completati vengono eseguite dopo che il workflow è contrassegnato come non riuscito. In seguito, `retry_workflow()` genera un'eccezione perché il workflow è già stato compensato.

## Tipi di passaggio supportati

È possibile associare la compensazione a:

- passaggi sequenziali `then()`
- gruppi `parallel()`
- passaggi `for_each()`
- passaggi `run_workflow()`
- passaggi di attesa `delay()`
- passaggi di segnale `wait_for_signal()`
- passaggi di dipendenza del workflow `wait_for_workflow()`

Per `run_workflow()` e `for_each()`, lo snapshot di compensazione viene registrato dopo che il lavoro annidato o ramificato è terminato del tutto.

## `on_cancel()` e compensazione

Usate `compensate_with()` per annullare in ordine inverso gli effetti collaterali dei passaggi completati.

Usate `on_cancel()` per un singolo hook di pulizia a livello di workflow, da eseguire ogni volta che il workflow viene annullato esplicitamente.
