При работе над проектом (будь то хайповые микросервисы или монолит) довольно часто возникает ситуация, когда необходимо, чтобы один сервис поставил задачу для другого сервиса. Задача довольно тривиальная, если на обеих сторонах используется один и тот же фреймворк. Но все становится намного интересней, когда на стороне подписчика допустим Laravel со своим дефолтным форматом, а на стороне издателя что‑то модное на Go.

Пример дефолтного формата Laravel
{
  "uuid": "59f3007b-e63c-4c71-b298-885563664cd6",
  "displayName": "App\\Jobs\\ProcessPodcast",
  "job": "Illuminate\\Queue\\CallQueuedHandler@call",
  "maxTries": null,
  "maxExceptions": null,
  "failOnTimeout": false,
  "backoff": null,
  "timeout": null,
  "retryUntil": null,
  "data": {
    "commandName": "App\\Jobs\\ProcessPodcast",
    "command": "O:23:\"App\\Jobs\\ProcessPodcast\":1:{s:11:\"stringParam\";s:12:\"abcdef012345\";}"
  }
}


Довольно странно спихнуть формирование подобного payload на другую комманду разработчиков. Из очевидных недостатокв можно выделить не только жесткую привязку к формату Laravel, но еще и явное перечисление задействованных классов. Поэтому рассматриваем следующее решение, которое первым приходит на ум: свой хандлер, который научим понимать тот формат, который согласуем.

Предположим, что удалось согласовать нечто подобное
{
  "uuid": "59f3007b-e63c-4c71-b298-885563664cd3",
  "job": "EXAMPLE",
  "data": {
    "type": "TYPE_TEST",
    "params": {
      "stringParam": "tabcdef012345"
    }
  }
}

Но, чтобы не закапываться в программировании изобретая очередной велосипед, попробуем воспользоваться средствами сервис‑контейра.

uuid

Думаю с этой пропертей все понятно и без пояснений. Поэтому едем дальше.

job

В данном случае, мы замаскировали Illuminate\\Queue\\CallQueuedHandler@call под EXAMPLE. В сервис‑контейнере не достаточно будет просто указать EXAMPLE как синоним для класса Illuminate\Queue\CallQueuedHandler, поскольку Laravel при обработке очереди попробует вызвать метод fire, если другой не указан явно. Поэтому просто унаследуемся и расширим базовый класс нужным методом.

<?php

declare(strict_types=1);

namespace App\Providers;

use App\QueueHandler;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public $bindings = [
        // app()->bind(QueueHandler::NAME, QueueHandler::class);
        QueueHandler::NAME => QueueHandler::class
    ];
}
<?php

declare(strict_types=1);

namespace App;

use Illuminate\Contracts\Queue\Job;
use Illuminate\Queue\CallQueuedHandler;

class QueueHandler extends CallQueuedHandler
{
    public const NAME = 'EXAMPLE';
    public const JOB_CLASS = 'type';
    public const JOB_PARAMS = 'params';

    public function fire(Job $job, array $data): void {
        $this->call($job, $data);
    }

    protected function getCommand(array $data)
    {
        return $this->container->make(
            $data[self::JOB_CLASS],
            is_array($data[self::JOB_PARAMS] ?? null) ? $data[self::JOB_PARAMS] : []
        );
    }
}

data

Выше, в классе QueueHandler мы еще переопределили метод getCommand, чтобы резолвинг джобы взять в свои руки. Осталось дело за малым — связать TYPE_TEST с нашей джобой App\Jobs\ProcessPodcast через какой‑нибудь ServiceProvider.

<?php

app()->bind('TYPE_TEST', \App\Jobs\ProcessPodcast::class);

Заключение

Как итог, получаем довольно гибкий формат, который легко воспринимается сторонними разработчиками.

Простая кастомизация на стороне Laravel без изменений кода других сервисов.

Уменьшение объема гоняемых по сети данных.

Комментарии (0)