Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 112 — Reintentos e idempotencia#

⬅️ Clase 111 · 📚 Parte 8 · 🎓 Clases · 113 ➡️ Parte 8 — Tiempo real y segundo plano · Nivel 🔴 avanzado · Pista tiempo-real (Tiempo real y segundo plano) ✅ Clase construida — 4 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Las clases anteriores dejaron el sistema lleno de sitios donde algo se reintenta: un cliente que reconecta, una cola que vuelve a coger un trabajo, un temporizador que dispara dos veces.

Y hay uno más, el más común de todos: una respuesta que se perdió. El cobro se hizo, la respuesta no llegó, y quien pidió no tiene forma de distinguir eso de que no se hiciera. Va a reintentar. Y con razón.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Un cobro de treinta. Se pide dos veces con la misma clave, y el segundo tiene que devolver lo mismo sin volver a cobrar.

Y para comparar, tres peticiones sin clave: tres cobros. Que eso pase no es un fallo del servidor. Es lo correcto.

🧮 El contrato#

# Petición Qué comprueba
1 POST /cobros con clave se cobra, y no viene marcado como repetido
2 POST /cobros con la misma clave no vuelve a cobrar, y lo dice
3 GET /cobros un solo cobro, de treinta
4 GET /idempotencia.json tres con clave: 1. Tres sin clave: 3.
5 GET /idempotencia.json lo que falla dos veces sale al tercer intento
6 GET /idempotencia.json dónde guarda la clave y qué hace falta

El caso 4 publica los dos números juntos a propósito:

        "json_contiene": {
          "con_clave_peticiones": 3,
          "con_clave_cobros": 1,
          "sin_clave_peticiones": 3,
          "sin_clave_cobros": 3,
          "la_clave_evita_el_duplicado": true
        }

El 3 de abajo no es un defecto que haya que arreglar en el servidor: es la demostración de por qué la clave la tiene que poner quien pide.

<!-- generado: fichas -->

📖 Las palabras que esta clase define#

Si alguna de estas no te dice nada todavía, esta es la clase donde se aprende. Las definiciones viven en el glosario, que reúne las del programa entero.

Palabra Qué significa
Reintento Volver a ejecutar un trabajo que falló. Solo es seguro si el trabajo es idempotente: reintentar un cobro no idempotente cobra dos veces. Y necesita espera creciente, o el reintento tumba lo que ya estaba caído.

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Express framework web de Node.js (JavaScript) 2010 MIT OpenJS Foundation
FastAPI framework web de Python (Python) 2018 MIT proyecto independiente
Spring Boot framework de aplicación de JVM (Java) 2014 Apache-2.0 Broadcom/VMware y colaboradores
ASP.NET Core framework web de .NET (C#) 2016 MIT Microsoft y .NET Foundation

🔧 Express#

Definió el modelo de middleware encadenado que copiaron casi todos los frameworks de Node.js. Minimalista no significa biblioteca: posee el bucle de peticiones.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 FastAPI#

Deriva validación, serialización y documentación OpenAPI de las anotaciones de tipo. Demostró que el tipado opcional de Python podía ser infraestructura, no adorno.

Arrancarla suelta, sin el verificador:

PORT=3000 python -m uvicorn main:app --host 127.0.0.1 --port 3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
main.py código Python
requirements.txt dependencias de Python, una por línea, con versión fijada

🔧 Spring Boot#

Autoconfiguración y servidor incrustado sobre Spring. Convirtió un framework famoso por su configuración XML en uno de arranque inmediato.

Preparar sus dependencias, dentro de su directorio:

mvn -q -B package -DskipTests

Arrancarla suelta, sin el verificador:

PORT=3000 java -jar target/clase-112-1.0.0.jar --server.port=3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
pom.xml manifiesto de Maven: el proyecto, su Java, sus dependencias y cómo se empaqueta
src/main/java/labs/Aplicacion.java código Java

🔧 ASP.NET Core#

Reescritura multiplataforma y de código abierto de la pila web de Microsoft. Sus API mínimas trajeron el estilo de los microframeworks al ecosistema .NET.

Preparar sus dependencias, dentro de su directorio:

dotnet build -c Release --nologo -v quiet

Arrancarla suelta, sin el verificador:

PORT=3000 dotnet run -c Release --no-build --urls http://127.0.0.1:3000

Qué hay dentro de su directorio:

Archivo Qué es
Clase112.csproj proyecto de .NET: el marco de destino y las dependencias
Program.cs código C#
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

Express · las dos respuestas, separadas#

express/server.mjs — el caso que hay que tener en la cabeza:

 * Ese es el caso que hay que tener en la cabeza. El cobro se hizo, la respuesta
 * no llegó, y quien pidió no tiene forma de distinguir eso de que no se hiciera.
 * Va a reintentar. Y con razón.

Y la distinción que ordena la clase entera:

 *   - **Reintentar bien**: espera creciente, un tope, y no reintentar lo que no
 *     tiene arreglo —un 400 no mejora por repetirlo—.
 *   - **Que reintentar no haga daño**: es lo de verdad importante, y consiste en
 *     que quien pide traiga una clave, y el servidor recuerde qué contestó a esa
 *     clave.
 * Lo segundo se llama idempotencia y es la única de las dos que arregla el
 * problema. Reintentar bien sin ella solo reparte el daño mejor.

Y lo que casi todo el mundo implementa a medias:

 * Guarda, por clave, **la respuesta que ya se dio**. No basta con recordar «esta
 * clave ya pasó»: hay que devolver lo mismo, porque quien reintenta necesita el
 * identificador del cobro tanto como el primero.
 * Y tiene que caducar. Una clave guardada para siempre es una fuga de memoria
 * con forma de tabla; una que caduca demasiado pronto deja pasar un reintento
 * tardío. Un día suele ser el valor razonable, y hay que elegirlo a propósito.

Y por qué sin clave el servidor tiene que cobrar:

  // SIN CLAVE NO HAY NADA QUE HACER. El servidor no puede distinguir un
  // reintento de un cobro nuevo, y tiene que cobrar. Es correcto y es el motivo
  // de que la clave la ponga QUIEN PIDE: solo él sabe si es lo mismo.

FastAPI · por qué esto no lo trae ningún framework#

fastapi/main.py:

Ninguno de los cuatro frameworks de esta clase trae idempotencia. Y no es una
carencia: **la clave la tiene que poner quien pide**, porque solo él sabe si dos
peticiones son el mismo intento. Lo único que el servidor puede hacer es
recordar qué contestó a cada clave, y eso son diez líneas.

Spring Boot · una anotación para la mitad del problema#

spring-boot/src/main/java/labs/Aplicacion.java:

 * Spring tiene una pieza para la mitad del problema: `@Retryable`, de Spring
 * Retry, con espera creciente y tope declarados en la anotacion. Es la respuesta
 * mas completa de los cuatro para reintentar bien, y **no toca la otra mitad**.
 * Y la otra mitad es la que importa. Reintentar bien reparte el dano mejor;
 * lo que lo evita es que reintentar no haga nada la segunda vez, y eso no lo
 * puede resolver ninguna anotacion: **la clave la tiene que poner quien pide**,
 * porque solo el sabe si dos peticiones son el mismo intento.

ASP.NET Core · la mejor caja de herramientas para reintentar#

aspnet-core/Program.cs:

// .NET tiene la mejor pieza de los cuatro para la mitad de reintentar: la
// biblioteca de resiliencia —lo que antes se llamaba Polly— con espera creciente,
// fluctuación, tope y cortacircuitos, todo declarado. Está tan integrada que
// `AddStandardResilienceHandler` te la pone en un cliente HTTP con una línea.
// Y no toca la otra mitad. Reintentar bien reparte el daño mejor; **lo que lo
// evita es que reintentar no haga nada la segunda vez**, y eso no lo puede
// resolver ninguna biblioteca de cliente: la clave la tiene que poner quien pide,
// porque solo él sabe si dos peticiones son el mismo intento.

🔬 Comparación#

Para reintentar bien Para que no haga daño
Express nada: a mano nada, y son diez líneas
FastAPI nada: a mano, o tenacity nada, y son diez líneas
Spring Boot @Retryable, con espera y tope declarados nada
ASP.NET Core biblioteca de resiliencia, con cortacircuitos nada

Y lo medido, idéntico en los cuatro:

3 peticiones con la misma clave  →  1 cobro
3 peticiones sin clave           →  3 cobros
lo que falla dos veces           →  bien al tercer intento

Cuatro lecturas:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 112

Para probarlo tú, la comprobación en dos líneas: manda el mismo cobro dos veces con la misma clave y mira el libro.

curl -s -X POST -H "content-type: application/json" -H "idempotency-key: k1" -d '{"importe":30}' http://127.0.0.1:4100/cobros

Repite el comando tal cual y luego pide /cobros. Si el total es sesenta, no hay idempotencia.

🧪 Reto de transferencia#

  1. Busca tus escrituras que cobran, mandan o crean. Para cada una, pregunta qué pasa si llega dos veces. Las que dan igual no necesitan nada.
  2. Mira si tus clientes mandan clave. Si no la mandan, la idempotencia del servidor no sirve de nada: es un acuerdo entre los dos.
  3. Comprueba tu política de reintento. Si reintenta un 400, estás gastando peticiones en algo que no va a funcionar nunca.

🔗 Enlaces#

Fuentes#