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

Clase 110 — Colas de trabajo#

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

🎯 Objetivo#

Generar un informe, mandar un correo, redimensionar una imagen, llamar a un proveedor lento. Nada de eso tiene que pasar antes de contestar.

Hacerlo dentro de la petición cuesta tres cosas a la vez: quien pide espera, un fallo del proveedor se convierte en un error de la petición aunque lo importante ya estuviera guardado, y no hay forma de reintentar sin repetir la petición entera.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Un informe que tarda cuatrocientos milisegundos. Se pide, se contesta en milisegundos, y se consulta después.

El resultado final es el mismo con cola y sin ella. Lo único que las distingue es el reloj, así que el reloj es lo que se mide.

🧮 El contrato#

# Petición Qué comprueba
1 POST /tareas 202 con Location, y estado encolada
2 GET /tareas/{id} enseguida todavía no está hecho
3 GET /cola.json la respuesta no espera al trabajo
4 GET /cola.json y el trabajo sí tarda, medido de verdad
5 GET /tareas/{id} después ahora sí: terminada, con su resultado
6 GET /cola.json cómo encola cada uno y qué le falta

El caso 2 es la trampa que hace falta: sin él, una implementación que hiciera el trabajo dentro de la petición pasaría los casos 1 y 5 sin despeinarse.

<!-- 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
Cola de trabajo Sacar de la petición el trabajo que no tiene que ocurrir ahora: enviar el correo, generar el informe. La petición responde antes y el trabajo se ejecuta después, con reintentos.

🧰 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-110-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
Clase110.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 · no esperar la promesa, y un await que no sobra#

express/server.mjs — el encuadre:

 * La respuesta es contestar **202 Aceptado**, decir dónde mirar y hacer el
 * trabajo después. El código de estado importa: un 200 significa «hecho», y esto
 * no está hecho. Es la clase 015 aplicada donde más se nota.

Y lo que en Node no es lo que parece:

 * Y ahí está el detalle que confunde a mucha gente: esto NO es paralelismo. El
 * trabajo corre en el mismo bucle de eventos que atiende las peticiones. Sirve
 * porque lo que tarda es esperar —una consulta, una llamada de red— y no
 * calcular. Un trabajo que consuma procesador de verdad bloquea el servidor
 * igual, y entonces hace falta otro proceso.

Y un fallo que encontró el contrato y se queda escrito:

    // Una función `async` **no empieza a ser asíncrona hasta su primer `await`**:
    // todo lo que haya antes corre síncrono, dentro de la petición que la lanzó.
    // Sin esta línea, el estado ya era «en curso» cuando la respuesta salía, y el
    // caso que exige «encolada» fallaba.
    // Es un detalle pequeño con una consecuencia grande: si lo primero que hace
    // el trabajo es algo costoso y no hay un `await` antes, ese coste se lo come
    // la petición, y la cola no ha servido para nada.

FastAPI · una pieza con nombre, y su límite#

fastapi/main.py:

FastAPI es el único de los cuatro que trae una pieza con nombre para esto:
`BackgroundTasks`. Se declara como un parámetro más del manejador y se le añade
lo que hay que hacer después; el framework lo ejecuta **cuando la respuesta ya se
ha enviado**.
Es cómodo y hay que saber exactamente hasta dónde llega, porque su nombre invita
a confundirlo con una cola de verdad: la tarea vive en el proceso, no se
reintenta, no se puede consultar, y **si el proceso se reinicia desaparece**.
Para lo que sirve —mandar un correo después de contestar— es perfecta. Para lo
que no, la respuesta se llama Celery, y es otra pieza de infraestructura.

Spring Boot · la anotación que más se usa mal#

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

 *   - **Solo funciona a traves del proxy.** Llamar a un metodo `@Async` desde
 *     otro metodo de la MISMA clase no hace nada: se ejecuta igual de sincrono.
 *     Es el fallo numero uno con esta anotacion, y por eso aqui el trabajo vive
 *     en un `@Service` aparte.
 *   - **Hace falta `@EnableAsync`.** Sin ella, la anotacion se ignora en
 *     silencio: sin error, sin aviso, y todo sigue esperando.
 *   - **Usa un grupo de hilos.** Aqui si es paralelismo de verdad, al contrario
 *     que en Node o en Python, y eso significa que el grupo se puede agotar.

Y por qué el trabajador está en otra clase, dicho donde está:

     * No es un capricho de organizacion: `@Async` funciona porque Spring
     * envuelve el objeto en un proxy, y una llamada dentro de la misma clase no
     * pasa por el proxy. Ponerlo aqui haria que no ocurriera nada de lo que
     * promete la anotacion.

ASP.NET Core · el único con una cola de verdad en la biblioteca estándar#

aspnet-core/Program.cs:

// De los cuatro, es el único con **una cola de verdad en la biblioteca estándar**:
// `System.Threading.Channels`. No es una pieza del framework web, es del lenguaje,
// y hace exactamente lo que hace falta: una cola con un extremo por el que se
// escribe y otro por el que se lee, con espera sin bloquear.
// Y el que lee es un `BackgroundService`: un servicio con ciclo de vida propio,
// que arranca con la aplicación y se para con ella. Eso es lo que en Node es «no
// esperar la promesa» y en Spring es `@Async`, aquí escrito como lo que de verdad
// es: **un consumidor separado del productor**.

Con la decisión que hay que tomar y casi nadie toma:

// LA COLA. Sin límite aquí; en producción, un límite es obligatorio —si se
// encola más rápido de lo que se consume, sin límite se acaba la memoria y con
// límite se rechaza, que es mucho mejor sitio donde fallar.

Y el bucle del trabajador, bien escrito:

        // `ReadAllAsync` espera sin gastar procesador y termina solo cuando se
        // cierra la cola o se cancela el testigo. Es la forma correcta de
        // escribir un bucle de trabajador, y la que evita el `while (true)` con
        // `sleep` que casi todo el mundo escribe la primera vez.

🔬 Comparación#

Cómo se encola ¿Es paralelismo? Lo que ofrece la casa
Express una promesa que no se espera ❌ mismo bucle de eventos nada
FastAPI BackgroundTasks, parámetro del manejador ❌ mismo bucle una pieza con nombre
Spring Boot @Async en otra clase, con @EnableAsync ✅ grupo de hilos una anotación, con dos trampas
ASP.NET Core Channel + BackgroundService una cola de verdad, en el lenguaje

Y lo medido, que es lo mismo en los cuatro:

202 en decenas de milisegundos   ·   trabajo terminado a los ~450

Cuatro lecturas:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 110

Para verlo tú, la prueba de una línea que dice si tu alta espera o no:

curl -s -o /dev/null -w "%{http_code} %{time_total}\n" -X POST -H "content-type: application/json" -d '{"descripcion":"prueba"}' http://127.0.0.1:4100/tareas

Si el tiempo se parece al del trabajo, no hay cola: hay una promesa esperada.

🧪 Reto de transferencia#

  1. Busca tu petición más lenta. Mira qué hace y pregunta cuánto de eso tiene que ocurrir antes de contestar. Casi siempre es menos de lo que parece.
  2. Comprueba tus códigos de estado. Si algo se hace después de contestar y contestas 200, estás mintiendo. Cambiarlo a 202 es una línea.
  3. Averigua qué pasa si reinicias con la cola llena. Encola diez cosas, para el proceso y vuelve a arrancarlo. Si desaparecieron, ya sabes qué pieza te falta.

🔗 Enlaces#

Fuentes#