
# Tööd

Tööd on Queuety-i põhiline tööühik. Saada töö järjekorda käsitleja nime ja koormaga ning tööline võtab selle ja käivitab taustal.

## Tööde järjekorda saatmine

```php
use Queuety\Queuety;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] );
```

`dispatch()` tagastab `PendingJob` koosturi. Enne automaatset järjekorda saatmist saad sellele valikuid aheldada:

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High )
    ->delay( 300 )
    ->max_attempts( 5 );
```

Töö ID kohe kättesaamiseks kutsu välja `->id()`:

```php
$job_id = Queuety::dispatch( 'send_email', $payload )->id();
```

## Käsitlejate kirjutamine

Käsitlejaks sobib iga klass, mis rakendab liidest `Queuety\Handler`:

```php
use Queuety\Handler;

class SendEmailHandler implements Handler {
    public function handle( array $payload ): void {
        wp_mail( $payload['to'], $payload['subject'], $payload['body'] );
    }

    public function config(): array {
        return [
            'queue'           => 'emails',
            'max_attempts'    => 5,
            'backoff'         => 'exponential',
        ];
    }
}
```

Meetod `handle()` saab töö koorma ja teeb töö ära. Meetod `config()` tagastab valikulised vaikeväärtused:

| Võti | Tüüp | Kirjeldus |
|---|---|---|
| `queue` | `string` | Järjekorra vaikenimi |
| `max_attempts` | `int` | Käivituste maksimaalne arv (sh esimene käivitus), enne kui töö viiakse ootele |
| `backoff` | `string` | Korduskatsete ooteaja strateegia: `exponential`, `linear` või `fixed` |
| `rate_limit` | `array` | Kiirusepiirang kujul `[max_executions, window_seconds]` |
| `concurrency_group` | `string` | Kallis käsitleja tööliste ühine vastuvõturühm |
| `concurrency_limit` | `int` | Selle rühma samaaegselt töödeldavate tööde maksimaalne arv |
| `cost_units` | `int` | Suhteline käivituskulu, mida kasutatakse kaalutud järjekorra või teenusepakkuja eelarvetes, töövoo eelarvetes ja vastuvõtuheuristikates |

Need ressursivõtmed kuuluvad Queuety laiema [Resource Management](https://queuety.inline0.dev/et/docs/features/resource-management) mudeli juurde, kus töö metaandmed, töövoo eelarved ja tööliste vastuvõtt toimivad koos.

Registreeri käsitleja:

```php
Queuety::register( 'send_email', SendEmailHandler::class );
```

Või kasutage automaatseks registreerimiseks [PHP atribuute](https://queuety.inline0.dev/et/docs/features/attributes).

## Prioriteet

Töödel on neli prioriteeditaset. Töötajad võtavad esmalt ette kõrgema prioriteediga tööd.

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'handler', $payload )->with_priority( Priority::Urgent );
```

| Tase | Väärtus | Kirjeldus |
|---|---|---|
| `Priority::Low` | `0` | Vaikimisi. Taustatöö, mis võib oodata. |
| `Priority::Normal` | `1` | Tavaline prioriteet. |
| `Priority::High` | `2` | Töödeldakse enne tasemeid Low ja Normal. |
| `Priority::Urgent` | `3` | Töödeldakse esimesena. Kasuta säästlikult. |

## Viivitus

Lükka töö edasi, et see muutuks kättesaadavaks alles määratud arvu sekundite möödudes:

```php
Queuety::dispatch( 'send_reminder', $payload )->delay( 3600 ); // 1 hour
```

Töö lisatakse kohe, kuid töötajad ei võta seda ette enne viivitusaja möödumist.

## Unikaalsed tööd

Väldi sama käsitleja ja andmete korduvat tööle saatmist. Kui sama käsitleja ja andmetega ootel või töötlemisel töö on juba olemas, tagastatakse uue loomise asemel olemasoleva töö ID.

```php
Queuety::dispatch( 'sync_inventory', [ 'sku' => 'ABC-123' ] )->unique();
```

## Töö sõltuvused

Töö võib sõltuda teisest tööst. Töötaja ei võta seda ette enne, kui sõltuv töö on lõpetatud.

```php
$first_id = Queuety::dispatch( 'import_data', $payload )->id();

Queuety::dispatch( 'send_notification', [ 'import_id' => $first_id ] )
    ->after( $first_id );
```

## Tööde paketina saatmine

Saada mitu tööd ühe mitmerealise INSERT-päringuga, et saavutada maksimaalne läbilaskevõime:

```php
$job_ids = Queuety::batch( [
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'a@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'b@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'c@example.com' ] ],
] );
```

Iga üksus võib sisaldada valikulisi võtmeid: `queue`, `priority`, `delay`, `max_attempts`.

## Uuesti proovimine ja viivitused

Kui töö käivitab erandi, paneb Queuety selle tagasi olekusse `pending` ja lisab sellele viivitusega `available_at`, kuni töö on käivitatud kokku `max_attempts` korda (esimene käivitus läheb arvesse, seega `max_attempts( 3 )` tähendab kuni kahte korduskatset). Iga korduskatse eelneva viivituse määrab viivituse strateegia, kus `attempt` tähistab äsja ebaõnnestunud käivituse numbrit:

| Strateegia | Viivituse valem | Näide (pärast käivitusi 1, 2, 3) |
|---|---|---|
| `exponential` | `2^attempt * 30` sekundit, ülempiiriga 3600 | 60s, 120s, 240s |
| `linear` | `attempt * 60` sekundit | 60s, 120s, 180s |
| `fixed` | `60` sekundit | 60s, 60s, 60s |

Määra globaalne vaikeväärtus asukohas `wp-config.php`:

```php
define( 'QUEUETY_RETRY_BACKOFF', 'linear' );
```

Või määra see iga käsitleja jaoks asukohas `config()`:

```php
public function config(): array {
    return [ 'backoff' => 'exponential', 'max_attempts' => 5 ];
}
```

Või iga tööle saatmise korral:

```php
Queuety::dispatch( 'handler', $payload )->max_attempts( 10 );
```

## Ootele paigutatud tööd

Kui kõik korduskatsed on ammendatud, märgitakse töö **maetud** olekusse. Maetud tööd jäävad andmebaasi kontrollimiseks alles, kuid neid ei töödelda. Saad neid käsitsi uuesti proovida. Käsitsi korduskatse viib töö uuesti olekusse `pending`, katsete arvu lähtestamata, nii et uuesti proovitud maetud töö käivitatakse veel ühe korra ja maetakse ebaõnnestumise korral taas:

```php
// Retry a specific buried job
Queuety::retry( $job_id );

// Retry all buried jobs
$count = Queuety::retry_buried();
```

Või CLI kaudu:

```bash
wp queuety retry 42
wp queuety retry-buried
```

## Ajalimiit

Queuety käivitab `pcntl_alarm()` maksimaalse käitusaja jaoks ning peaks tähtaja ületamisel katse nurjama, andes `TimeoutException`. Seda ajalõppu praegu ei jõustata ([#10](https://github.com/inline0/queuety/issues/10)): töötaja ei luba asünkroonseid signaale, mistõttu pika käitusajaga käsitleja töötab lõpuni; alarmi käivitumisel katkestatakse aga `sleep()`-is blokeeritud käsitleja ja märgitakse lõpetatuks. Määra ajalõpp globaalselt:

```php
define( 'QUEUETY_MAX_EXECUTION_TIME', 300 ); // 5 minutes
```

Tööd, mis on olekus `processing` kauem kui `QUEUETY_STALE_TIMEOUT` sekundit, taastatakse automaatselt:

```php
define( 'QUEUETY_STALE_TIMEOUT', 600 ); // 10 minutes
```

Taastage aegunud tööd käsitsi:

```bash
wp queuety recover
```

## Järjekorrad

Tööd saadetakse nimelisse järjekorda. Iga töötaja töötleb üht järjekorda või järjestatud järjekordade loendit (vt [Töötajad](https://queuety.inline0.dev/et/docs/features/workers#multi-queue-priorities)):

```php
Queuety::dispatch( 'handler', $payload )->on_queue( 'emails' );
```

```bash
wp queuety work --queue=emails
```

Vaikimisi järjekord on `default`. Töökoormuste eraldamiseks saad käitada iga järjekorra jaoks eraldi töötajaprotsessi.

## Kiiruse piiramine

Piira, kui sageli saab käsitlejat kindla ajavahemiku jooksul käivitada:

```php
Queuety::dispatch( 'call_api', $payload )
    ->rate_limit( 10, 60 ); // max 10 executions per 60 seconds
```

Lisateavet leiad jaotisest [Kiiruse piiramine](https://queuety.inline0.dev/et/docs/features/rate-limiting).

## Töölähetatavad tööklassid

Queuety v0.6.0 lisab klassipõhise töö API. Selle asemel et hoida käsitleja nimi, andmed ja käsitlejaklass eraldi, määrad ühe klassi, mis koondab kõik: andmed (tüübitud avalike atribuutidena), käivitamisloogika ja valikulise vahetarkvara.

### Liides `Contracts\Job`

```php
use Queuety\Contracts\Job;

interface Job {
    public function handle(): void;
}
```

Klass `Job` hoiab oma andmeid avalike atribuutidena ja käivitab oma loogika meetodis `handle()`. Eraldi meetodit `config()` ega käsitleja registreerimist pole vaja.

### Tunnusjoon `Dispatchable`

Lisa tunnusjoon `Dispatchable`, et lisada oma tööklassile staatiline meetod `dispatch()`:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class SendEmailJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $to,
        public readonly string $subject,
        public readonly string $body,
    ) {}

    public function handle(): void {
        wp_mail( $this->to, $this->subject, $this->body );
    }
}
```

Saada see tööle, edastades konstruktori argumendid:

```php
SendEmailJob::dispatch( 'user@example.com', 'Welcome!', 'Hello there.' )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High );
```

Tunnusjoon `Dispatchable` kutsub sisemiselt välja `Queuety::dispatch()`, nii et kõik valikud `PendingJob` (järjekord, prioriteet, viivitus, max_attempts, unique, after) toimivad täpselt samamoodi.

### Kuidas serialiseerimine toimib

Kui saadad tööklassi `Job` eksemplari tööle, eraldab Queuety `JobSerializer`-i abil täielikult kvalifitseeritud klassinime käsitlejana ja kõik avalikud atribuudid andmetena. Töötaja taastab eksemplari, sobitades andmete võtmed konstruktori parameetrite nimedega. Backed enumid serialiseeritakse nende aluseks olevaks väärtuseks ja taastatakse automaatselt.

### Töö omadused

Tööklassid võivad korduskatsete ja ajalõpu käitumise juhtimiseks deklareerida avalikke atribuute. Töötaja loeb need peegelduse abil ja rakendab automaatselt.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ImportProductsJob implements Job {
    use Dispatchable;

    public int $tries = 5;
    public int $timeout = 120;
    public array $backoff = [ 10, 30, 60, 120 ];
    public string $concurrency_group = 'imports';
    public int $concurrency_limit = 2;
    public int $cost_units = 4;

    public function __construct(
        public readonly int $supplier_id,
    ) {}

    public function handle(): void {
        // Import products...
    }
}
```

| Atribuut | Tüüp | Kirjeldus |
|---|---|---|
| `$tries` | `int` | Katsete maksimaalne arv enne töö maetamist. Tühistab tööle saatmise ajal määratud väärtuse `max_attempts`. |
| `$timeout` | `int` | Maksimaalne käitusaeg sekundites. Tühistab globaalse konstandi `QUEUETY_MAX_EXECUTION_TIME`. Vajab `pcntl`-i ja seda veel ei jõustata (vt [Ajalõpp](https://queuety.inline0.dev/et/docs/jobs#timeout)). |
| `$backoff` | `array` | Iga korduskatse viivituse väärtuste massiiv sekundites. Viimast väärtust kasutatakse ka järgmistel katsetel. |
| `$concurrency_group` | `string` | Kallite tööde jagatud töötajale lubamise rühm. |
| `$concurrency_limit` | `int` | Sellesse rühma kuuluvate samaaegsete `processing` tööde maksimumarv. Ilma `$concurrency_group`-ita eiratakse. |
| `$cost_units` | `int` | Suhteline käitamiskulu, mida kasutavad töövoo eelarved ja töötajale lubamise loogika. |

Massiiv `$backoff` määrab kasvavad korduskatsete viivitused. Näiteks `[10, 30, 60, 120]` tähendab, et enne esimest korduskatset oodatakse 10 sekundit, enne teist 30 sekundit, enne kolmandat 60 sekundit ja enne edasisi korduskatseid 120 sekundit.

Ressursiga seotud atribuudid on kasulikud, kui tööklassid esindavad eri kuluprofiile. Kiire vahemälu värskendamine ja suur teenusepakkuja päring võivad mõlemad olla „üks töö”, kuid töövoo eelarve või töötajale lubamise otsuse puhul ei peaks neid tingimata võrdselt arvestama, kui eesmärk on hoida protsess turvalistes piirides.

Täielikku mudelit, sealhulgas töövoo eelarveid ja töötajapoolset lubamist, kirjeldab [Ressursihaldus](https://queuety.inline0.dev/et/docs/features/resource-management).

### Ebaõnnestumise hook

Kui töö maetakse pärast kõigi korduskatsete ammendamist, kutsub Queuety töö eksemplaril meetodi `failed()` välja, kui see on määratud. Kasuta seda puhastuseks, teavitusteks või logimiseks.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ProcessPaymentJob implements Job {
    use Dispatchable;

    public int $tries = 3;

    public function __construct(
        public readonly int $order_id,
    ) {}

    public function handle(): void {
        // Process the payment...
    }

    public function failed( \Throwable $exception ): void {
        // Notify the team that payment processing failed permanently.
        error_log( "Payment failed for order {$this->order_id}: {$exception->getMessage()}" );
    }
}
```

Meetod `failed()` saab erandi, mis põhjustas lõpliku ebaõnnestumise. Töö eksemplar deserialiseeritakse algsete andmetega, seega on kõik avalikud atribuudid saadaval.

### Tingimuslik tööle saatmine

Tunnusjoon `Dispatchable` pakub tingimuslikuks tööle saatmiseks meetodeid `dispatch_if()` ja `dispatch_unless()`. Mõlemad võtavad esimeseks argumendiks tõeväärtustingimuse, millele järgnevad töö konstruktori argumendid.

```php
// Only dispatch if the user has opted in to notifications.
SendEmailJob::dispatch_if( $user->wants_notifications, $user->email, 'Update', $body );

// Dispatch unless the feature flag is disabled.
SyncInventoryJob::dispatch_unless( $maintenance_mode, $sku );
```

`dispatch_if()` saadab tööle, kui tingimus on `true`, ja tagastab `PendingJob`. Kui tingimus on `false`, tagastab see `null`.

`dispatch_unless()` saadab tööle, kui tingimus on `false`, ja tagastab `PendingJob`. Kui tingimus on `true`, tagastab see `null`.

Mõlemad meetodid toetavad tööle saatmisel aheldatavaid valikuid `PendingJob`:

```php
SendEmailJob::dispatch_if( $should_send, $to, $subject, $body )
    ?->on_queue( 'emails' )
    ?->with_priority( Priority::High );
```

### Sünkroonne tööle saatmine

Kasuta `Queuety::dispatch_sync()`-i, et käivitada töö kohe praeguses protsessis seda järjekorda lisamata. See on kasulik testimiseks ja tööde puhul, mis peavad käivituma samas protsessis.

```php
use Queuety\Queuety;

// With a Job instance
Queuety::dispatch_sync( new SendEmailJob( to: 'user@example.com', subject: 'Hello', body: 'Hi!' ) );

// With a handler name and payload
Queuety::dispatch_sync( 'send_email', [ 'to' => 'user@example.com' ] );
```

Töö meetodit `handle()` kutsutakse otse. Andmebaasi rida ei looda, töötajat ei kaasata ja vahevara ei rakendata.

### Tööde aheldamine

Tööde aheldamine käivitab tööd üksteise järel. Iga ahelas olev töö sõltub sellele eelneva töö edukast lõpetamisest. Kui mõni töö ebaõnnestub, jäävad järgnevad tööd ootele.

#### Kasutades `Queuety::chain()`

```php
use Queuety\Queuety;

$first_job_id = Queuety::chain( [
    new FetchDataJob( $url ),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->catch( ChainFailedHandler::class )
    ->dispatch();
```

`Queuety::chain()` tagastab `ChainBuilder`, mille aheldatavate meetoditega saab määrata järjekorra ja ebaõnnestumise käsitleja. Meetod `dispatch()` loob kõik tööd järjestikuste `depends_on` seostega ja tagastab esimese töö ID.

#### Kasutades `with_chain()`

Tunnusjoon `Dispatchable` pakub meetodit `with_chain()`. Praegu on see samaväärne meetodiga `Queuety::chain( $jobs )`: klassi, millel seda kutsutakse, ahelasse ei lisata ([#13](https://github.com/inline0/queuety/issues/13)), seega loetle massiivis ka esimene töö:

```php
FetchDataJob::with_chain( [
    new FetchDataJob(),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->dispatch();
```

#### Ahela ebaõnnestumise käsitlemine

Registreeri ebaõnnestumise käsitleja klass meetodiga `catch()`. Käsitlejaklassil peab olema meetod `handle()`. See kutsutakse välja, kui mõni töö ahelas püsivalt maetakse. Töötaja toetab valikuid `handle()`, `handle( Job $job )` ja `handle( Job $job, \Throwable $exception )`.

```php
class ChainFailedHandler {
    public function handle(): void {
        // Alert, clean up, etc.
    }
}
```

### Vahetarkvara lisamine

Tööklassid võivad määrata meetodi `middleware()`, mis tagastab käivitamist ümbritseva vahetarkvara:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;
use Queuety\Middleware\RateLimited;
use Queuety\Middleware\Timeout;

class CallOpenAIJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $prompt,
    ) {}

    public function handle(): void {
        // Call the API...
    }

    public function middleware(): array {
        return [
            new RateLimited( max: 60, window: 60 ),
            new Timeout( seconds: 120 ),
        ];
    }
}
```

Sisseehitatud vahetarkvara ja kohandatud vahetarkvara kirjutamise üksikasjad leiad jaotisest [Vahetarkvara](https://queuety.inline0.dev/et/docs/features/middleware).

### Käsitlejapõhiste ja klassipõhiste tööde võrdlus

| | Käsitlejapõhine (v0.1+) | Klassipõhine (v0.6+) |
|---|---|---|
| Tööle saatmine | `Queuety::dispatch( 'send_email', $payload )` | `SendEmailJob::dispatch( $to, $subject )` |
| Käsitleja | Eraldi klass, mis rakendab `Handler` | Sama klass rakendab `Contracts\Job` |
| Andmed | Tüübita `array $payload` | Tüübitud avalikud atribuudid |
| Registreerimine | `Queuety::register()` või `#[QueuetyHandler]` | Pole vajalik (kasutab FQCN-i) |
| Vahetarkvara | Pole saadaval | Valikuline meetod `middleware()` |
| Seadistus | Meetod `config()` | Töö atribuudid + ahel `PendingJob` |

Mõlemad lähenemisviisid on täielikult toetatud. Olemasolevad käsitlejapõhised tööd toimivad muudatusteta edasi. Samas projektis saad kasutada mõlemat stiili.

## Töö elutsükkel

```
dispatch() -> pending -> processing -> completed
                  |           |
                  |           +-> exception -> pending (retry after backoff)
                  |                       |
                  |                       +-> buried (max attempts reached)
                  |
                  +-> delayed (available_at in the future)
```

| Olek | Kirjeldus |
|---|---|
| `pending` | Ootab, et töötaja selle ette võtaks |
| `processing` | Praegu käivitatakse |
| `completed` | Edukalt lõpetatud |
| `failed` | Broneeritud; töötaja ei määra seda praegu. Ebaõnnestunud katse, mida uuesti proovitakse, läheb tagasi olekusse `pending` |
| `buried` | Kõik korduskatsed ammendatud |
