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

Clase 027 — El orden importa#

⬅️ 026 · 📚 Parte 2 · 🎓 Clases · 028 ➡️ Parte 2 — La tubería · Nivel 🟡 intermedio · Pista backendClase construida — 4 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Predecir el efecto del orden de registro, y descubrir que en dos de estos cuatro frameworks el orden de ejecución no es el orden de lectura.

🧩 La situación#

Tres capas —uno, dos, tres— registran su entrada. El manejador registra la suya. El resultado debe ser uno, dos, tres, manejador en los cuatro.

📖 Cómo se recorre una tubería#

No es una cola: es una pila. Cada capa envuelve a la siguiente, así que la petición entra de fuera adentro y la respuesta sale de dentro afuera:

        entra                     sale
  uno  ──────►                        ◄────── uno
    dos  ──────►                    ◄────── dos
      tres ──────► [manejador] ◄────── tres

Eso tiene una consecuencia práctica muy concreta: el código que escribas después de continuar se ejecuta al volver, con la respuesta ya generada. Ahí es donde se mide la duración, se añade una cabecera calculada o se cierra un recurso.

🧮 El contrato#

Petición Respuesta
GET /traza {"traza":["entra:uno","entra:dos","entra:tres","manejador"]}
GET /traza otra vez lo mismo

El segundo caso comprueba algo que no parece de esta clase y resultó serlo: que la traza no se acumula entre peticiones.

🔍 Lo que esta clase destapó#

La primera versión guardaba la traza en una variable del módulo. El resultado fue este:

✘ express  json {"traza":["sale:uno","sale:dos","sale:tres","entra:uno",...]}

La traza de una petición aparecía en la respuesta de la siguiente. Estado global compartido entre peticiones, que en un servidor es un fallo de corrección y, cuando el estado contiene datos de usuario, un fallo de seguridad.

La solución es la que usan las cuatro implementaciones: guardar el estado en la petición, no en el módulo.

Framework Almacén por petición
Express propiedad en el objeto peticion
FastAPI peticion.state
Spring Boot atributos de la petición (setAttribute)
ASP.NET Core contexto.Items

Los cuatro tienen uno, y no por casualidad: es el mecanismo correcto para cualquier dato que pertenezca a una petición concreta — el usuario autenticado, el identificador de correlación de la clase 030, el inquilino en una aplicación multiempresa.

<!-- 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
Almacén por petición El sitio donde una capa deja datos para las siguientes sin usar variables globales: peticion.traza en Express, contexto.Items en .NET, peticion.state en Starlette, los atributos de la petición en el mundo de los servlets. Cuatro nombres, un problema.

🧰 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-027-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
Clase027.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#

Las cuatro registran tres capas y guardan la traza en la petición. Y las cuatro declaran el orden de una manera distinta — una de ellas al revés de lo que parece.

Antes de mirar el orden, mira dónde vive la traza, porque es la parte que este contrato destapó en el primer intento.

Express · express/server.mjs — el orden es el de registro#

function capa(nombre) {
  return (peticion, respuesta, siguiente) => {
    peticion.traza ??= [];
    peticion.traza.push(`entra:${nombre}`);
    siguiente();
app.use(capa("uno"));
app.use(capa("dos"));
app.use(capa("tres"));

Lo que se lee de arriba abajo se ejecuta de fuera adentro.

Y el detalle que importa más que el orden: la traza vive en la petición, no en una variable del módulo. Con estado global, dos peticiones simultáneas mezclarían sus trazas — y este contrato lo destapó en el primer intento del laboratorio, que es exactamente el tipo de fallo que en producción aparece solo bajo carga.

Lo que se escriba después de siguiente() se ejecuta al volver, en orden inverso. No entra en el cuerpo de la respuesta porque para entonces ya salió.

ASP.NET Core · aspnet-core/Program.cs — igual, y el más explícito#

void Capa(string nombre) => app.Use(async (contexto, siguiente) =>
{
    if (!contexto.Items.TryGetValue("traza", out var valor))
    {
        valor = new List<string>();
        contexto.Items["traza"] = valor;
    }
    ((List<string>)valor!).Add($"entra:{nombre}");
    await siguiente();
Capa("uno");
Capa("dos");
Capa("tres");

Mismo modelo que Express: el orden es el de las llamadas. contexto.Items es el almacén por petición — nace y muere con ella, igual que peticion.traza.

Es el modelo más predecible de los cuatro, y por eso la documentación de ASP.NET Core insiste tanto en el orden de las llamadas Use: es la única declaración que hay.

FastAPI · fastapi/main.pyal revés#

app.middleware("http")(capa("tres"))
app.middleware("http")(capa("dos"))
app.middleware("http")(capa("uno"))

Léelo dos veces: para obtener el orden observable uno, dos, tres hay que registrarlas al revés.

Las capas de Starlette se apilan: la última registrada envuelve a las anteriores, así que se ejecuta primero. No es un capricho — es la consecuencia natural de construir la pila envolviendo la aplicación una y otra vez.

Y es la trampa número uno de quien viene de Express. Lo peor es que no produce un error: produce un orden distinto, silencioso, que solo se nota cuando la capa de autenticación acaba ejecutándose después de la que necesitaba saber quién eres.

        if not hasattr(peticion.state, "traza"):
            peticion.state.traza = []
        peticion.state.traza.append(f"entra:{nombre}")

peticion.state es el almacén por petición de Starlette. Mismo papel, tercer nombre.

Spring Boot · spring-boot/…/Aplicacion.java — ni una cosa ni la otra#

    private static FilterRegistrationBean<Filter> registrar(String nombre, int orden) {
        FilterRegistrationBean<Filter> registro = new FilterRegistrationBean<>(new Capa(nombre));
        registro.setOrder(orden);
        return registro;
    }
    @Bean
    public FilterRegistrationBean<Filter> uno() {
        return registrar("uno", 1);
    }

El orden se declara con un número, no se deduce de nada — ni de la posición en el archivo, ni del momento del registro.

Es más verboso y elimina una dependencia peligrosa: el orden en que Spring descubre los componentes no está garantizado. Dejar el orden implícito sería dejar el comportamiento al azar, y la clase 002 ya avisó de que en Spring el descubrimiento es un examen del classpath y no una secuencia de llamadas.

            List<String> traza = (List<String>) peticion.getAttribute("traza");
            if (traza == null) {
                traza = new ArrayList<>();
                peticion.setAttribute("traza", traza);
            }
            traza.add("entra:" + nombre);
            cadena.doFilter(peticion, respuesta);

Los atributos de la petición son el almacén por petición del mundo de los servlets. Cuarto nombre para lo mismo: peticion.traza, contexto.Items, peticion.state y getAttribute. Cuando cuatro frameworks de cuatro ecosistemas inventan la misma pieza, es que el problema es del dominio y no de ninguno de ellos.

🔬 Comparación#

Framework Orden de ejecución Riesgo
ASP.NET Core el de registro ninguno; es lo esperable
Express el de registro ninguno
Spring Boot el número declarado olvidar declararlo deja el orden indefinido
FastAPI el inverso al de registro leer el código y equivocarse

🎯 Por qué el orden decide comportamiento#

No es una curiosidad. Cuatro ejemplos donde el orden cambia el resultado:

Si pones… antes de… Pasa que…
autenticación limitación de tasa gastas CPU autenticando peticiones que ibas a rechazar
compresión caché cacheas lo comprimido y no puedes servirlo a quien no lo admite
manejo de errores el resto no captura los errores de las capas que van después
registro todo lo demás no ves el estado final, porque otra capa lo cambió al volver

La tercera es la más común: un manejador de errores registrado el primero no ve los errores de las capas posteriores, porque en la ida ya pasó de largo.

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 027

🧪 Reto de transferencia#

Añade a cada capa un registro al volversale:uno, etc.— y comprueba en la salida del proceso que el orden es tres, dos, uno. No entra en el cuerpo de la respuesta porque para entonces ya se envió, y entender por qué es el objetivo del reto.

🔗 Enlaces#

Fuentes#