Queuing Laravel tasks by third-party services

While working on a projectbe it hype microservices or a monolith) quite often there is a situation when it is necessary that one service sets a task for another service. The task is quite trivial if the same framework is used on both sides. But everything becomes much more interesting when, on the subscriber side, we allow Laravel with its default format, and on the publisher side, something fashionable in Go.

Laravel Default Format Example
{
  "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\";}"
  }
}

It is rather strange to push the formation of such a payload onto another development team. Of the obvious shortcomings, one can single out not only a rigid binding to the Laravel format, but also an explicit enumeration of the classes involved. Therefore, we are considering the next solution that comes to mind first: our own handler, which we will teach to understand the format that we agree on.

Let’s assume we can agree on something like this
{
  "uuid": "59f3007b-e63c-4c71-b298-885563664cd3",
  "job": "EXAMPLE",
  "data": {
    "type": "TYPE_TEST",
    "params": {
      "stringParam": "tabcdef012345"
    }
  }
}

But, in order not to dig into programming, inventing another bicycle, let’s try to use the tools of the service container.

uid

I think with this property everything is clear and without explanation. Therefore, we go further.

job

In this case, we masked Illuminate\\Queue\\CallQueuedHandler@call under EXAMPLE. In a service container, it will not be enough to simply specify EXAMPLE as a synonym for class Illuminate\Queue\CallQueuedHandlersince Laravel, when processing the queue, will try to call the method fire, unless otherwise specified explicitly. Therefore, we simply inherit and extend the base class with the desired method.

<?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

Above in class QueueHandler we also overridden the method getCommandto take resolving jobs into your own hands. It remains the case for small – to tie TYPE_TEST with our job App\Jobs\ProcessPodcast through some ServiceProvider.

<?php

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

Conclusion

As a result, we get a fairly flexible format that is easily accepted by third-party developers.

Simple customization on the Laravel side without changing the code of other services.

Reducing the amount of data transferred over the network.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *