Ir al contenido principal
Laravel, shipping fast.

La idempotencia es mitad servidor y mitad cliente. La mitad del cliente tiene que estar por escrito, porque un consumidor que reintenta lo que no debe, o con claves nuevas cada vez, no obtiene ningún beneficio.

  • Genera una clave por operación, antes del primer intento, y reutilízala en cada reintento de esa operación.
  • Reintenta ante: un error de red o un timeout, un 5xx, un 429 tras la espera de Retry-After, y un 409 cuyo código sea request_in_progress.
  • No reintentes ante: un 4xx distinto de esos. Un 422 volverá a ser un 422.
  • Espera cada vez más, con jitter. Duplica la espera entre intentos y añade una parte aleatoria, para que cien clientes que fallaron juntos no vuelvan todos en el mismo segundo.
  • Detente. Ríndete tras un número fijo de intentos y haz visible el fallo.

Tu propio driver del Capítulo 3 es un cliente del proveedor, y más estricto que esta lista: reintenta solo un 429, porque no puede enviarle al proveedor una clave de idempotencia y por tanto no puede saber si es seguro repetir cualquier otra cosa. Esa es la diferencia que marca una clave. Con una, un consumidor puede reintentar casi todo. Sin ella, casi nada.

Probarlo

// tests/Feature/IdempotencyTest.php
it('creates one license for two requests', function () {
    Http::fake(['*/sites*' => Http::sequence()
        ->push(['data' => []])
        ->push(['data' => $this->license], 201),
    ]);

    $key = (string) Str::uuid();

    $send = fn () => $this->postJson(
        '/api/licenses',
        ['name' => 'Acme', 'domain' => 'acme.test'],
        ['Idempotency-Key' => $key],
    );

    $first = $send()->assertCreated();

    $send()->assertCreated()
        ->assertHeader('Idempotent-Replayed', 'true')
        ->assertExactJson($first->json());

    Http::assertSentCount(2);
});

$this->license es una respuesta de ejemplo del proveedor. Se fija en un beforeEach, que además autentica a un consumidor con un token de proveedor y la habilidad licenses:write. El Capítulo 12 tiene un helper para eso.

La aserción que importa es la última. Entraron dos peticiones. Al proveedor se lo llamó dos veces, una para la lista y otra para crear la licencia, y esas dos llamadas pertenecen a la primera petición. La segunda petición nunca llegó al proveedor.

Escribe también las otras dos: la misma clave con un cuerpo distinto recibe un 422, y una petición que falló no deja nada detrás, de modo que el reintento se ejecuta de verdad.

Resumen del capítulo 8

  • Un consumidor que ve un timeout va a reintentar, y hace bien. Diseña para ello.
  • GET, PUT y DELETE son idempotentes solo si tu código cumple la promesa. Un segundo borrado es un éxito.
  • Para POST, acepta una Idempotency-Key: haz el trabajo una vez, recuerda la respuesta, repítela.
  • Asocia cada clave a su consumidor. Rechaza la misma clave con un cuerpo distinto. Responde 409 mientras la primera está en curso.
  • Recuerda solo las respuestas que tuvieron un efecto.
  • La clave protege tu API, no al proveedor que hay detrás. Pasa la clave hacia delante, o converge en una clave natural.
  • Documenta la mitad del cliente: una clave por operación, qué reintentar, esperas crecientes con jitter y cuándo detenerse.

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