Clase 027 — El orden importa#
⬅️ 026 · 📚 Parte 2 · 🎓 Clases · 028 ➡️ Parte 2 — La tubería · Nivel 🟡 intermedio · Pista
backend✅ Clase construida — 4 implementaciones verificadas contracontrato.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] ◄────── tresEso 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.
- Documentación oficial: https://expressjs.com/
- Estado en el catálogo: activo
- Versión que ejecuta esta clase:
express ^5.1.0 - Necesita en el PATH:
node,pnpm
Preparar sus dependencias, dentro de su directorio:
pnpm install --silent --ignore-scriptsArrancarla suelta, sin el verificador:
PORT=3000 node server.mjsQué 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.
- Documentación oficial: https://fastapi.tiangolo.com/
- Estado en el catálogo: activo
- Versión que ejecuta esta clase:
fastapi==0.121.3, uvicorn==0.40.0 - Necesita en el PATH:
python
Arrancarla suelta, sin el verificador:
PORT=3000 python -m uvicorn main:app --host 127.0.0.1 --port 3000Qué 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.
- Documentación oficial: https://spring.io/projects/spring-boot
- Estado en el catálogo: activo
- Versión que ejecuta esta clase:
spring-boot 3.5.6, Java 21, spring-boot-starter-web - Necesita en el PATH:
java,mvn
Preparar sus dependencias, dentro de su directorio:
mvn -q -B package -DskipTestsArrancarla suelta, sin el verificador:
PORT=3000 java -jar target/clase-027-1.0.0.jar --server.port=3000Qué 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.
- Documentación oficial: https://learn.microsoft.com/aspnet/core/
- Estado en el catálogo: activo
- Versión que ejecuta esta clase:
net10.0 - Necesita en el PATH:
dotnet
Preparar sus dependencias, dentro de su directorio:
dotnet build -c Release --nologo -v quietArrancarla suelta, sin el verificador:
PORT=3000 dotnet run -c Release --no-build --urls http://127.0.0.1:3000Qué 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.mjsdice 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.py — al 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#
- Suponer que el orden de lectura es el de ejecución. En FastAPI no lo es.
- No declarar el orden en Spring. Lo indefinido funciona hasta que deja.
- Guardar estado de petición en una variable global. Es el fallo que esta clase destapó.
- Registrar el manejador de errores demasiado pronto.
- Poner la capa cara antes de la que descarta.
✅ Verificación#
node scripts/run-class.mjs 027🧪 Reto de transferencia#
Añade a cada capa un registro al volver —sale: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#
- [gof-design-patterns] Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns. Addison-Wesley, 1994. ISBN 9780201633610 — https://openlibrary.org/isbn/9780201633610
- [ousterhout-philosophy] Ousterhout, John. A Philosophy of Software Design. Yaknyam Press, 2018. ISBN 9781732102200 — https://openlibrary.org/isbn/9781732102200