Preskočiť na obsah

Integrácia do frameworkov ​

Hermes nemá bundle pre framework a ani ho nepotrebuje. Jeho triedy dostávajú závislosti v konštruktore, takže ich vytvorí akýkoľvek kontajner. Stránky v tejto sekcii ukazujú tú istú malú integráciu v troch frameworkoch:

Každý príklad je súbor súborov v examples/frameworks a kód na týchto stránkach sa načítava z nich. Každý bol nainštalovaný do novo vytvoreného projektu a spustený proti Redisu (ext-redis aj Predis), RabbitMQ a SQS (lokálny server moto).

Čo má každá integrácia ​

ČasťČo to jeKto ju používa
DriverFactoryVytvorí DriverInterface z jednoduchej konfigurácie: redis (ext-redis), predis, rabbitmq alebo sqs, plus názov fronty a limit správ.kontajner
EmitterInterfaceZdieľaný Emitter s PSR-3 loggerom frameworku.controllery, príkazy, joby
DispatcherInterfaceDispatcher s loggerom, ukončením a handlermi zaregistrovanými cez registerHandler().len príkaz workera
ShutdownInterfaceSharedFileShutdown nad súborom v dočasnom priečinku alebo v storage aplikácie.worker a hermes:restart
hermes:workVolá $dispatcher->handle(). Blokuje až do ukončenia alebo limitu správ. --priority ho obmedzí na niektoré priority.Supervisor alebo systemd
hermes:restartVolá $shutdown->shutdown(). Bežiace workery sa zastavia po aktuálnej správe.skript nasadenia
app:register-userUkážkový producent, ktorý vkladá user.registered.vy, na vyskúšanie

Tá istá DriverFactory a UserRegisteredHandler sa používajú vo všetkých troch frameworkoch:

php
<?php

declare(strict_types=1);

namespace App\Hermes;

use Aws\Sqs\SqsClient;
use InvalidArgumentException;
use PhpAmqpLib\Connection\AMQPLazyConnection;
use Predis\Client;
use Redis;
use Tomaj\Hermes\Driver\AmazonSqsDriver;
use Tomaj\Hermes\Driver\DriverInterface;
use Tomaj\Hermes\Driver\LazyRabbitMqDriver;
use Tomaj\Hermes\Driver\PredisSetDriver;
use Tomaj\Hermes\Driver\RedisSetDriver;

/**
 * Builds the Hermes driver from plain configuration, so the web process and the worker
 * always use the same driver, queue name and connection.
 */
final class DriverFactory
{
    /**
     * @param array{
     *     driver: string,
     *     queue: string,
     *     max_items: int,
     *     redis: array{host: string, port: int},
     *     rabbitmq: array{host: string, port: int, user: string, password: string, vhost: string},
     *     sqs: array{region: string, endpoint: ?string, key: string, secret: string},
     * } $config
     */
    public static function create(array $config): DriverInterface
    {
        $queue = $config['queue'];

        $driver = match ($config['driver']) {
            'redis' => new RedisSetDriver(self::redis($config['redis']), $queue),
            'predis' => new PredisSetDriver(new Client(['host' => $config['redis']['host'], 'port' => $config['redis']['port']]), $queue),
            'rabbitmq' => new LazyRabbitMqDriver(
                new AMQPLazyConnection(
                    $config['rabbitmq']['host'],
                    $config['rabbitmq']['port'],
                    $config['rabbitmq']['user'],
                    $config['rabbitmq']['password'],
                    $config['rabbitmq']['vhost'],
                ),
                $queue,
                [],
                1, // seconds to sleep when the queue is empty
            ),
            'sqs' => new AmazonSqsDriver(self::sqs($config['sqs']), $queue),
            default => throw new InvalidArgumentException("Unknown Hermes driver \"{$config['driver']}\""),
        };

        // Start a fresh worker process after this many messages.
        $driver->setMaxProcessItems($config['max_items']);

        return $driver;
    }

    /**
     * @param array{host: string, port: int} $config
     */
    private static function redis(array $config): Redis
    {
        $redis = new Redis();
        $redis->connect($config['host'], $config['port']);

        return $redis;
    }

    /**
     * @param array{region: string, endpoint: ?string, key: string, secret: string} $config
     */
    private static function sqs(array $config): SqsClient
    {
        $options = [
            'region' => $config['region'],
            'version' => 'latest',
            'credentials' => ['key' => $config['key'], 'secret' => $config['secret']],
        ];
        if ($config['endpoint'] !== null && $config['endpoint'] !== '') {
            $options['endpoint'] = $config['endpoint']; // a local SQS such as moto or ElasticMQ
        }

        return new SqsClient($options);
    }
}

Driver sa vytvorí raz na proces. Webový proces aj worker musia dostať rovnakú triedu drivera a názov fronty; factory ich číta z jednej konfigurácie, takže sa nerozídu. Čo ktorý driver dokáže, nájdete v Prehľade driverov.

php
<?php

declare(strict_types=1);

namespace App\Hermes;

use Psr\Log\LoggerInterface;
use Tomaj\Hermes\Handler\RetryableHandlerInterface;
use Tomaj\Hermes\Handler\RetryTrait;
use Tomaj\Hermes\MessageInterface;

final class UserRegisteredHandler implements RetryableHandlerInterface
{
    use RetryTrait;

    public function __construct(private readonly LoggerInterface $logger) {}

    #[\Override]
    public function handle(MessageInterface $message): bool
    {
        $email = $message->getPayload()['email'] ?? null;
        if (!is_string($email)) {
            return false; // a broken message stays broken, do not retry it
        }

        // Send the welcome email here. Throw an exception to retry later.
        $this->logger->info("Welcome email sent to {$email}", ['message_id' => $message->getId()]);

        return true;
    }
}

Pravidlá platné v každom frameworku ​

  • Producenti závisia od EmitterInterface, nikdy od Emitter. Od Hermesu 6.0 sú triedy final, takže PHPUnit nevie Emitter zamockovať. Mockujte rozhranie; každá stránka frameworku má test.
  • Dispatcher si vytvára len worker. Vytvoriť ho vo webovej požiadavke neuškodí, ale nikdy tam nie je potrebný.
  • Každá aplikácia nech má vlastný názov fronty. Dve aplikácie, ktoré zdieľajú jeden Redis a predvolený kľúč hermes, si navzájom berú správy. Worker zahodí správu, pre ktorú nemá handler.
  • Po nasadení reštartujte workery. Worker drží starý kód, kým neskončí. Ako posledný krok nasadenia spustite hermes:restart a nové workery nechajte spustiť správcu procesov. Pozrite Ukončenie a limit správ a Nasadenie do produkcie.
  • Zastavenie signálom potrebuje ext-pcntl. S ním SIGTERM od Supervisora, systemd alebo Kubernetes nechá workera dokončiť správu a skončiť, aj vo vnútri hermes:work (Hermes počas behu handle() preberá obsluhy signálov konzoly). Bez neho signál ukončí PHP okamžite, aj uprostred handlera, preto najprv zavolajte hermes:restart. Pozrite Ukončenie a limit správ.

Náhodná zložka opakovania a zaregistrovaný Random\Randomizer ​

Hermes 6.0 (#103) pridáva do Dispatchera voliteľný štvrtý argument konštruktora: ?Random\Randomizer $randomizer = null. Bez neho náhodná zložka opakovania používa nový Randomizer s bezpečným enginom. Pozrite Opakovanie.

Kontajnery, ktoré dispatcher autowirujú, vložia službu Random\Randomizer, ak ju aplikácia zaregistrovala na niečo iné, napríklad Mt19937 so seedom pre fixtures. Časy opakovania potom pochádzajú z tohto enginu. Každá stránka frameworku ukazuje, ako namiesto toho odovzdať null. Tento riadok pridajte len pri Hermese 6.0 alebo novšom: vo verzii 5.x a vo vydaniach pred #103 argument neexistuje a kontajner konfiguráciu odmietne.

Provider pre Laravel vytvára dispatcher cez new Dispatcher(...), takže sa ho to netýka.

Overené s ​

FrameworkVerzieDriveryĎalej overené
Nettenette/web-project, nette/application 3.3.0, nette/di 3.2.6, contributte/console 0.11.0ext-redis, Predis 3.7, RabbitMQ (php-amqplib 3.7.5), SQS na motoPOST na presenter, PHPUnit test, Supervisor
Laravel11.57.0, 12.70.0 a 13.38.0tie isté štyrifeature testy, Supervisor
Symfony7.4.21, monolog-bundle 4.1.0tie isté štyriPOST na controller, test WebTestCase, Supervisor

PHP 8.4, Hermes dev-master. Každý framework bol spustený aj na Hermese 5.0.0 a na vetvách #103 a #105.

Časté otázky ​

Existuje bundle alebo balík Hermesu pre môj framework? ​

Nie. Integrácia je factory, niekoľko definícií služieb a dva konzolové príkazy, ktoré ukazujú stránky Nette, Laravel a Symfony. Skopírujte ich a zmeňte, čo potrebujete.

Môžem Hermes používať vedľa Laravel queues alebo Symfony Messenger? ​

Áno. Hermes sa ich nedotýka. Pre Hermes použite iný kľúč v Redise alebo inú frontu v RabbitMQ, aby si navzájom nečítali správy.

Ktorý driver nakonfigurovať? ​

Predis alebo ext-redis, ak chcete priority a odložené správy, RabbitMQ alebo SQS, ak ich už prevádzkujete. Čo ktorý driver podporuje, uvádza porovnanie.

Potrebujem jedného workera na handler? ​

Nie. Jeden proces hermes:work spúšťa všetky handlery zaregistrované v dispatcheri. Pre vyššiu priepustnosť spustite viac procesov a cez --priority si niektoré nechajte na urgentné správy. Pozrite Priorita.

Ako testovať kód, ktorý vkladá správy? ​

Typujte EmitterInterface a v teste ho nahraďte PHPUnit mockom. Každá stránka frameworku má funkčný test a handlery pokrýva Testovanie.

Vydané pod licenciou MIT.