[inhalt]
Projekt BISO 3 - Handbuch

Background-Job-System

BISO bietet ein Background-Job-System an, welches Hintergrund-Arbeiten definieren / ausführen kann. Dies können beliebige Aufgaben sein, welche entweder der Entkoppelung des Frontends dienen (Start über das UI, dann Job im Hintergrund ausführen), oder Maintenance-Arbeiten, welche als Job konfiguriert werden können.

Die Job-Ausführung kann auch wiederholt erfolgen: Jobs können sowohl einzelne / einmalig auszuführende Tasks wie auch in bestimmten Intervallen wiederholende Jobs sein. Beispielweise können so täglich auszuführende Arbeiten definiert werden. Dies ist aber nicht zu verwechseln mit einem Scheduling wie cron: Jobs können nicht zu bestimmten Zeiten gestartet werden, nur in bestimmen Intervallen wiederholt ausgeführt werden.

Architektur

Ein Job definiert die Aufgabe, welche im Hintergrund ausgeführt wird. Der Job definiert den Wiederholungstyp (einzeln, wiederholend) und beinhaltet die für die Aufgabe notwendigen Daten und Programmcode.

Ein MultiStepJob ist ein Job, welcher mehrere JobSteps instanziert und diese dann abarbeitet. Dies ist z.B. dann gewünscht, wenn der Job viele kleine Einzelschritte ausführen muss (Bsp: Abarbeiten einer Liste von Fällen).

Die **JobFactory**-Klasse dient zur Verwaltung der Jobs: Jobs werden nicht manuell erstellt / gestartet, sondern via die JobFactory-Klasse gemanagt.

In beiden Fällen müssen entsprechende Kind-Klassen definiert werden, welche die eigentliche Logik der abstrakten Methoden implementieren:

Job-Klassendiagramm

Implementation von Job-Klassen

Um eigene Job-Klassen zu implementieren, können/müssen folgende Klassen implementiert werden:

Managen / Starten von Jobs

Die Job-Klassen sollen nicht manuell instanziert / gespeichert werden. Für das Job-Management dient die JobFactory-Klasse. Sie ist dafür zuständig, Jobs zu erstellen und zu starten:

<?php
use Biso\App\jobs\JobFactory;

$logger = Logger::getLogger('jobs');
$jobFactory = new JobFactory($logger);

// Erstellen eines neuen Jobs: Instanzieren einer spezifischen Job-Klasse:
// Dies erstellt den Job, und `init()` wird aufgerufen.
// Danach landet dieser in der Job-Queue.
$job = $jobFactory->createNewJob(MyJob::class, ['option1' => 'value1']);

// Starten des nächsten Jobs in der Queue:
// Blockiert solange, bis der Job fertig ist.
$job = $jobFactory->runNextNewJob();

// Laden eines bestehenden Jobs mit der richtigen Klasse:
$job = $jobFactory->loadJob($jobId);

Beispiel-Jobs

SimpleJob: Einfacher Job ohne Zwischenschritte

<?php
use Biso\App\jobs\Job;

class SimpleJob extends Job {
    /**
     * Initialisierungsfunktion: Hier erhält der Job etwelche Parameter, die verarbeitet werden können:
     */
    #[Override]
    public function init($options = null) {
        $this->getLogger()->info('SimpleJob is initializing. Status: ' . $this->status);
        foreach ($options ?: [] as $key => $value) {
            $this->getJobData()->{$key} = $value;
        }
    }

    /**
     * Funktion zum Ausführen des Jobs: Hier wird die eingentliche Arbeit verrichtet. Am Schluss
     * muss der End-Status zurückgegeben werden:
     */
    #[Override]
    protected function run(): string {
        $this->getLogger()->info('SimpleJob is running');
        return static::STATUS_SUCCESS;
    }
}


// Registration / Erstellen des Jobs:
$jobFactory = new JobFactory($logger);
$jobFactory->createNewJob(SimpleJob::class, ['option1' => 'value1']);
$jobFactory->runNextNewJob();

SimpleMultiStepJob: Einfacher Job mit Zwischenschritten

<?php

use Biso\App\jobs\JobStep;
use Biso\App\jobs\MultiStepJob;

class SimpleMultiStepJob extends MultiStepJob {
    /**
     * In der init()-Methode werden die einzelnen Zwischenschritte instanziert:
     */
    #[Override]
    public function init($options = null) {
        for ($i = 0; $i < ($options['steps'] ?? 0); $i++) {
            $step = new SimpleJobStep();
            $step->job_id = $this->getId();
            $step->getStepData()->number = $i;
            $step->store();
        }
    }

    /**
     * runStep erhält den auszuführenden Zwischenschritt und implementiert die Logik.
     * Der End-Status muss zurückgegeben werden:
     */
    #[Override]
    protected function runStep(JobStep $step): string {
        $this->getLogger()->info("Step number: {$step->getStepData()->number}");
        return static::STATUS_SUCCESS;
    }
}

// Registration / Erstellen des Jobs:
$jobFactory = new JobFactory($logger);
$jobFactory->createNewJob(SimpleMultistepJob::class, ['steps' => 5]);
$jobFactory->runNextNewJob();

Benutzer-Information

Jobs haben die Möglichkeit, am Schluss eine Benutzer-Info (Email) zu senden. Dabei wird die Email von der am Benutzer (user_id) zugewiesenen Person ermittelt.

Dazu dient auf dem Job die Methode Job::notifyUser($message). Diese kann zu einem beliebigen Zeitpunkt aufgerufen werden. Eine Möglichkeit ist, dies im afterRun()-Hook zu machen:

<?php

use Biso\App\jobs\Job;

class DemoJob extends Job {
    #[Override]
    protected function afterRun() {
        $this->notifyUser("Job abgeschlossen. 1234 Fälle wurden gelöscht");
    }
}

Die notifyUser()-Methode sendet eine Email an den Benutzer, der den Job erstellt hat.

Konfiguration

Für Jobs, welche am Schluss eine Mail-Notifikation versenden, muss ein SMTP-Sender in config.php definiert werden:

<?php
Config::set('smtp_sender', 'absender@domain.com');

Ausführen des Job-Runners

Der Job-Runner wird mittels BISO-Commands ausgeführt:

# Starten eines Job-Runners:
./biso-cli job-runner run

# Starten des Cleanup-Prozesses:
# räumt alte / nicht aktualiserte Jobs auf.
./biso-cli job-runner cleanup

# Anzeigen Jobs:
./biso-cli job-runner list

# Anzeigen anstehender Jobs:
./biso-cli job-runner list new queued

# Anzeigen laufender Jobs:
./biso-cli job-runner list running

Wenn keine Jobs mehr abzuarbeiten sind, beendet sich der Job-Runner-Prozess. Der Job-Runner kann mittels cron-Job z.B. alle Minuten angestossen werden: Ist er (noch) am Laufen, beendet sich der Prozess wieder.

mehrere Jobs gleichzeitig ausführen

Für die parallele Abarbeitung von Jobs können mehrere Job-Runner-Prozesse gestartet werden. Dazu muss jedoch pro Prozess ein dediziertes Lockfile definiert werden:

./biso-cli job-runner run --lockfile /tmp/jobrunner-1.lock
./biso-cli job-runner run --lockfile /tmp/jobrunner-2.lock
./biso-cli job-runner run --lockfile /tmp/jobrunner-3.lock

Dies startet 3 Instanzen gleichzeitig. Jobs werden nur an einen Prozess verteilt.

Scheduling des Job Runners

Der Job-Runner muss regelmässig (z.B. alle 1 min) vom System-Scheduler (cron, Scheduled Tasks) angestossen werden:

Wir empfehlen daher folgende Scheduling-Konfiguration (hier am Beispiel Crontab):

# Min Hour DoM Month DoW
# Starten von 3 Instanzen, alle 1 Minuten:
   *   *    *    *    *   php /biso/webroot/biso-cli job-runner run --lockfile=/tmp/biso-runner-lock-1.lock
   *   *    *    *    *   php /biso/webroot/biso-cli job-runner run --lockfile=/tmp/biso-runner-lock-2.lock
   *   *    *    *    *   php /biso/webroot/biso-cli job-runner run --lockfile=/tmp/biso-runner-lock-3.lock
# Ausführen des Cleanups, um alte / gestorbene Jobs aufzuräumen (alle 6 Stunden):
   *   */6    *    *    *   php /biso/webroot/biso-cli job-runner cleanup