Una regla fundamental es simple: no hagas esperar a quien llama por algo que no necesita esperar.
Un comerciante pide a la License API que emita una licencia para un sitio nuevo. El driver de Statamic hace una llamada de red real, y esa llamada puede tardar uno o dos segundos, más si el servicio externo tiene una mañana lenta. Quien llama necesita la clave de licencia en el cuerpo de la respuesta ahora, porque lo siguiente que pasa es que el sitio se configura con ella. Esa petición se queda síncrona, te guste o no.
Ahora el caso opuesto: se revoca una licencia porque un sitio se desmanteló. Nadie está esperando esa respuesta. El sitio ya no existe. Lo único que importa es que la revocación acabe ocurriendo, no que alguien aguante un viaje de ida y vuelta a Statamic para enterarse.
Mismo dominio, mismo tipo de operación, dos respuestas distintas a «¿esto debería ser síncrono?». Este capítulo va de dar la respuesta correcta a esa pregunta, y de lo que pasa realmente después de despachar un job: si puede sobrevivir a ejecutarse dos veces, si sabe cuándo dejar de intentarlo y si alguien se entera cuando se cae calladamente. Un job que sólo funciona cuando no va nada mal no es un job en segundo plano. Es un fallo que aún no se ha disparado.
Que pertenece fuera de la peticion
Mira otra vez las dos operaciones de licencia. La creación devuelve datos que quien llama necesita de inmediato —key, domain, status— y todo eso tiene que estar en la respuesta HTTP. El borrado no devuelve nada que le importe. Esa es la prueba real: no «¿esto es lento?» sino «¿alguien necesita el resultado antes de que salga la respuesta?».
// app/Jobs/DeleteLicenseJob.php
final class DeleteLicenseJob implements ShouldBeEncrypted, ShouldQueue
{
use Queueable;
public int $tries = 3;
/** @var array<int, int> */
public array $backoff = [60, 300];
public ?int $timeout = 30;
public function __construct(
public string $key,
public ?string $driver = null,
public ?string $token = null,
) {
/** @var string $queue */
$queue = config('services.license.delete_queue', 'default');
$this->onQueue($queue);
}
public function handle(): void
{
if (is_string($this->driver) && $this->driver !== '') {
Config::set('services.license.default', $this->driver);
if (is_string($this->token) && $this->token !== '') {
Config::set("services.license.drivers.{$this->driver}.token", $this->token);
}
}
LicenseFacade::deleteLicense(key: $this->key);
}
}
El controlador que atiende el endpoint de borrado despacha este job y devuelve un MessageResponse de inmediato. Sin consultas periódicas, sin esperar a Statamic. El constructor merece atención: driver y token se capturan en el momento del despacho, del bearer de quien llama, y se reaplican dentro de handle() antes de que corra la facade. Los jobs se ejecutan en un proceso worker sin ninguna petición HTTP detrás, así que todo lo que el contexto de la petición aportaría normalmente (qué driver, qué credenciales) tiene que viajar con la carga del job. ShouldBeEncrypted en esta clase significa que esa carga —nombre de driver y token incluidos— se cifra en reposo en la tabla de la cola. Un job que lleva un token Bearer es un job que filtra un token Bearer si te olvidas de esa bandera.