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

Clase 006 — Coste total: aprender, mantener, contratar, salir#

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

🎯 Objetivo#

Poner número a lo que cuesta un framework más allá del código que se escribe el primer día.

Cuatro dimensiones: aprenderlo, mantenerlo, contratar a quien lo sepa y salir de él. Tres se pueden medir desde el propio proyecto. La cuarta no, y esta clase se niega a inventársela.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Dos frameworks del mismo nicho —Express y NestJS, los dos de Node, los dos backend— resuelven el mismo servicio de dos rutas.

El resultado es idéntico: mismos códigos de estado, mismos cuerpos, mismo comportamiento ante una entrada inválida. Los cuatro primeros casos del contrato están ahí para demostrarlo.

Y ahí acaba lo que se puede comparar mirando la salida. Todo lo demás —lo que se tarda en entenderlo, lo que se descarga, lo que cuesta cambiarlo— no aparece en ninguna respuesta HTTP. Esta clase lo saca a una ruta.

🧮 El contrato#

# Petición Qué comprueba
1-2 POST /tareas y GET /tareas que el servicio es el mismo en las dos
3-4 POST /tareas con título vacío que también fallan igual
5 GET /coste las cuatro dimensiones, ninguna de adorno
6 GET /coste/aprender medido: true
7 GET /coste/mantener medido: true
8 GET /coste/contratar medido: false
9 GET /coste/salir medido: true

El caso 8 es el que hace honesta a la clase. El contrato exige que la implementación declare que no puede medir esa dimensión, en lugar de devolver un número plausible.

Un número inventado con formato de número medido es peor que un hueco: el hueco se ve, y el número no.

<!-- 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
Coste total Lo que cuesta un framework más allá del código: aprenderlo, mantenerlo actualizado, encontrar a quien lo conozca y salir de él. Las cuatro dimensiones se deciden juntas y solo la primera es visible el primer día.

🧰 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
NestJS framework de aplicación de Node.js/TypeScript (TypeScript) 2017 MIT proyecto independiente

🔧 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
coste.mjs código JavaScript (módulo 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)

🔧 NestJS#

Trae a Node.js el modelo de Angular y Spring: módulos, decoradores e inyección de dependencias por constructor.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec tsc -p tsconfig.json

Arrancarla suelta, sin el verificador:

PORT=3000 node dist/main.js

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
src/app.module.ts código TypeScript
src/coste/coste.controller.ts código TypeScript
src/coste/coste.service.ts código TypeScript
src/main.ts código TypeScript

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 dos calculan sus cifras cada vez que alguien pregunta, leyendo sus propios archivos. No hay ningún número escrito a mano en ninguna de las dos.

Express · express/server.mjs y express/coste.mjs#

El servicio entero:

app.post("/tareas", (peticion, respuesta) => {
  const titulo = peticion.body?.titulo;
  if (typeof titulo !== "string" || titulo.trim().length === 0) {
    respuesta.status(422).json({ code: "TITULO_INVALIDO" });
    return;
  }
  const tarea = { id: tareas.length + 1, titulo: titulo.trim() };
  tareas.push(tarea);
  respuesta.status(201).json(tarea);
});

Diez líneas. La lista es una constante, la validación es un if y el manejador hace las tres cosas: comprobar, guardar y responder.

Y el archivo que mide, que es el que más dice:

/**
 * LAS MEDIDAS.
 *
 * Fíjate en lo que NO hay en este archivo: ni un `import express`. Es código
 * JavaScript corriente, y por eso se podría llevar a otro framework tal cual.
 *
 * Esa es exactamente la medida de la dimensión «salir»: cuántos archivos tuyos
 * mencionan al framework y cuántos no.
 */

Mantener se cuenta en el archivo de bloqueo, no en package.json:

function paquetesTransitivos() {
  const bloqueo = readFileSync(path.join(RAIZ, "pnpm-lock.yaml"), "utf8");
  const desde = bloqueo.indexOf("\npackages:");
  if (desde === -1) return 0;
  return bloqueo
    .slice(desde)
    .split(/\r?\n/)
    .filter((linea) => /^ {2}\S.*:$/.test(linea)).length;
}

Una dependencia directa. Noventa y cinco paquetes debajo. Esa segunda cifra es la que aparece en los avisos de seguridad y la que hay que actualizar.

Salir se cuenta buscando el nombre del framework en tus propios archivos:

function archivosQueMencionanAlFramework() {
  return archivosDeCodigo().filter((ruta) => /["']express["']/.test(readFileSync(ruta, "utf8")))
    .length;
}

Uno de dos. La mitad de este proyecto se podría mover a otro framework tal cual.

Y la dimensión que no se mide:

  contratar: () => ({
    medido: false,
    por_que: "cuánta gente sabe esto y cuánto cobra no está en ningún archivo del repositorio",
    donde_se_mira: "encuestas públicas del sector y ofertas de tu mercado local, con su fecha",
    aviso: "inventarse este número es peor que no tenerlo",
  }),

NestJS · nestjs/src/ — el mismo servicio en siete archivos#

El módulo raíz, que en Express no tiene equivalente:

@Module({
  controllers: [TareasController, CosteController],
  providers: [TareasService, CosteService],
})
export class AppModule {}
 * Este archivo no hace nada en tiempo de ejecución salvo declarar qué existe. En
 * Express no hay equivalente: importar el archivo ya lo registra.

El controlador, que ya no valida ni guarda:

@Controller("tareas")
export class TareasController {
  @Post()
  @HttpCode(201)
  crear(@Body() cuerpo: CrearTareaDto) {
    return this.tareas.crear(cuerpo.titulo);
  }

Tres líneas contra las diez de Express — porque el trabajo se ha ido a otros dos archivos.

El servicio:

@Injectable()
export class TareasService {
  private readonly tareas: Tarea[] = [];

Y el objeto de transferencia, que es la validación:

export class CrearTareaDto {
  @Transform(({ value }) => (typeof value === "string" ? value.trim() : value))
  @IsString()
  @IsNotEmpty()
  titulo!: string;
}
 * Se paga con dos dependencias más —`class-validator` y `class-transformer`— y
 * con dos conceptos más que aprender. Se cobra en que la regla está declarada
 * una vez y la aplica el framework en todas las rutas que reciban este tipo.

El arranque, con la configuración que Express no necesita:

  aplicacion.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      whitelist: true,
      errorHttpStatusCode: 422,
      exceptionFactory: () => new UnprocessableEntityException({ code: "TITULO_INVALIDO" }),
    }),
  );
 * `errorHttpStatusCode` está aquí porque NestJS responde 400 por omisión y el
 * contrato pide 422. `exceptionFactory` está aquí porque el cuerpo por omisión
 * es el suyo y el contrato pide el nuestro. Las dos son el mismo tipo de trabajo:
 * doblar lo que el framework decidió por ti.

Los conceptos declarados, tres veces más que en Express:

  private readonly conceptos = [
    "manejador",
    "middleware",
    "enrutado",
    "módulo",
    "proveedor",
    "inyección por constructor",
    "decorador",
    "objeto de transferencia",
    "tubería de validación",
  ];

Los tres primeros son los mismos de Express, y no por casualidad: NestJS corre sobre Express por debajo. No los sustituye — los añade.

🔬 Comparación#

Los números salen de ejecutar las dos implementaciones. Reprodúcelos tú:

node scripts/run-class.mjs 006
Dimensión Qué se cuenta Express NestJS
Aprender conceptos para leerlo 3 9
archivos 2 7
líneas de código 100 186
Mantener dependencias directas 1 7
paquetes transitivos 95 158
Contratar no medible no medible
Salir archivos que mencionan al framework 1 de 2 6 de 7

Y una quinta cifra que esta clase no puede medir pero la clase 004 sí, porque sale del catálogo:

Alternativas reales en su ecosistema
Express 3 — Fastify, hapi, Koa
NestJS 0

Léelo junto: NestJS es el más caro de salir por dos vías a la vez. Seis de siete archivos lo mencionan, y no hay ningún framework de su categoría en Node al que mudarse. Salir no es cambiar una dependencia: es reescribir el proyecto en otro modelo.

Esto no dice que NestJS sea peor. Dice qué se paga. Lo que se cobra — estructura impuesta, límites explícitos, validación que ninguna ruta puede saltarse — es real y en un equipo de quince personas puede valer mucho más que las cifras de la tabla.

Lo que no es defendible es elegirlo sin conocer estos números, porque son fáciles de calcular y muy caros de descubrir tarde.

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 006

Para ver los números de una implementación con el servidor levantado:

curl -s http://127.0.0.1:4100/coste/mantener

Y sobre un proyecto tuyo, sin instalar nada:

grep -c "^  [^ ].*:$" pnpm-lock.yaml

🧪 Reto de transferencia#

  1. Mide tu proyecto actual en las tres dimensiones medibles. El coste de salida —cuántos archivos mencionan al framework sobre el total— suele ser el número que más sorprende.
  2. Escribe tu propia lista de conceptos. Los que hacen falta para que alguien nuevo lea un archivo cualquiera de tu código. Si pasa de diez, ya sabes por qué la incorporación tarda.
  3. Busca el dato de contratar en una encuesta pública del sector, con su fecha y su tamaño de muestra. Comparar esa cifra con la impresión que tenías es la mitad del ejercicio.

🔗 Enlaces#

Fuentes#