
# Työt

Työt ovat Queuety-järjestelmän perusyksikkö. Lähetä työ suoritettavaksi käsittelijän nimellä ja hyötykuormalla, niin työntekijä poimii ja suorittaa sen taustalla.

## Töiden lähettäminen

```php
use Queuety\Queuety;

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

`dispatch()` palauttaa `PendingJob`-rakentajan. Voit ketjuttaa siihen asetuksia ennen kuin se lähettää työn automaattisesti:

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

Saat työn tunnuksen heti kutsumalla toimintoa `->id()`:

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

## Käsittelijöiden kirjoittaminen

Käsittelijä on mikä tahansa luokka, joka toteuttaa `Queuety\Handler`-rajapinnan:

```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',
        ];
    }
}
```

Metodi `handle()` vastaanottaa työn hyötykuorman ja suorittaa työn. Metodi `config()` palauttaa valinnaiset oletusarvot:

| Avain | Tyyppi | Kuvaus |
|---|---|---|
| `queue` | `string` | Oletusjonon nimi |
| `max_attempts` | `int` | Enimmäissuorituskerrat, ensimmäinen mukaan lukien, ennen kuin työ haudataan |
| `backoff` | `string` | Uudelleenyritysten viivestrategia: `exponential`, `linear` tai `fixed` |
| `rate_limit` | `array` | Nopeusrajoitus muodossa `[max_executions, window_seconds]` |
| `concurrency_group` | `string` | Kalliiden käsittelijöiden yhteinen työntekijöiden hyväksyntäryhmä |
| `concurrency_limit` | `int` | Ryhmän yhtäaikaisesti käsiteltävien töiden enimmäismäärä |
| `cost_units` | `int` | Suorituksen suhteellinen kustannus, jota käytetään painotettujen jono- tai palveluntarjoajabudjettien, työnkulkubudjettien ja hyväksyntäheuristiikkojen yhteydessä |

Nämä resurssiavaimet ovat osa Queuety-järjestelmän laajempaa [Resurssienhallinta](https://queuety.inline0.dev/fi/docs/features/resource-management)-mallia, jossa työn metatiedot, työnkulkubudjetit ja työntekijöiden hyväksyntä toimivat yhdessä.

Rekisteröi käsittelijä:

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

Tai käytä [PHP-attribuutteja](https://queuety.inline0.dev/fi/docs/features/attributes) automaattiseen rekisteröintiin.

## Prioriteetti

Töillä on neljä prioriteettitasoa. Työntekijät varaavat ensin korkeamman prioriteetin työt.

```php
use Queuety\Enums\Priority;

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

| Taso | Arvo | Kuvaus |
|---|---|---|
| `Priority::Low` | `0` | Oletus. Taustatyö, joka voi odottaa. |
| `Priority::Normal` | `1` | Tavallinen prioriteetti. |
| `Priority::High` | `2` | Käsitellään ennen arvoja Low ja Normal. |
| `Priority::Urgent` | `3` | Käsitellään ensimmäisenä. Käytä säästeliäästi. |

## Viive

Aseta työlle viive, jotta se on käytettävissä vasta määritetyn sekuntimäärän kuluttua:

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

Työ lisätään heti, mutta työntekijät eivät varaa sitä ennen kuin viive on kulunut.

## Yksilölliset työt

Estä saman käsittelijän ja hyötykuorman töiden lähettäminen kahteen kertaan. Jos odottava tai käsittelyssä oleva työ samalla käsittelijällä ja hyötykuormalla on jo olemassa, palautetaan sen nykyinen tunnus uuden työn luomisen sijaan.

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

## Työriippuvuudet

Työ voi riippua toisesta työstä. Työntekijä ei varaa sitä ennen kuin riippuvuus on valmis.

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

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

## Erälähetys

Lähetä useita töitä yhdellä usean rivin INSERT-lauseella, jotta läpimeno on mahdollisimman suuri:

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

Jokainen kohde voi sisältää valinnaisia avaimia: `queue`, `priority`, `delay`, `max_attempts`.

## Uudelleenyritys ja viive

Kun työ aiheuttaa poikkeuksen, Queuety palauttaa sen arvoon `pending` viivästetyllä `available_at`-toiminnolla, kunnes työ on suoritettu yhteensä `max_attempts` kertaa (ensimmäinen suorituskerta lasketaan mukaan, joten `max_attempts( 3 )` tarkoittaa enintään kahta uudelleenyritystä). Kunkin uudelleenyrityksen viive määräytyy viivestrategian mukaan. `attempt` ilmaisee juuri epäonnistuneen suorituskerran numeron:

| Strategia | Viivekaava | Esimerkki (suorituskertojen 1, 2, 3 jälkeen) |
|---|---|---|
| `exponential` | `2^attempt * 30` sekuntia, enintään 3600 | 60s, 120s, 240s |
| `linear` | `attempt * 60` sekuntia | 60s, 120s, 180s |
| `fixed` | `60` sekuntia | 60s, 60s, 60s |

Aseta yleinen oletusarvo kohteessa `wp-config.php`:

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

Tai käsittelijäkohtaisesti kohdassa `config()`:

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

Tai lähetyskertakohtaisesti:

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

## Sivuun siirretyt työt

Kun kaikki uudelleenyritysyritykset on käytetty, työ merkitään **haudatuksi**. Haudatut työt säilyvät tietokannassa tarkastelua varten, mutta niitä ei käsitellä. Voit yrittää niitä uudelleen manuaalisesti. Manuaalinen uudelleenyritys asettaa työn jälleen tilaan `pending` nollaamatta sen yritysten määrää, joten uudelleen yritetty haudattu työ suoritetaan vielä kerran ja haudataan uudelleen, jos suoritus epäonnistuu:

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

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

Tai käyttämällä CLI:

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

## Aikakatkaisu

Queuety käynnistää `pcntl_alarm()`-ajastimen enimmäissuoritusajalle, ja sen on tarkoitus epäonnistua yrityksessä `TimeoutException`-virheellä, jos aika ylittyy. Aikakatkaisua ei tällä hetkellä valvota ([#10](https://github.com/inline0/queuety/issues/10)): työntekijä ei ota asynkronisia signaaleja käyttöön, joten pitkään suoritettava käsittelijä jatkaa loppuun asti, ja `sleep()`-kutsuun jumiutunut käsittelijä keskeytetään hälytyksen lauetessa ja merkitään sitten suoritetuksi. Määritä aikakatkaisu yleisesti:

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

Työt, joiden tila on `processing` yli `QUEUETY_STALE_TIMEOUT` sekuntia, palautetaan automaattisesti:

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

Palauta vanhentuneet työt manuaalisesti:

```bash
wp queuety recover
```

## Jonot

Työt lähetetään nimettyihin jonoihin. Kukin työntekijä käsittelee yhtä jonoa tai järjestettyä jonolistaa (katso [Työntekijät](https://queuety.inline0.dev/fi/docs/features/workers#multi-queue-priorities)):

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

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

Oletusjono on `default`. Voit eristää työkuormia suorittamalla kullekin jonolle omaa työntekijäprosessia.

## Nopeuden rajoittaminen

Rajoita, kuinka usein käsittelijä voidaan suorittaa tietyn aikavälin aikana:

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

Lisätietoja on kohdassa [Nopeuden rajoittaminen](https://queuety.inline0.dev/fi/docs/features/rate-limiting).

## Lähetettävät työluokat

Queuety v0.6.0 esittelee luokkapohjaisen työn API. Erillisten käsittelijän nimen, hyötykuorman ja käsittelijäluokan sijaan määrittelet yhden luokan, joka kokoaa kaiken yhteen: hyötykuorman (tyypitettyinä julkisina ominaisuuksina), suorituslogiikan ja valinnaiset väliohjelmistot.

### `Contracts\Job`-rajapinta

```php
use Queuety\Contracts\Job;

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

`Job`-luokka sisältää hyötykuormansa julkisina ominaisuuksina ja suorittaa oman logiikkansa kohdassa `handle()`. Erillistä `config()`-metodia tai käsittelijän rekisteröintiä ei tarvita.

### `Dispatchable`-trait

Lisää `Dispatchable`-trait saadaksesi staattisen `dispatch()`-metodin työluokkaasi:

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

Lähetä työ välittämällä konstruktorin argumentit:

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

`Dispatchable`-trait kutsuu sisäisesti `Queuety::dispatch()`-metodia, joten kaikki `PendingJob`-asetukset (jono, prioriteetti, viive, max_attempts, unique, after) toimivat täsmälleen samalla tavalla.

### Sarjallistamisen toiminta

Kun lähetät `Job`-esiintymän, Queuety-luokan `JobSerializer` poimii täysin määritetyn luokan nimen käsittelijäksi ja kaikki julkiset ominaisuudet hyötykuormaksi. Työntekijän puolella sarjallistaja muodostaa esiintymän uudelleen yhdistämällä hyötykuorman avaimet konstruktorin parametrien nimiin. Enum-arvot, joilla on tausta-arvo, sarjallistetaan niiden perustana olevaksi arvoksi ja palautetaan automaattisesti.

### Työn ominaisuudet

Työluokat voivat määrittää julkisia ominaisuuksia uudelleenyritys- ja aikakatkaisutoiminnan ohjaamiseksi. Työntekijä lukee ne reflektiolla ja soveltaa niitä automaattisesti.

```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...
    }
}
```

| Ominaisuus | Tyyppi | Kuvaus |
|---|---|---|
| `$tries` | `int` | Työn enimmäisyritysmäärä ennen sen hautaamista. Ohittaa lähetyshetkellä määritetyn arvon `max_attempts`. |
| `$timeout` | `int` | Suorituksen enimmäiskesto sekunteina. Ohittaa yleisen vakion `QUEUETY_MAX_EXECUTION_TIME`. Edellyttää asetusta `pcntl`, eikä sitä vielä valvota (katso [Aikakatkaisu](https://queuety.inline0.dev/fi/docs/jobs#timeout)). |
| `$backoff` | `array` | Viivearvojen taulukko kullekin uudelleenyritykselle sekunteina. Viimeistä arvoa käytetään myös myöhemmissä yrityksissä. |
| `$concurrency_group` | `string` | Kalliiden töiden yhteinen työntekijän pääsyryhmä. |
| `$concurrency_limit` | `int` | Ryhmän samanaikaisesti suoritettavien `processing`-töiden enimmäismäärä. Ohitetaan, jos `$concurrency_group` puuttuu. |
| `$cost_units` | `int` | Suhteellinen suoritusmaksu, jota käytetään työnkulun budjeteissa ja työntekijän pääsynhallinnassa. |

`$backoff`-taulukko määrittää kasvavat uudelleenyritysviiveet. Esimerkiksi `[10, 30, 60, 120]` tarkoittaa, että ensimmäistä uudelleenyritystä odotetaan 10 sekuntia, toista 30 sekuntia ja kolmatta 60 sekuntia. Kaikissa myöhemmissä yrityksissä odotetaan 120 sekuntia.

Resurssiominaisuuksista on hyötyä, kun työluokat edustavat erilaisia kustannusprofiileja. Nopea välimuistin päivitys ja suuri palveluntarjoajakutsu voivat kumpikin olla ”yksi työ”, mutta niitä ei välttämättä pidä laskea samalla tavalla, kun työnkulun budjetti tai työntekijän pääsyä koskeva päätös pyrkii pitämään prosessin turvallisissa rajoissa.

Katso koko mallin kuvaus, mukaan lukien työnkulun budjetit ja työntekijäpuolen pääsynhallinta, kohdasta [Resurssien hallinta](https://queuety.inline0.dev/fi/docs/features/resource-management).

### Epäonnistuneen työn koukku

Kun työ haudataan kaikkien uudelleenyritysyritysten epäonnistuttua, Queuety kutsuu työesiintymän `failed()`-metodia, jos sellainen on määritelty. Käytä sitä siivoukseen, hälytyksiin tai lokitukseen.

```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()}" );
    }
}
```

`failed()`-metodi saa parametrikseen lopullisen epäonnistumisen aiheuttaneen poikkeuksen. Työesiintymä sarjallistetaan alkuperäisestä hyötykuormastaan, joten kaikki julkiset ominaisuudet ovat käytettävissä.

### Ehdollinen lähetys

`Dispatchable`-trait tarjoaa ehdolliseen lähettämiseen metodit `dispatch_if()` ja `dispatch_unless()`. Molemmat saavat ensimmäisenä argumenttinaan totuusarvoisen ehdon ja sen jälkeen työn konstruktorin argumentit.

```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()` lähettää työn, kun ehto on `true`, ja palauttaa `PendingJob`. Se palauttaa arvon `null`, kun ehto on `false`.

`dispatch_unless()` lähettää työn, kun ehto on `false`, ja palauttaa `PendingJob`. Se palauttaa arvon `null`, kun ehto on `true`.

Molemmat metodit tukevat ketjutettavia `PendingJob`-asetuksia työn lähetyksen yhteydessä:

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

### Synkroninen lähetys

Käytä `Queuety::dispatch_sync()`-metodia suorittaaksesi työn heti nykyisessä prosessissa lisäämättä sitä jonoon. Tämä sopii testaamiseen ja töihin, jotka on suoritettava suoraan.

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

Työn `handle()`-metodia kutsutaan suoraan. Tietokantariviä ei luoda, työntekijää ei käytetä eikä väliohjelmistoa sovelleta.

### Töiden ketjutus

Töiden ketjutuksessa tehtävät suoritetaan järjestyksessä yksi toisensa jälkeen. Kukin ketjun työ riippuu siitä, että edellinen työ valmistuu onnistuneesti. Jos jokin työ epäonnistuu, ketjun seuraavat työt jäävät odottamaan.

#### `Queuety::chain()`-metodin käyttö

```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()` palauttaa arvon `ChainBuilder`, jossa on sujuvan rajapinnan metodit jonon ja epäonnistumisen käsittelijän määrittämiseen. `dispatch()`-metodi luo kaikki työt peräkkäisinä `depends_on`-suhteina ja palauttaa ensimmäisen työn tunnuksen.

#### `with_chain()`-metodin käyttö

`Dispatchable`-trait tarjoaa `with_chain()`-metodin. Se vastaa tällä hetkellä `Queuety::chain( $jobs )`-metodia: luokkaa, jolle metodia kutsutaan, ei lisätä ketjuun ([#13](https://github.com/inline0/queuety/issues/13)), joten lisää myös taulukon ensimmäinen työ:

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

#### Ketjun epäonnistumisen käsittely

Rekisteröi epäonnistumisen käsittelijäluokka käyttämällä `catch()`-metodia. Käsittelijäluokassa on oltava `handle()`-metodi. Sitä kutsutaan, kun jokin ketjun työ haudataan pysyvästi. Työntekijä tukee arvoja `handle()`, `handle( Job $job )` ja `handle( Job $job, \Throwable $exception )`.

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

### Väliohjelmiston lisääminen

Työluokat voivat määrittää `middleware()`-metodin, joka palauttaa suorituksen ympärille kietoutuvan väliohjelmiston:

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

Lisätietoja sisäänrakennetuista väliohjelmistoista ja omien väliohjelmistojen kirjoittamisesta on kohdassa [Väliohjelmistot](https://queuety.inline0.dev/fi/docs/features/middleware).

### Vertailu käsittelijäpohjaisiin töihin

| | Käsittelijäpohjainen (v0.1+) | Luokkapohjainen (v0.6+) |
|---|---|---|
| Lähetys | `Queuety::dispatch( 'send_email', $payload )` | `SendEmailJob::dispatch( $to, $subject )` |
| Käsittelijä | Erillinen luokka, joka toteuttaa `Handler` | Sama luokka toteuttaa `Contracts\Job` |
| Hyötykuorma | Tyypittämätön `array $payload` | Tyypitetyt julkiset ominaisuudet |
| Rekisteröinti | `Queuety::register()` tai `#[QueuetyHandler]` | Ei tarvita (käytössä on FQCN) |
| Väliohjelmisto | Ei käytettävissä | Valinnainen `middleware()`-metodi |
| Määritys | `config()`-metodi | Työominaisuudet + `PendingJob`-ketju |

Molempia tapoja tuetaan täysin. Nykyiset käsittelijäpohjaiset työt toimivat edelleen ilman muutoksia. Voit käyttää molempia tyylejä samassa projektissa.

## Työn elinkaari

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

| Tila | Kuvaus |
|---|---|
| `pending` | Odottaa työntekijän noudettavaksi ottamista |
| `processing` | Suoritetaan parhaillaan |
| `completed` | Suoritettu onnistuneesti |
| `failed` | Varattu; työntekijä ei aseta tätä tilaa tällä hetkellä. Uudelleen yritettävä epäonnistunut suoritus palaa tilaan `pending` |
| `buried` | Kaikki uudelleenyritysyritykset on käytetty |
