Dominando Monorepositorios con NX - Parte 2
🧱 Parte 2: Arquitectura y librerías compartidas (Enterprise Patterns)
“Arquitectura escalable en Nx: construyendo librerías y dominios al estilo enterprise”
En la Parte 1, creamos nuestro primer monorepo con Nx y entendimos los conceptos básicos. Ahora tenemos una aplicación store funcional, pero un monorepo no es solo para agrupar apps; su verdadero poder reside en cómo estructuramos y compartimos el código.
En esta segunda parte, vamos a transformar nuestro simple workspace en una arquitectura robusta y escalable, aplicando los patrones de diseño "Enterprise Angular Monorepo". Organizaremos nuestro código en librerías por dominios funcionales, estableceremos límites de dependencias y aprenderemos a visualizar la estructura con el grafo de Nx.
1. Repaso rápido del monorepo creado
Actualmente, nuestra estructura es:
mi-super-monorepo/
├── apps/
│ └── store/
├── libs/
│ # (Vacío por ahora)
...
Todo el código de nuestra aplicación store vive dentro de apps/store. Esto está bien para empezar, pero si la app crece, se convertirá en un monolito difícil de mantener. La solución es descomponerla en librerías lógicas y reutilizables.
2. El enfoque de "Enterprise Angular Monorepo Patterns"
La idea es organizar las librerías (libs/) no solo como un montón de utilidades, sino siguiendo una estructura de dominios de negocio. Cada dominio (ej: products, orders, auth) tendrá sus propias librerías especializadas.
Una estructura de carpetas típica bajo libs/ podría ser:
libs/
├── products/
│ ├── data-access/ # Lógica de datos (servicios HTTP, estado)
│ ├── feature/ # Componentes "Smart" que orquestan un caso de uso
│ └── ui/ # Componentes "Dumb" y reutilizables
└── shared/
├── ui/ # Componentes de UI globales (botones, layouts)
└── util/ # Funciones y constantes compartidas
Tipos de librerías:
feature: Contiene componentes "inteligentes" que implementan un caso de uso específico (ej: ProductListPage, CreateOrderForm). Orquestan la comunicación entre la UI y los servicios de datos.
ui: Contiene componentes "tontos" (dumb/presentational). Reciben datos vía @Input() y emiten eventos vía @Output(). Son altamente reutilizables y no tienen dependencias de servicios.
data-access: Gestiona el estado y la comunicación con APIs externas. Contiene servicios de Angular, modelos de datos, y lógica de estado (ej: NgRx, Signals Store, o simples BehaviorSubject).
util o shared: Librerías transversales con funciones de ayuda, constantes, tipos, etc., que pueden ser usados en cualquier parte.
3. Creación de librerías compartidas con Nx
Vamos a crear nuestras primeras librerías para el dominio products y una librería shared/ui para componentes comunes.
# 1. Librería de UI compartida para toda la app
nx g @nx/angular:lib ui --directory=shared --standalone
# 2. Librería de Data Access para el dominio 'products'
nx g @nx/angular:lib data-access --directory=products
# 3. Librería de UI específica para 'products'
nx g @nx/angular:lib ui --directory=products
# 4. Librería Feature para la lista de productos
nx g @nx/angular:lib feature-list --directory=products
Ahora, tu carpeta libs se verá así:
libs/
├── products/
│ ├── data-access/
│ ├── feature-list/
│ └── ui/
└── shared/
└── ui/
Cada una de estas carpetas es un proyecto Nx independiente con su propio project.json.
4. Comunicación entre apps y libs: control de dependencias
Supongamos que queremos usar un botón de nuestra librería shared/ui dentro de la app store.
Crea un componente en la librería: Nx puede generar un componente dentro de una librería específica:
nx g @nx/angular:component button --project=shared-ui --exportEl flag --project le dice a Nx dónde crear el componente, y --export lo añade al index.ts público de la librería.
Úsalo en la aplicación: Gracias a los alias de TypeScript que Nx configura automáticamente, puedes importarlo de forma limpia en apps/store/src/app/app.component.ts:
import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; // ¡Importación limpia desde una librería! import { ButtonComponent } from '@mi-super-monorepo/shared/ui'; @Component({ selector: 'app-root', standalone: true, imports: [RouterOutlet, ButtonComponent], // Añade el componente importado template: ` <h1>¡Bienvenido a la tienda!</h1> <mi-super-monorepo-button>Haz clic aquí</mi-super-monorepo-button> <router-outlet /> `, }) export class AppComponent {}
5. Lint Rules para mantener la arquitectura limpia
Una de las joyas de Nx es su capacidad para forzar reglas de arquitectura. Queremos evitar el "código espagueti". Por ejemplo, una regla podría ser: "una librería de tipo ui nunca debe importar desde una de data-access".
Esto se logra con tags y enforceModuleBoundaries.
Asigna tags a tus librerías: Abre el project.json de cada librería y añade tags:
libs/shared/ui/project.json:
"tags": ["scope:shared", "type:ui"]libs/products/data-access/project.json:
"tags": ["scope:products", "type:data-access"]libs/products/feature-list/project.json:
"tags": ["scope:products", "type:feature"]
Configura las reglas de linting: Abre el fichero .eslintrc.json en la raíz del proyecto y busca la regla @nx/enforce-module-boundaries. La configuraremos así:
{ // ... otras reglas "rules": { "@nx/enforce-module-boundaries": [ "error", { "enforceBuildableLibDependency": true, "allow": [], "depConstraints": [ // Un scope solo puede depender de 'shared' o de sí mismo { "sourceTag": "scope:products", "onlyDependOnLibsWithTags": ["scope:products", "scope:shared"] }, // Una feature puede depender de 'ui' y 'data-access' { "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui", "type:data-access", "type:util"] }, // La UI solo puede depender de 'util' (y de otras UI) { "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:ui", "type:util"] }, // Data Access solo puede depender de 'util' { "sourceTag": "type:data-access", "onlyDependOnLibsWithTags": ["type:util"] } ] } ] } }
Ahora, si intentas importar un servicio de products-data-access dentro de un componente de shared-ui, tu linter (y tu CI) fallará, ¡manteniendo tu arquitectura pura y predecible!
6. Visualiza tus dependencias con Nx Graph
¿Cómo puedes estar seguro de que tu arquitectura tiene sentido? Nx te lo muestra visualmente. Ejecuta este comando:
nx graph
Se abrirá una vista interactiva en tu navegador mostrando todas las aplicaciones y librerías, y las relaciones entre ellas. Es una herramienta increíblemente poderosa para entender la estructura de tu proyecto, depurar dependencias circulares y explicar la arquitectura a nuevos miembros del equipo.
🎯 Objetivo Cumplido:
Hemos pasado de una simple aplicación a una arquitectura de monorepo profesional y escalable. Ahora tienes:
Una estructura de librerías organizada por dominios y tipos.
Reglas de linting que protegen la integridad de tu arquitectura.
La capacidad de visualizar tus dependencias con nx graph.
En la Parte 3, pondremos todo esto en piloto automático. Configuraremos un pipeline de Integración Continua (CI/CD) que testeará y desplegará nuestras aplicaciones de forma inteligente, aprovechando al máximo el poder de nx affected.