Filas no Laravel (sem Jobs)
Tags: php, laravel, queue
Published: 2021-10-28 05:45:42

## Introdução

Recentemente, durante uma atividade no trabalho, precisei utilizar filas (SQS, especificamente) para fazer uma troca de informações assíncronas entre dois sistemas distintos (ambos utilizam Laravel).
Após alguma pesquisa na documentação do Laravel e no Google, não consegui encontrar algo que atendesse minhas expectativas, que eram:

1. Não queria criar a mesma classe Job nos dois sistemas. E se eu não tivesse acesso ao código do outro sistema ou ele fosse escrito em outra linguagem?
2. Queria aproveitar a implementação de filas do próprio Laravel, o que facilitaria bastante no caso de uma substituição do mecanismo de filas utilizado (SQS para banco de dados, por exemplo).

## Problema

O uso do sistema de filas do Laravel, como apresentado na documentação, nos leva a pensar que só é possível utilizá-lo em um monolito, onde o sistema que publica um "job" na fila é o mesmo que vai consumí-lo.
Isso porque, ao fazer o dispatch do job, o objeto é serializado e enviado como payload para a fila, que, no momento de seu processamento, é desserializado, já com argumentos do construtor, models instanciadas e hidratadas, etc.
Para a atividade a ser realizada, eu só precisava enviar para a fila um payload em JSON.

## Como resolver?

O primeiro passo foi conhecer o QueueManager do Laravel. Como você pode adivinhar, é ele quem faz o gerenciamento das filas para o framework, e é instanciado durante o boot da aplicação.
Com ele é possível obter as instâncias das filas, já configuradas, de acordo com `config/queue.php`.
Tendo o QueueManager, basta chamar o método `connection`, passando o nome da conexão desejada e um objeto de fila é retornado. Esse objeto implementa a interface `Illuminate\Contracts\Queue\Queue`.

```php
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Queue\QueueManager;

public function queue(): Queue
{
    $queueManager = app()->make(QueueManager::class);
    return $queueManager->connection('database');
}
```

> **OBS 1**: Aqui eu usei a conexão `database` para fins didáticos, mas pode ser utilizada qualquer outra suportada.
> **OBS 2**: Em versões mais antigas do Laravel, o bind no container foi feito utilizando um alias, e você pode acessá-lo assim: `app()->make('queue')`

A partir disso, já seria possível chamar quaisquer métodos diretamente, mas eu fiz uma classe para ficar responsável por essa interação:

```php
use Illuminate\Contracts\Queue\Job;
use Illuminate\Contracts\Queue\Queue;

class Dispatcher
{
    public function __construct(
        private Queue $queue
    )
    {
    }

    public function push(string $name, array $data, string $queue = null): void
    {
        $this->queue->push($name, $data, $queue);
    }

    public function pushRaw(array|string $data, string $queue = null): void
    {
        if (is_array($data)) {
            $data = json_encode($data);
        }

        $this->queue->pushRaw($data, $queue);
    }

    public function pop(string $queue = null): ?Job
    {
        return $this->queue->pop($queue);
    }
}
```

Nessa classe eu recebo um objeto `Queue` no construtor e tenho alguns métodos para interagir com a fila.
Os métodos `push` e `pushRaw` correspondem aos métodos de mesmo nome da interface `Queue`, e inserem registros na fila, com a diferença de que o `pushRaw` não faz nenhum tratamento do payload (a conversão de array para json é por minha conta, ele espera uma string). Já o método pop retorna um registro da fila, caso exista, já mapeado para a classe Job, que possui métodos para devolvê-lo para a fila, fazer sua exclusão, etc.

Exemplos dos payloads:

1. Com o método `push`:

```json
{
  "uuid": "39a0d4d2-447e-4f38-b48b-e30ce968a9ab",
  "displayName": "some-name",
  "job": "some-name",
  "maxTries": null,
  "maxExceptions": null,
  "failOnTimeout": false,
  "backoff": null,
  "timeout": null,
  "data": {
    "name": "John Doe",
    "age": 25,
    "country": "BR"
  }
}
```

2. Com o método `pushRaw`:

```json
{
  "name": "Jane Doe",
  "age": 23,
  "country": "US"
}
```

Se você quiser conferir o projeto com essa implementação, ele está disponível aqui: https://github.com/samfelgar/queue-example

Até a próxima!