Utilizando las Colas o Queues y los Trabajos o Jobs para posponer Tareas en Laravel

- Andrés Cruz

In english
Utilizando las Colas o Queues y los Trabajos o Jobs para posponer Tareas en Laravel

En esta entrada vamos a conocer el uso de las Colas y los Trabajos en Laravel (Queues y Jobs) para poder realizar trabajos en segundo plano.

La idea de esta API es que nosotros podemos aplazar tareas (dejarlas en segundo plano) que requieren un alto consumo de computo y todo esto es para mejorar la experiencia del usuario y evitar cuelgues en la aplicación; de tal manera que estas tareas de alto cómputo se ejecutarán en otro hilo o proceso en nuestro servidor y se irán resolviendo a medida que lleguen o dependiendo de la prioridad de las mismas.

Básicamente todo este proceso se lleva a cabo entre las tareas/trabajos/jobs (los procesos elevados de cómputo) y las colas (mecanismo para registrar los jobs).

De igual manera vamos a hablar un poco más en detalle de estos dos componentes fundamentales.

Los trabajos o jobs y las colas o queues

Los trabajos son la tarea pesada o que requieren mucho procesamiento que queremos aplazar; y pueden ser de cualquier tipo como envío masivo o simple de emails, procesar imágenes, vídeos etc; por lo tanto son estas tareas que nosotros vamos registrando como pendientes para que Laravel las vaya procesando uno a uno a medida que esta disponible; asi que lo siguiente que te puedes preguntan es, ¿Cómo se maneja esta organización?; es decir, quien es el que se encarga de manejar la prioridad entre tareas o trabajos (colas) y cómo mantenemos una organización de todo esto.

Los jobs

En Laravel, los jobs son una parte importante de la funcionalidad de la cola de trabajos del framework, es decir los jobs son usados para procesar los trabajos pesados almacenados mediante “colas”; estas tareas pesadas pueden ser cualquier cosa como envio de emails, procesar imágenes, vídeos entre otros; esto es ideal ya que, estas tareas no forman parte de la sesión principal del usuario y se evita consumir recursos de la sesión principal del usuario, aparte de que, se evita las famosas ventanas de “no responde” y en general, se tiene una mejor experiencia en el uso de la aplicación.ya que no afecta el rendimiento o la capacidad de respuesta de la aplicación principal.

Los jobs en Laravel pueden ser creados para realizar cualquier tarea que se necesite en el back-end sin que el usuario tenga que esperar a que se complete la tarea; es decir, como enviar un correo o exportar datos en un excel o un formato similar.

Lo estupendo de los jobs es que, se ejecutan de manera secuencial, suponte que creas un job para procesar correos, los correos se van enviando uno a uno sin necesidad de sobrecargar la página, cosa que no sucederia si el envio de correos se manejada desde la sesión principal del usuario y de repente, vengan 5 o más usuarios a enviar correos desde la aplicación, por lo tanto, en un mismo instante de tiempo, tendrias múltiples envios de correos y gastando los recursos que esto requiera.

En resumen, los jobs en Laravel son una parte esencial de la cola de trabajos del framework, y permiten que las tareas pesadas o no esenciales se realicen en segundo plano para mejorar el rendimiento y la capacidad de respuesta de la aplicación principal.

Las colas

Ya en este punto, hemos introducido el concepto de “colas” en el uso de los jobs, de igual manera, vamos a explicarlo rápidamente; las colas de trabajo corresponden a la operación que se quiere realizar, que como comentamos antes, corresponde a la operación “pesada” que se quiere realizar, es decir, en vez de enviar un correo desde un controlador, se almacena en una cola que luego es procesado mediante otro proceso.

Crea tus propias colas en Laravel para mantener el orden

La respuesta a lo comentado anteriormente es bastante simple, serían las colas como mecanismo que permiten registrar o añadir estos trabajos o jobs; podemos registrar o tener tantas colas como queramos y podemos especificar a Laravel mediante artisan que colas queremos que procesen primero; por ejemplo, veamos el siguiente ejemplo:

php artisan queue:work --queue=high,default

Mediante el comando anterior le estamos diciendo a Laravel que primero procese los trabajos de una cola llamada "hight" y luego lo de una cola llamada "default", que por cierto, es la cola que tenemos definida por defecto.

Conexiones para la estructura para mantener las colas

Las conexiones son la manera predeterminada que tenemos para indicar cómo se va a llevar a cabo todo el proceso de las colas (ya que recuerda que la cola absorbe a la tarea, por lo tanto, en este punto, una vez registrada la tarea o job en una cola, en nuestro cola la tarea ya no pinta nada); pero para poder emplear toda esta estructura debemos indicar la conexión y con ella debemos indicar:

  • El controlador o driver
  • Valores predeterminados o de configuración

Por lo tanto, mediante la conexiones nosotros podemos especificar el driver que vamos a emplear que puede ser cualquier como la base de datos u otros servicios; por ejemplo:

Base de datos:

  1. Amazon SQS: aws/aws-sdk-php ~3.0
  2. Beanstalkd: pda/pheanstalk ~4.0
  3. Redis: predis/predis ~1.0 or phpredis PHP extension

Y algunos parámetros extra para la configuración.

Configurar el Driver o conector de las colas para la base de datos

Ahora vamos a abrir el archivo de config/queue.php y el .env, en los cuales vamos a buscar una configuración llamada QUEUE_CONNECTION

Que por defecto se llama QUEUE_CONNECTION y tiene la configuración de sync, que si lo analizas un poco verás que tenemos varios tipos de conexiones que podemos emplear, entre ella la llamada database que es la que vamos a emplear, por lo tanto configúralo al menos en el archivo de .env; en mi caso lo voy a dejar así en el .env:

QUEUE_CONNECTION=database
Y en el config/queue.php quede:
    'default' => env('QUEUE_CONNECTION', 'database'),

Crear la tabla de jobs en nuestra base de datos

Ahora bien lo siguiente que de puedes preguntar sería, ¿cómo nosotros podemos configurar la tabla en nuestra base de datos?; siguiente la documentación oficial:

php artisan queue:table php artisan migrate

Ya tenemos un comando de artisan que nos ayuda en todo este proceso, que sería el que nos permite generar la migración para crear la tabla; y con esto estamos listos.

Creando nuestro primer Job en Laravel

Ahora vamos a crear nuestro primer Job que vamos a configurar con la cola de default; para eso, vamos a crear uno mediante nuestro artisan:

php artisan make:job ProcessImageSmall

Y con esto tenemos un archivo generado en App\Jobs

El cual consta de dos bloques principales:

  1. __construct
  2. handle

La función constructora para inicializar los datos básicos que vamos a emplear y la función de handle para hacer el proceso pesado; así de simple, por lo tanto en la función de handle podemos enviar los emails, procesar videos o imágenes, para nuestro ejemplo vamos a suponer que tenemos una imagen que estamos recibiendo por parámetros:

  protected $image;
 
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(PostImage $image)
    {
        $this->image = $image;
    }

Esta imagen es básicamente una instancia de un modelo que luce de la siguiente manera:

class PostImage extends Model
{
    protected $fillable = ['post_id', 'image'];
 
    public function post(){
        return $this->belongsTo(Post::class);
    }
 
    public function getImageUrl(){
        return URL::asset('images/'.$this->image);
        //return Storage::url($this->image);
    }
}

Y ahora, vamos a hacer un proceso pesado como escalar una imagen; para esto, vamos a emplear el siguiente paquete que podemos instalar mediante composer:

composer require intervention/image

El código que vamos a emplear, básicamente se auto explica solo:

<?php
 
namespace App\Jobs;
 
use App\PostImage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
use Intervention\Image\ImageManagerStatic;
 
class ProcessImageSmall implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    protected $image;
 
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(PostImage $image)
    {
        $this->image = $image;
    }
 
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $image = $this->image;
 
        $fullPath = public_path('images' . DIRECTORY_SEPARATOR . $image->image);
        $fullPathSmall = public_path('images' . DIRECTORY_SEPARATOR . 'small' . DIRECTORY_SEPARATOR . $image->image);
 
        $img = ImageManagerStatic::make($fullPath)->resize(300,200);
        $img->save($fullPathSmall);
    }
}

En el cual simplemente creamos una instancia de nuestro paquete, que instalamos anteriormente, especificamos la ubicación de la imagen y mediante la función de resize escalamos la imagen a las dimenciones especificadas; luego, guardamos la nueva imagen y con esto estamos listos.

Despachar un trabajo

Lo siguiente que tenemos que especificar, seria en donde nosotros vamos a llamar a este trabajo, que puede ser en cualquier parte de nuestra aplicación como cuando nosotros cargamos una imagen, etc; supongamos que tenemos un método para cargar una imagen en Laravel por ejemplo:

  public function image(Request $request, Post $post)
    {
        $request->validate([
            'image' => 'required|mimes:jpeg,bmp,png|max:10240', //10Mb
        ]);
 
        $filename = time() . "." . $request->image->extension();
 
        $request->image->move(public_path('images'), $filename);
 
        //$path = $request->image->store('public/images');
 
        $image = PostImage::create(['image' => /*$path*/ $filename, 'post_id' => $post->id]);
 
        ProcessImageSmall::dispatch($image);
 
        return back()->with('status', 'Imagen cargada con exito');
 
    }

Desde aquí; si te fijas bien en el código, verás que definimos una instancia de nuestra cola con el método de dispatch para despachar el trabajo ProcessImageSmall::dispatch($image)

Levantar el demonio para procesar las colas

Ahora si revisamos la tabla anterior, veremos que se registró un trabajo, pero el mismo no ha sido despachado, para despachar el mismo, tenemos que activar el demonio que se encarga de hacer esta labor, levantar el proceso que se encarga de hacer los trabajos pendientes en la tabla jobs:

php artisan queue:work

Y con esto veremos que tenemos todo listo y el trabajo ya fue despachado:

Despachar trabajo

Con esto, aprendimos a emplear los trabajos y las colas en Laravel; recuerda que esto es uno de los muchísimos temas que tratamos en profundidad en nuestro curso de Laravel que puedes tomar en esta plataforma en la sección de cursos.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.