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

Clase 008 — Leer la documentación oficial y el código fuente#

⬅️ 007 · 📚 Parte 0 · 🎓 Clases · 009 ➡️ Parte 0 — El método: qué es un framework y cómo se compara · Nivel 🟢 introductorio · Pista backend (Backend y API) ✅ Clase construida — 3 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Encontrar la respuesta en la fuente primaria antes que en un tutorial.

Y no como consejo: cada implementación de esta clase abre el framework que tiene instalado y contesta cuatro preguntas sobre él con lo que encuentra dentro. Ninguna respuesta está escrita a mano.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Tienes una duda concreta sobre tu framework. Escribes la pregunta en un buscador y el primer resultado es un artículo de hace cuatro años, escrito para la versión anterior, con un ejemplo que ya no compila.

Mientras tanto, el código que responde a tu pregunta está en tu disco, a un cat de distancia, y la documentación de tu versión exacta está enlazada desde el propio paquete.

Esta clase hace esas cuatro consultas en tres frameworks y devuelve las respuestas por HTTP. La cuarta separa los ecosistemas en dos grupos.

🧮 El contrato#

# Petición Qué comprueba
1 GET /preguntas las cuatro preguntas, iguales en las tres
2 GET /pregunta/version leida_del_paquete: true
3 GET /pregunta/documentacion respondida: true
4 GET /pregunta/donde-vive existe: true
5 GET /pregunta/codigo-fuente respondida: true
6 GET /pregunta/cual-es-mas-rapido 404 PREGUNTA_DESCONOCIDA

Fíjate en lo que el contrato no exige.

En el caso 2 no exige que la versión instalada coincida con la declarada, porque muchas veces no coincide y forzarlo obligaría a mentir. En el caso 3 no exige que la dirección salga del paquete, porque en la JVM no sale. Y en el 5 no exige que haya código fuente, porque en la JVM no lo hay.

Un contrato que exigiera esas tres cosas parecería más estricto y sería menos verdadero. Lo que el contrato exige es que cada implementación conteste con lo que hay, incluido cuando lo que hay es un «no».

Y el caso 6 cierra la puerta a lo contrario: preguntar algo que no está en la lista devuelve un 404, no una aproximación.

<!-- generado: fichas -->

🧰 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

🔧 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-008-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

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 · express/server.mjs#

Cómo se localiza el framework, sin suponer nada:

const manifiestoDeExpress = require.resolve("express/package.json");
const raizDeExpress = path.dirname(manifiestoDeExpress);
const metadatos = JSON.parse(readFileSync(manifiestoDeExpress, "utf8"));
 * `require.resolve` devuelve la ruta del archivo que Node cargaría de verdad.
 *
 * No es lo mismo que suponer `node_modules/express`: con enlaces simbólicos,
 * espacios de trabajo o versiones anidadas, el que se carga puede estar en otro
 * sitio. Preguntar al resolvedor es la única respuesta fiable.

En este repositorio la diferencia se ve: la ruta real es node_modules/.pnpm/express@5.2.1/node_modules/express, no node_modules/express. Quien la hubiera escrito a mano se habría equivocado.

La versión que se pidió frente a la que hay:

const rangoDeclarado = JSON.parse(readFileSync("package.json", "utf8")).dependencies.express;
    declarada_en_el_proyecto: rangoDeclarado,
    instalada: metadatos.version,

Declarado ^5.1.0. Instalado 5.2.1. Las dos cosas son correctas —el acento circunflejo admite versiones menores nuevas— y solo una de las dos identifica lo que se está ejecutando.

    por_que_importa:
      "un rango no identifica lo que se ejecuta; en un informe de error solo vale la versión exacta",

La documentación, tomada del paquete:

    sitio: metadatos.homepage ?? "no lo declara",
    repositorio: metadatos.repository?.url ?? metadatos.repository ?? "no lo declara",
    incidencias: metadatos.bugs?.url ?? "no lo declara",
    licencia: metadatos.license,
    por_que_importa:
      "el buscador ordena por popularidad; el paquete declara dónde está la verdad de ESTA versión",

Y la cuarta pregunta, que en Node tiene una respuesta cómoda:

   * En Node la respuesta es sí, y es fácil olvidar lo raro que es. El paquete
   * que se descarga TRAE EL CÓDIGO, no un compilado. Se puede abrir, poner un
   * `console.log` y volver a ejecutar.

Siete archivos de código, el primero index.js, en una ruta que la propia respuesta te da hecha.

FastAPI · fastapi/main.py#

La misma idea con las herramientas de Python:

RAIZ = Path(fastapi.__file__).parent
# No es lo mismo que suponer una ruta de `site-packages`: con entornos
# virtuales, instalaciones editables o varias versiones, el que se importa puede
# estar en otro sitio. Preguntarle al modulo es la unica respuesta fiable.

Y aquí sale el hallazgo más incómodo de la clase.

requirements.txt fija fastapi==0.121.3. La versión instalada en la máquina donde se escribió esto era 0.135.3:

        "aviso": (
            "coinciden"
            if coincide
            else "NO COINCIDEN: `requirements.txt` pide una version y el entorno tiene otra"
        ),
        "por_que_pasa": (
            "pip instala en un entorno COMPARTIDO y `requirements.txt` es un deseo, no un "
            "hecho: mientras nadie ejecute `pip install -r`, el archivo y la maquina "
            "pueden decir cosas distintas. Un archivo de bloqueo por proyecto —lo que hacen "
            "pnpm, Cargo o Bundler— cierra ese hueco"
        ),

Es una diferencia de ecosistema, no un descuido: pnpm instala dentro del proyecto y respeta un archivo de bloqueo; pip instala en un entorno compartido y requirements.txt solo describe lo que se quería. Por eso existen los entornos virtuales, y por eso olvidarlos cuesta tan caro.

La documentación, con la construcción moderna de Python:

    datos = metadata.metadata("fastapi")
    urls = {}
    for entrada in datos.get_all("Project-URL") or []:
        etiqueta, _, direccion = entrada.partition(",")
        urls[etiqueta.strip().lower()] = direccion.strip()
    `Project-URL` es una lista de pares «etiqueta, direccion» y es donde los
    paquetes modernos de Python ponen su documentacion. `Home-page` es el campo
    antiguo, que muchos ya no rellenan.

Y una ventaja que Python tiene sobre los otros dos:

        "ademas": "inspect.getsource(fastapi.FastAPI.get) devuelve el cuerpo del metodo sin salir del interprete",

No solo el código está en disco: el intérprete te lo entrega desde dentro, sin abrir un editor.

Spring Boot · spring-boot/…/Aplicacion.java — donde la respuesta cambia#

Está en el elenco por la cuarta pregunta, y conviene leer las tres primeras para llegar a ella.

Localizar el framework, preguntándole a la máquina virtual:

    private static String origenDe(Class<?> clase) {
        try {
            return clase.getProtectionDomain().getCodeSource().getLocation().toString();
     * `getProtectionDomain().getCodeSource()` devuelve el archivo del que se
     * cargo la clase. No hay que suponer rutas ni buscar en el disco: la JVM
     * sabe exactamente de donde vino cada cosa que ha cargado.

La versión, publicada por el propio framework:

        String instalada = SpringBootVersion.getVersion();
        // `SpringBootVersion` lo publica el propio framework leyendo el
        // manifiesto de su jar. Es la fuente mas fiable que existe: la escribe
        // quien construyo el artefacto, no quien lo usa.

La documentación, que aquí el paquete no trae:

        salida.put("leida_del_paquete", false);
        salida.put("por_que_no",
                "el jar no publica una direccion consultable en ejecucion; en Node y en Python el manifiesto del paquete si la trae");
        salida.put("por_que_importa",
                "cuando el paquete no lo dice, alguien tiene que mantener esa direccion a mano — y eso caduca");

Y la cuarta pregunta, con la respuesta que separa los ecosistemas:

        salida.put("hay_codigo_fuente_en_disco", false);
        salida.put("que_viaja_en_el_paquete", "bytecode compilado, no el codigo original");
        salida.put("como_conseguirlo",
                "mvn dependency:sources, o el repositorio en github.com/spring-projects/spring-boot");
     * En la JVM, NO. Lo que se descarga es bytecode: clases compiladas. Para
     * leer el original hay que pedir aparte el jar de fuentes —`-sources.jar`—
     * o ir al repositorio en la red.
     *
     * A cambio, la JVM ofrece algo que los otros dos no: la REFLEXION. No se
     * puede leer el cuerpo de un metodo, pero si su forma exacta, y sin
     * compilar nada ni abrir un archivo.

Y la reflexión, ejecutándose:

        for (Method metodo : Arrays.stream(RequestMapping.class.getDeclaredMethods())
                .sorted((a, b) -> a.getName().compareTo(b.getName()))
                .toList()) {
            metodos.add(metodo.getName() + ": " + metodo.getReturnType().getSimpleName());
        }

GET /pregunta/codigo-fuente devuelve los atributos exactos de @RequestMappingconsumes, headers, method, name, params, path, produces, value— leídos de la clase cargada. No es el código, pero es la verdad, y no depende de ningún tutorial.

🔬 Comparación#

Pregunta Express FastAPI Spring Boot
¿Versión, del paquete?
¿Coincide con lo declarado? sí (^5.1.0 → 5.2.1) no (==0.121.3 → 0.135.3)
¿Documentación en el paquete? no
¿Ruta real localizable?
¿Código fuente en disco? no
Lo que sí se puede inspeccionar el archivo el archivo y inspect.getsource la forma, por reflexión

Tres cosas que se leen de la tabla:

Ninguna de las tres es mejor. Pero saber en cuál estás cambia cómo se contesta una duda: en Node abres el archivo; en la JVM lees la firma y buscas el repositorio.

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 008

Con el servidor levantado:

curl -s http://127.0.0.1:4100/pregunta/version

Y lo mismo sobre cualquier proyecto tuyo, sin este repositorio:

node -p "require('express/package.json').version"

🧪 Reto de transferencia#

  1. Averigua la versión exacta de las tres dependencias más importantes de tu proyecto y compárala con lo que dice tu archivo de dependencias. Si alguna no coincide, ya sabes por dónde empezar el próximo informe de error.
  2. Abre el código de tu framework y busca la función que atiende tu ruta. Pon un registro temporal, ejecuta y bórralo. Media hora bien invertida.
  3. Busca la respuesta a una duda tuya en tres sitios —un tutorial, la documentación oficial de tu versión y el código— y compara las tres. La diferencia entre la primera y la tercera es el contenido de esta clase.

🔗 Enlaces#

Fuentes#