Ir al contenido principal
Laravel, shipping fast.

El middleware de arriba deja pasar una petición sin clave. Es la manera suave de introducir la función: los consumidores existentes siguen funcionando, y los que se preocupan pueden adoptarla.

Para un endpoint donde un duplicado cuesta dinero real, haz la clave obligatoria y responde 400 cuando falte. Un consumidor obligado a enviar una clave ha sido obligado a pensar en los reintentos. Para la License API, donde una licencia duplicada es una molestia y no una factura, opcional es el valor por defecto correcto.

En cualquier caso, la clave misma está restringida, como hace el middleware: un UUID o nada.

El caso que esto no resuelve

Sé honesto contigo mismo sobre lo que garantiza el middleware. Garantiza que tu API ejecuta el trabajo una vez por clave. No puede garantizar lo que pasó en el proveedor.

Sigue el peor caso. El primer intento llega a Statamic, Statamic crea la licencia, y la conexión con Statamic agota el tiempo antes de que vuelva la respuesta. Tu API devuelve un 504. No se guardó nada, correctamente, porque tu API no sabe lo que pasó. El consumidor reintenta con la misma clave. El trabajo se ejecuta otra vez. Statamic crea una segunda licencia.

La clave no puede arreglar esto, porque la incertidumbre está un salto más allá. Hay dos respuestas reales.

Pasa la clave hacia delante. Si el proveedor acepta una clave de idempotencia propia, envíale la del consumidor. La garantía se extiende entonces hasta el lugar donde de verdad se crea la licencia.

Mira antes de crear. Si no la acepta, comprueba lo que ya existe. Una licencia para acme.test que este consumidor ya tiene es la respuesta a la petición, no un conflicto con ella. Con eso, store tiene su forma. Los capítulos 18 y 19 le añaden una cosa cada uno, y el Capítulo 19 lo imprime tal como queda al final:

// app/Http/Controllers/LicenseController.php
public function store(
    StoreLicenseRequest $request,
): JsonResponse {
    $consumer = $request->user();
    $domain = $request->validated('domain');
    $held = Licenses::all();

    $license = $this->holding($held, $domain);

    if ($license === null) {
        if ($held->count() >= $consumer->license_limit) {
            throw new LicenseLimitReached;
        }

        $license = Licenses::create(
            name: $request->validated('name'),
            domain: $domain,
        );
    }

    return LicenseResource::make($license)
        ->response()
        ->setStatusCode(Response::HTTP_CREATED);
}
/** @param  Collection<int, License>  $held */
private function holding(
    Collection $held,
    string $domain,
): ?License {
    foreach ($held as $license) {
        if (in_array($domain, $license->domains, true)) {
            return $license;
        }
    }

    return null;
}

La lista se obtiene una vez y se usa dos: para buscar el dominio, y para el límite del Capítulo 7. El orden importa. A un consumidor que está en su límite y reintenta una creación que ya salió bien hay que entregarle su licencia, no un 409, así que la búsqueda va antes que el límite.

El dominio es una clave natural: un valor del mundo real que ya identifica la cosa. Un segundo intento encuentra lo que hizo el primero y converge en ello. El Capítulo 10 usa la misma idea para los jobs.

Siempre que exista una clave natural del mundo real, prefiérela. Una clave de idempotencia protege contra los reintentos que un consumidor decidió etiquetar. Una clave natural protege contra todos.

No se pudo cargar el audio. Inténtalo de nuevo en un momento.