·4 min de lectura·Mounaji Studio

Reintentos sin duplicados: la clave de idempotencia sale del contenido, no del reloj

Un reintento es normal en cualquier fetch; un post duplicado en el feed de una marca, no. Cómo armar una Idempotency-Key para que el segundo clic devuelva lo ya creado, y por qué la validación tiene que ser todo o nada cuando publicas en varios canales.

engineeringapisreliability

También en: English

Un reintento es lo más normal que hace un fetch. Se corta la conexión a mitad, el usuario aprieta el botón otra vez, una cola reentrega un trabajo. Lo que no es normal es que el mismo post aparezca dos veces en el feed de una marca.

Dos reglas chicas en un publicador alcanzan para evitar tanto el duplicado como el envío a medias. Van en el orden en que te topas con los problemas.

Regla 1: la clave sale del contenido

Dos carriles. Si la clave sale del reloj o de un UUID, cada clic genera una clave distinta y se crean dos publicaciones. Si sale de la pieza, la versión y el canal, el reintento genera la misma clave y devuelve lo que ya existe.

Una Idempotency-Key le dice al servidor: «si ya viste esta clave, devuelve lo que creaste la primera vez en vez de crearlo de nuevo». Que funcione depende por completo de dónde sale la clave.

  • Del reloj (Date.now()): cada intento tiene una clave distinta, así que el servidor ve dos pedidos y crea dos cosas. El reintento es justo el problema que la clave tenía que resolver.
  • De un UUID aleatorio por llamada: la misma falla. El segundo clic genera un segundo UUID.

La clave tiene que ser una función pura de lo que se está enviando. En un publicador se puede armar con la pieza, su versión y el canal, más el número de card cuando la subida es una sola imagen:

const claveDe = (piezaId, version, ...resto) =>
  ['estudio', piezaId, `v${version}`, ...resto].join(':');

claveDe('pieza-42', 3, 'instagram');  // estudio:pieza-42:v3:instagram
claveDe('pieza-42', 3, 'card-2', 'jpeg');  // estudio:pieza-42:v3:card-2:jpeg

Misma pieza, misma versión, mismo canal: misma clave, siempre. Aprietas dos veces y la segunda devuelve lo que creó la primera. Editas la pieza y cambia la versión, así que cambia la clave y la versión nueva sale como la cosa nueva que es.

Cada subida y cada pieza llevan su propia clave. Si la red se corta a mitad de un carrusel de tres imágenes y vuelves a apretar, las que ya subieron se devuelven, no se suben de nuevo.

Una clave no alcanza: la pieza ya mandada tiene que pedirlo

La clave te protege de un reintento dentro de un intento. No te protege de que alguien mande la misma pieza la semana que viene por error. Para eso el publicador guarda el historial de lo enviado, y una pieza con un envío previo se rechaza salvo que la llamada lo pida explícitamente:

function puedeMandar(pieza, { otraVez = false, envios = [] } = {}) {
  // ...sólo sale lo aprobado...
  if (envios.length && !otraVez) {
    return `ya se mandó — para mandarla de nuevo, otraVez: true`;
  }
  return null;
}

Volver a mandar está permitido; lo que no puede pasar es que ocurra por descuido.

Regla 2: la validación es todo o nada

Tres variantes se validan antes de tocar la red. Instagram y email son válidas pero la tercera tiene la leyenda demasiado larga, así que no sale ninguna y se listan todos los problemas. Una vez que empezó el envío, un fallo no frena a los otros canales.

La otra mitad del problema aparece cuando un botón publica en varios canales a la vez. Supón que Instagram y el email pasan la validación pero la tercera variante tiene un caption demasiado largo. Si mandas las dos válidas y reportas el error de la tercera, la marca queda hablando en dos redes y muda en la tercera, y nadie lo decidió.

Por eso el publicador valida todas las variantes antes de tocar la red. Si alguna tiene problemas, no sale ninguna; la respuesta lista todos, los corriges en pantalla y vuelves a apretar:

const malas = previa.variantes.filter((v) => !v.ok);
if (previa.problemas.length || malas.length || !previa.variantes.length) {
  return { ok: false, rechazo: /* cada problema, con su canal */ };
}

El test de esta regla se probó primero en rojo: rompimos una variante a propósito y comprobamos que la llamada devolvía un rechazo y no hacía ninguna llamada a la red. Un chequeo que nunca falló no demostró que funcione.

Hay un límite que importa. El todo o nada rige para la validación, antes de enviar nada. Una vez que empiezan los envíos, un fallo en un canal no frena a los otros, y el resultado dice qué canal falló y por qué.

Las dos reglas, en corto

  1. Clave = f(contenido): pieza + versión + canal (+ card). Nunca el reloj, nunca un UUID aleatorio. Reintentar devuelve lo que ya existe.
  2. Valida todo antes de enviar algo: si una variante falla, no sale ninguna.

¿Tienes un caso donde quieres ver esto andando? Cuéntanos qué hace tu negocio y lo armamos sobre tu material.

Comentarios y correcciones

Chat with us