"Configuración del proyecto" suena como una lista de verificación de archivos para crear, pero las elecciones detrás de esto (un servicio por repositorio o muchos, salida compilada registrada o no, un diseño hecho a mano o uno generado) se remontan a un pequeño número de compensaciones subyacentes.
Esta página trata sobre esas compensaciones: la topología del repositorio, la división entre construcción y tiempo de ejecución, y el problema de coordinación que las herramientas de monorepo existen para resolver.
La estructura de un proyecto codifica tres decisiones separables: qué cuenta como un elemento desplegable, dónde se encuentra el límite del paso de compilación y si un historial de Git contiene un servicio o muchos, y la mayoría de las preguntas sobre "qué herramienta necesitamos" en realidad se refieren a una de esas tres.
Por Qué Importa: Tomar una decisión incorrecta sobre la topología en cualquier dirección cuesta tiempo real: un monorepo prematuro grava cada PR con una sobrecarga de coordinación, mientras que una división de servicios demasiado tardía significa desenredar código compartido que ya está acoplado en producción.
Conceptos Clave:topología del repositorio, límite desplegable, división entre construcción y tiempo de ejecución, grafo de tareas, scaffolding, CI basado en afectados.
Cuándo Usar Este Modelo: Decidir si un nuevo servicio necesita su propio repositorio, elegir cuándo introducir workspaces o un orquestador de tareas, razonar por qué src/ y dist/ están separados, y comprender qué estandariza realmente un generador o un repositorio de plantillas.
Limitaciones / Compensaciones: Ninguna estructura es gratuita: un monorepo intercambia la simplicidad por servicio por un costo de coordinación transversal, y un repositorio de un solo servicio intercambia ese costo de coordinación por una eventual duplicación de código una vez que aparece un segundo elemento desplegable.
Temas Relacionados: workspaces de npm, Turborepo, Nx, límites de servicio, scaffolding y plantillas.
Debajo de los nombres de las carpetas, la estructura de un proyecto realmente codifica tres cosas.
La primera es un límite desplegable: lo que cuenta como una unidad de código ejecutable e implementable de forma independiente, un package.json, una construcción, un proceso que se implementa como un todo.
La segunda es la división entre construcción y tiempo de ejecución: la línea entre el código fuente que editas (src/, TypeScript) y el artefacto que realmente se ejecuta en producción (dist/, JavaScript compilado), una división que existe porque Node no ejecuta TypeScript de forma nativa en la mayoría de las configuraciones de producción, por lo que la compilación es un paso en tiempo de construcción, nunca en tiempo de ejecución.
La tercera es la topología del repositorio: si un elemento desplegable vive solo en su propio historial de Git (un repositorio de un solo servicio) o si múltiples elementos desplegables y paquetes compartidos viven juntos en un historial de Git, coordinados a través de workspaces (un monorepo).
Estas tres decisiones son independientes entre sí en principio (puedes tener un monorepo sin herramientas de construcción compartidas, o un repositorio de un solo servicio con una elaborada tubería de construcción), pero en la práctica tienden a moverse juntas, porque las herramientas que soportan una a menudo asumen las otras.
Una analogía útil: la estructura de un repositorio es el plano de un edificio.
El plano no hace nada por sí mismo, pero decide dónde se supone que debe ir el nuevo trabajo (una nueva toma de corriente tiene un lugar obvio para conectarse porque el plan de cableado ya existe), que es exactamente lo que evita que un equipo invente su propio cableado a medida que avanza.
single-service-repo/ multi-service-monorepo/
src/ apps/
test/ api/ (desplegable)
package.json worker/ (desplegable)
Dockerfile packages/
shared/ (biblioteca, no se despliega sola)
package.json (raíz de los workspaces)
La topología del repositorio se traduce directamente en la elección de herramientas, y comprender esa cascada explica por qué ciertas herramientas aparecen juntas.
Un repositorio de un solo servicio no necesita nada más allá de un gestor de paquetes: solo hay un package.json, un grafo de dependencias, una construcción.
Un monorepo necesita workspaces (npm, pnpm o el protocolo de workspaces de Yarn; consulta Workspaces y Monorepos) como mínimo, porque los workspaces son lo que permite que un paquete dependa de otro paquete en el mismo repositorio sin publicarlo primero en un registro.
Pero los workspaces por sí solos solo resuelven la vinculación: le dicen al gestor de paquetes dónde residen los paquetes locales, no cuáles cambiaron realmente, y no cómo evitar reconstruir o volver a probar paquetes que no lo hicieron.
Esa es la brecha específica que llenan los orquestadores de tareas como Turborepo y Nx: ambos construyen un grafo de dependencias entre los paquetes del workspace (no solo un grafo de vinculación, un grafo de tareas: "construir api requiere que shared ya esté construido") y lo usan para ejecutar solo lo que un cambio dado realmente afecta, almacenando en caché el resultado de todo lo demás.
Por eso un monorepo sin un orquestador de tareas sigue funcionando correctamente, solo que más lento a medida que crece: el orquestador es una capa de rendimiento y coordinación sobre los workspaces, no un reemplazo para ellos.
El scaffolding es el mecanismo que convierte una estructura elegida en algo que se replica de manera consistente, en lugar de ser re-decidido por quienquiera que cree el siguiente servicio.
npm init, una CLI de framework (como el generador de NestJS) o un repositorio de plantillas interno están haciendo el mismo trabajo conceptual en diferentes niveles de opinión: codificar una decisión de estructura una vez para que no tenga que ser discutida, o desviarse sutilmente, cada vez que alguien comienza un nuevo proyecto.
// turbo.json - codifica el grafo de tareas, no solo los enlaces del workspace{ "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "test": { "dependsOn": ["build"] } }}
El descubrimiento de la configuración también forma parte de la mecánica, no solo de la conveniencia humana: herramientas como tsc, ESLint y Docker buscan sus archivos de configuración subiendo por el árbol de directorios desde donde se invocan, que es exactamente por qué tsconfig.json, package.json y Dockerfile viven convencionalmente en la raíz de un repositorio (o paquete de workspace); la ubicación es cómo se encuentran, no simplemente dónde se organizan para facilitar la lectura.
Las herramientas de monorepo justifican su costo en una curva, no en un umbral: el momento adecuado para adoptar Turborepo o Nx se correlaciona con el recuento de elementos desplegables, el dolor del tiempo de construcción y la frecuencia con la que un paquete compartido cambia al unísono con sus consumidores, no con un número fijo de paquetes.
Monorepo Multi-Servicio cubre las reglas de límite específicas (qué pertenece a apps/ vs packages/, cuándo se debe extraer un paquete) que hacen que ese juicio sea concreto.
La ruta de adopción realista es incremental, no una única decisión de "big-bang": los equipos suelen comenzar con un repositorio de un solo servicio, agregan un segundo elemento desplegable y recurren a workspaces simples una vez que se necesita compartir código, y solo agregan un orquestador de tareas una vez que el tiempo de construcción o CI (no solo el recuento de paquetes) se convierte en el verdadero punto de dolor.
Saltar directamente a un monorepo con muchas herramientas para un repositorio de dos paquetes generalmente agrega sobrecarga de configuración y cognitiva sin un beneficio correspondiente todavía.
La CI es donde esta decisión se agrava de manera más visible: una tubería de repositorio completo reconstruye y vuelve a probar todo en cada cambio, mientras que la CI basada en afectados (que tanto Turborepo como Nx soportan) utiliza el mismo grafo de tareas para ejecutar solo lo que los archivos cambiados de un commit dado podrían haber tocado, lo que es la diferencia entre una tubería de cinco minutos y una de cuarenta minutos una vez que un monorepo tiene una docena de paquetes.
También hay una dimensión de gobernanza que vale la pena mencionar: una estructura consistente entre servicios (el mismo lugar para src/server.ts, los mismos nombres de scripts, la misma convención de verificación de estado) es lo que hace que la guardia y la incorporación sean rápidas en una organización con muchos servicios; cualquier ingeniero que haya trabajado en un repositorio de servicios puede navegar por otro, que es la verdadera recompensa del scaffolding más allá de ahorrar escritura el primer día.
Capa
Fortaleza
Debilidad
Mejor Ajuste
Repositorio de un solo servicio, sin orquestador
Configuración más simple posible, cero impuestos de coordinación
Duplicación de código una vez que aparece un segundo elemento desplegable
Un elemento desplegable, equipo pequeño
Solo Workspaces (sin orquestador de tareas)
Comparte código entre paquetes con un solo archivo de bloqueo
Sin caché ni CI basada en afectados: reconstrucciones completas cada vez
Monorepo pequeño, 2-4 paquetes, tiempos de construcción tolerables
Turborepo
Configuración de tubería simple, rápida de adoptar incrementalmente
Menos opinado sobre la organización del código que Nx
Equipos que desean compilaciones en caché/afectadas sin un framework
Nx
Generadores, límites de módulos forzados, integración profunda de herramientas
Curva de aprendizaje más pronunciada, estructura más inicial
Organizaciones más grandes que desean convenciones forzadas en muchos equipos
"Un monorepo solo significa múltiples repositorios gestionados juntos en Git." Es lo contrario: un historial de Git que contiene múltiples paquetes implementables o publicables de forma independiente, coordinados a través de workspaces y, generalmente, un orquestador de tareas.
"Necesitas Turborepo o Nx en el momento en que tienes dos paquetes." Los workspaces simples son suficientes hasta que el tiempo de construcción o prueba, o la falta de detección de cambios, se convierte en un cuello de botella real; adoptar un orquestador antes solo agrega configuración para mantener.
"src/ vs dist/ es una preferencia de estilo." Marca el límite del paso de compilación: lo que se edita a mano versus lo que realmente se envía y se ejecuta en producción, y confundir los dos facilita la implementación accidental de código obsoleto o sin compilar.
"Las herramientas de scaffolding son solo plantillas de inicio opcionales." Son la forma en que una organización impone la coherencia estructural en el momento en que se crea un proyecto, lo que es mucho más barato que corregir la deriva estructural en una docena de servicios más tarde.
"Un monorepo elimina la necesidad de pensar en el versionado entre paquetes." Los paquetes internos pueden permanecer en workspace:* indefinidamente dentro del repositorio, pero en el momento en que cualquier paquete se publica o se consume fuera del monorepo, la disciplina semver real vuelve a estar sobre la mesa.
¿Qué tres decisiones codifica realmente la "estructura del proyecto"?
Límites desplegables (qué es una unidad implementada de forma independiente), la división entre construcción y tiempo de ejecución (código fuente vs. salida compilada) y la topología del repositorio (repositorio de un solo servicio vs. monorepo); la mayoría de las preguntas concretas sobre la estructura se reducen a una de estas tres.
¿Por qué el código de Node necesita una división entre `src/` y `dist/`?
Porque TypeScript necesita un paso de compilación antes de convertirse en JavaScript plano que Node pueda ejecutar en la mayoría de las configuraciones de producción: src/ es lo que se edita, dist/ es el artefacto que realmente se implementa y ejecuta.
¿Los workspaces de npm por sí solos me dan un sistema de construcción de monorepo?
Te proporcionan vinculación de paquetes (un paquete puede depender de otro en el mismo repositorio sin publicarlo), pero no calculan qué cambió ni almacenan en caché los resultados de la construcción, que es el trabajo específico que realiza un orquestador de tareas como Turborepo o Nx.
¿Cuándo vale la pena adoptar Turborepo o Nx?
Cuando las construcciones de workspaces simples comienzan a tomar mucho tiempo porque todo se reconstruye en cada cambio, o cuando un número creciente de paquetes hace que "lo que realmente necesita ejecutarse" sea difícil de razonar manualmente, no simplemente en un número fijo de paquetes.
¿Por qué los archivos de configuración como `tsconfig.json` viven convencionalmente en la raíz del repositorio?
Porque las herramientas los buscan subiendo por el árbol de directorios desde donde se invocan; la ubicación en la raíz no es solo una convención para los humanos, es la forma en que la herramienta realmente encuentra el archivo.
¿Cuál es la diferencia práctica entre Turborepo y Nx?
Ambos construyen un grafo de tareas y almacenan en caché los resultados, pero Nx agrega generadores y reglas de límites de módulos forzados, lo que se adapta a organizaciones más grandes que desean coherencia en muchos equipos, mientras que Turborepo se mantiene más ligero y rápido de adoptar incrementalmente.
¿Es comenzar con un repositorio de un solo servicio la elección "incorrecta" si el proyecto podría crecer?
No, suele ser el valor predeterminado correcto, ya que un monorepo prematuro grava cada PR temprano con una sobrecarga de coordinación que no se amortiza hasta que hay un segundo elemento desplegable que realmente comparte código.
¿Qué significa en la práctica "CI basado en afectados"?
La tubería de CI utiliza el mismo grafo de tareas que el orquestador usa para las construcciones locales para determinar qué paquetes podría haber tocado un commit dado, y solo construye/prueba esos, en lugar de reconstruir y volver a probar todo el repositorio en cada cambio.
¿Cuál es el valor real de una herramienta de scaffolding o un repositorio de plantillas más allá de ahorrar escritura?
Codifica decisiones estructurales (diseño de carpetas, nombres de scripts, configuración de lint, configuración de Docker) una vez, para que cada nuevo servicio comience de manera consistente en lugar de desviarse ligeramente de lo que la última persona que configuró uno recordó incluir.
¿Un monorepo significa que los paquetes internos nunca necesitan números de versión?
Solo mientras permanezcan puramente internos: workspace:* se resuelve a la copia local sin tocar nunca semver, pero en el momento en que un paquete se publica externamente o se consume fuera del monorepo, la disciplina de versionado real se aplica de nuevo.
¿Cómo sé cuándo extraer un paquete compartido en lugar de copiar código?
Una regla general común es tolerar la duplicación hasta que un tercer consumidor necesite el mismo código; extraer después de la segunda copia a menudo es prematuro, y esperar una duplicación real y repetida evita adivinar una abstracción demasiado pronto.