Module Base
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
Librería Module Base (Core)
Propósito: definir contratos, estructura y API base para el sistema de módulos dinámicos en aplicaciones web del lado del cliente usando Angular 20, estableciendo estándares arquitectónicos con signals, standalone components y nueva sintaxis de control de flujo.
Estado actual
- Tipo: librería Nx de Angular 20.
- Ruta de importación:
@libs-core/module-base(configurada entsconfig.base.json). - Prioridad:
Muy Alta- fundamento arquitectónico del sistema modular en navegador. - Entorno: Cliente web con standalone components, signals y createComponent API de Angular 20.
- API pública actual: solo barrel
src/index.ts.
Rol en el sistema de módulos dinámicos para navegador
Module Base actúa como el ADN del sistema en el navegador, definiendo la estructura genética que seguirán todos los módulos para garantizar consistencia, interoperabilidad, carga dinámica eficiente y mantenibilidad en el entorno web.
API pública planificada
Contratos y Interfaces para navegador
- BrowserModuleContract: contrato principal que debe implementar cada módulo en el navegador
- ModuleManifest: descriptor de módulo con metadata, dependencias y configuración para carga dinámica
- ModuleFederationConfig: configuración específica para Module Federation
- ModuleEvents: eventos estándar del ciclo de vida de módulos en navegador
- ModulePermissions: sistema de permisos y CSP por módulo
- ModuleTypes: tipos y enums compartidos entre módulos con compatibilidad web
Public Actions
- ModuleActions: acciones públicas que expone cada módulo
- ActionContract: contrato para acciones inter-módulo
- ActionMetadata: metadata de acciones (permisos, descripción, parámetros)
State Management
- BaseModuleState: clase base para estados NGXS de módulos
- ModuleStateActions: acciones CRUD estándar para estados
- ModuleStateService: servicio base para gestión de estado
- StateContract: contrato para intercambio de estado entre módulos
Component Architecture
- BaseModuleComponent: componente base con funcionalidades comunes
- ModuleLayoutComponent: layout estándar para módulos
- ModuleRouterOutlet: outlet personalizado para rutas de módulos
- ComponentContract: contrato para comunicación entre componentes
Service Layer
- BaseModuleService: servicio base con logging y eventos integrados
- ModuleConfigService: gestión de configuración por módulo
- ModuleCommunicationService: comunicación entre módulos
- ServiceContract: contrato para servicios inter-módulo
Estructura del paquete actualizada
src/
├── lib/
│ ├── contracts/
│ │ ├── module-contract.ts
│ │ ├── module-manifest.ts
│ │ ├── action-contract.ts
│ │ ├── state-contract.ts
│ │ ├── component-contract.ts
│ │ └── service-contract.ts
│ ├── actions/
│ │ ├── module-actions.ts
│ │ ├── action-metadata.ts
│ │ └── public-actions.base.ts
│ ├── state/
│ │ ├── base-module-state.ts
│ │ ├── module-state-actions.ts
│ │ ├── module-state.service.ts
│ │ └── state-selectors.base.ts
│ ├── components/
│ │ ├── base-module.component.ts
│ │ ├── module-layout.component.ts
│ │ ├── module-router-outlet.component.ts
│ │ └── component-base.ts
│ ├── services/
│ │ ├── base-module.service.ts
│ │ ├── module-config.service.ts
│ │ ├── module-communication.service.ts
│ │ └── service-base.ts
│ ├── models/
│ │ ├── module-types.ts
│ │ ├── module-events.ts
│ │ ├── module-permissions.ts
│ │ └── lifecycle-states.ts
│ ├── decorators/
│ │ ├── module.decorator.ts
│ │ ├── public-action.decorator.ts
│ │ ├── module-service.decorator.ts
│ │ └── injectable-module.decorator.ts
│ └── utils/
│ ├── module-validator.ts
│ ├── contract-checker.ts
│ └── dependency-resolver.ts
└── index.ts
Funcionalidad clave
1. Contrato de módulo con Angular 20
// Contrato que debe implementar cada módulo usando signals
export interface BrowserModuleContract {
readonly manifest: ModuleManifest;
readonly actions: Signal<ModuleActionRegistry>;
readonly state: Signal<ModuleStateDefinition>;
readonly services: Signal<ModuleServiceRegistry>;
readonly components: Signal<ModuleComponentRegistry>;
// Signals para estado del módulo
readonly isInitialized: Signal<boolean>;
readonly isRunning: Signal<boolean>;
readonly loadingState: Signal<ModuleLoadingState>;
// Métodos de ciclo de vida
initialize(context: ModuleInitializationContext): Promise<void>;
configure(config: ModuleConfig): Promise<void>;
start(): Promise<void>;
stop(): Promise<void>;
destroy(): Promise<void>;
// Métodos específicos para Angular 20
createComponent<T>(componentType: Type<T>): Promise<ComponentRef<T>>;
registerStandaloneComponents(components: Type<any>[]): void;
}
// Implementación base usando signals
export abstract class BaseModuleContract implements BrowserModuleContract {
// Signals del módulo
readonly isInitialized = signal(false);
readonly isRunning = signal(false);
readonly loadingState = signal<ModuleLoadingState>('idle');
readonly actions = signal<ModuleActionRegistry>({});
readonly state = signal<ModuleStateDefinition>({});
readonly services = signal<ModuleServiceRegistry>({});
readonly components = signal<ModuleComponentRegistry>({});
// Computed signals derivados
readonly canStart = computed(() => this.isInitialized() && !this.isRunning());
constructor(public readonly manifest: ModuleManifest, protected readonly injector: EnvironmentInjector) {}
async createComponent<T>(componentType: Type<T>): Promise<ComponentRef<T>> {
// Usar createComponent API de Angular 20
return createComponent(componentType, {
environmentInjector: this.injector,
});
}
registerStandaloneComponents(components: Type<any>[]): void {
// Registrar componentes standalone en el registro del módulo
this.components.update((current) => ({
...current,
...components.reduce(
(acc, comp) => ({
...acc,
[comp.name]: comp,
}),
{}
),
}));
}
}
2. Manifest de módulo
// Descriptor completo de un módulo con navegación y embedding
export interface ModuleManifest {
id: string;
version: string;
name: string;
description: string;
dependencies: ModuleDependency[];
permissions: ModulePermission[];
routes: ModuleRoute[];
// Navegación y UI
navigation: ModuleNavigationConfig;
embeddableComponents: ModuleEmbeddableComponent[];
publicActions: ModulePublicAction[];
config: ModuleConfigSchema;
assets: ModuleAssets;
// Introspección y comunicación inter-módulo
exports: ModuleExports;
imports: ModuleImports;
provides: ModuleProvider[];
consumes: ModuleConsumer[];
// Metadata
author: string;
license: string;
tags: string[];
}
// Configuración de navegación del módulo
export interface ModuleNavigationConfig {
menuItems: NavigationMenuItem[];
breadcrumbs?: BreadcrumbConfig[];
toolbar?: ToolbarConfig[];
}
export interface NavigationMenuItem {
id: string;
label: string;
icon?: string;
route: string;
permissions?: string[];
parent?: string;
order: number;
badge?: () => Observable<number>;
}
// Componentes que pueden ser embebidos en otros módulos
export interface ModuleEmbeddableComponent {
id: string;
componentType: Type<any>;
displayName: string;
description: string;
inputs?: ComponentInput[];
permissions?: string[];
category: 'widget' | 'list' | 'form' | 'dashboard' | 'custom';
}
// Acciones públicas disponibles para otros módulos
export interface ModulePublicAction {
id: string;
actionClass: Type<any>;
displayName: string;
description: string;
permissions?: string[];
category: 'navigation' | 'crud' | 'workflow' | 'integration';
}
// Servicios/datos que el módulo proporciona a otros
export interface ModuleProvider {
id: string;
type: 'service' | 'data' | 'component' | 'asset';
interface: string;
implementation: Type<any> | string;
permissions?: string[];
}
// Servicios/datos que el módulo consume de otros
export interface ModuleConsumer {
moduleId: string;
providerId: string;
required: boolean;
fallback?: any;
}
3. Ejemplos de implementación de módulos
// Ejemplo: Módulo de Productos
export const ProductsModuleManifest: ModuleManifest = {
id: 'products',
name: 'Gestión de Productos',
version: '1.0.0',
description: 'Sistema de gestión de productos con inventario',
routes: [
{ path: 'products', component: ProductListComponent, permissions: ['products:read'] },
{ path: 'products/create', component: ProductCreateComponent, permissions: ['products:write'] },
{ path: 'products/:id', component: ProductDetailComponent, permissions: ['products:read'] },
{ path: 'products/:id/edit', component: ProductEditComponent, permissions: ['products:write'] },
],
navigation: {
menuItems: [
{
id: 'products-main',
label: 'Productos',
icon: 'inventory',
route: '/products',
order: 10,
permissions: ['products:read'],
},
{
id: 'products-create',
label: 'Nuevo Producto',
icon: 'add',
route: '/products/create',
parent: 'products-main',
order: 1,
permissions: ['products:write'],
},
],
},
embeddableComponents: [
{
id: 'product-list-widget',
componentType: ProductListWidgetComponent,
displayName: 'Lista de Productos',
description: 'Widget que muestra lista de productos filtrable',
category: 'widget',
inputs: [
{ name: 'category', type: 'string', required: false },
{ name: 'limit', type: 'number', required: false, default: 10 },
],
permissions: ['products:read'],
},
{
id: 'product-stats-dashboard',
componentType: ProductStatsDashboardComponent,
displayName: 'Estadísticas de Productos',
description: 'Dashboard con métricas de productos',
category: 'dashboard',
permissions: ['products:analytics'],
},
],
publicActions: [
{
id: 'purchase-product',
actionClass: PurchaseProductAction,
displayName: 'Comprar Producto',
description: 'Acción para comprar un producto',
category: 'workflow',
permissions: ['products:purchase'],
},
{
id: 'add-to-cart',
actionClass: AddToCartAction,
displayName: 'Agregar al Carrito',
description: 'Añadir producto al carrito de compras',
category: 'workflow',
permissions: ['products:read'],
},
],
provides: [
{
id: 'product-data-service',
type: 'service',
interface: 'IProductDataService',
implementation: ProductDataService,
permissions: ['products:read'],
},
{
id: 'product-images',
type: 'asset',
interface: 'IAssetProvider',
implementation: 'products/assets',
permissions: ['products:read'],
},
],
consumes: [
{
moduleId: 'media-assets',
providerId: 'image-service',
required: true,
},
{
moduleId: 'security',
providerId: 'auth-service',
required: true,
},
],
dependencies: ['media-assets', 'security'],
permissions: ['products:read', 'products:write', 'products:purchase', 'products:analytics'],
};
// Ejemplo: Módulo CMS
export const CmsModuleManifest: ModuleManifest = {
id: 'cms',
name: 'Sistema de Gestión de Contenidos',
version: '1.0.0',
description: 'CMS para crear y gestionar contenido',
routes: [
{ path: 'cms', component: CmsHomeComponent, permissions: ['cms:read'] },
{ path: 'cms/posts', component: PostListComponent, permissions: ['cms:read'] },
{ path: 'cms/posts/create', component: PostCreateComponent, permissions: ['cms:write'] },
{ path: 'cms/posts/:id/edit', component: PostEditComponent, permissions: ['cms:write'] },
],
navigation: {
menuItems: [
{
id: 'cms-main',
label: 'Contenido',
icon: 'article',
route: '/cms',
order: 20,
permissions: ['cms:read'],
},
{
id: 'cms-create',
label: 'Nueva Entrada',
icon: 'create',
route: '/cms/posts/create',
parent: 'cms-main',
order: 1,
permissions: ['cms:write'],
},
],
toolbar: [
{
id: 'quick-post',
label: 'Entrada Rápida',
icon: 'edit',
action: 'cms.create-quick-post',
position: 'global',
permissions: ['cms:write'],
},
],
},
embeddableComponents: [
{
id: 'post-creator-widget',
componentType: QuickPostCreatorComponent,
displayName: 'Creador de Entradas Rápido',
description: 'Widget para crear entradas desde cualquier módulo',
category: 'form',
permissions: ['cms:write'],
},
{
id: 'recent-posts-widget',
componentType: RecentPostsComponent,
displayName: 'Entradas Recientes',
description: 'Lista de entradas más recientes',
category: 'widget',
inputs: [
{ name: 'limit', type: 'number', default: 5 },
{ name: 'category', type: 'string', required: false },
],
permissions: ['cms:read'],
},
],
publicActions: [
{
id: 'create-quick-post',
actionClass: CreateQuickPostAction,
displayName: 'Crear Entrada Rápida',
description: 'Crear una nueva entrada desde cualquier lugar',
category: 'crud',
permissions: ['cms:write'],
},
{
id: 'embed-product-reference',
actionClass: EmbedProductReferenceAction,
displayName: 'Referenciar Producto',
description: 'Insertar referencia a producto en contenido',
category: 'integration',
permissions: ['cms:write'],
},
],
consumes: [
{
moduleId: 'products',
providerId: 'product-data-service',
required: false,
},
{
moduleId: 'products',
providerId: 'purchase-product',
required: false,
},
{
moduleId: 'media-assets',
providerId: 'asset-manager',
required: true,
},
],
dependencies: ['media-assets'],
permissions: ['cms:read', 'cms:write', 'cms:publish'],
};
4. Componente embebible para inter-módulos
// Componente de productos embebible en dashboard
@Component({
selector: 'app-product-list-widget',
standalone: true,
template: `
<div class="product-widget">
<h3>{{ title }}</h3>
@if (loading()) {
<div class="loading">Cargando productos...</div>
} @for (product of displayedProducts(); track product.id) {
<div class="product-item" (click)="selectProduct(product)">
<img [src]="getProductImage(product)" [alt]="product.name" />
<div class="product-info">
<h4>{{ product.name }}</h4>
<p class="price">{{ product.price | currency }}</p>
<button (click)="executeAction('purchase-product', product)" [disabled]="!canPurchase()">Comprar</button>
</div>
</div>
}
</div>
`,
})
export class ProductListWidgetComponent implements OnInit {
@Input() category?: string;
@Input() limit = 10;
@Input() title = 'Productos';
// Signals reactivos
readonly products = signal<Product[]>([]);
readonly loading = signal(false);
// Computed signal para productos filtrados
readonly displayedProducts = computed(() => {
const allProducts = this.products();
return this.category ? allProducts.filter((p) => p.category === this.category).slice(0, this.limit) : allProducts.slice(0, this.limit);
});
constructor(private productService = inject(ProductDataService), private actionRegistry = inject(ActionRegistryService), private assetService = inject(AssetService), private authService = inject(AuthService)) {}
async ngOnInit() {
await this.loadProducts();
}
private async loadProducts() {
this.loading.set(true);
try {
const products = await this.productService.getProducts();
this.products.set(products);
} finally {
this.loading.set(false);
}
}
getProductImage(product: Product): string {
// Usar servicio de assets para obtener imagen del módulo de productos
return this.assetService.getAssetUrl('products', product.imageId);
}
async executeAction(actionId: string, product: Product) {
// Ejecutar acción pública de otro módulo
try {
await this.actionRegistry.execute(actionId, { productId: product.id });
} catch (error) {
console.error('Error executing action:', error);
}
}
canPurchase(): boolean {
return this.authService.hasPermission('products:purchase');
}
selectProduct(product: Product) {
// Emitir evento para navegación
this.actionRegistry.execute('navigate-to-product', { productId: product.id });
}
}
4. Estado base con logging integrado
// Estado base con funcionalidades comunes
export abstract class BaseModuleState<T> {
constructor(protected logSystem: LogSystem, protected eventSystem: EventSystem) {}
protected logStateChange(action: string, before: T, after: T): void {
this.logSystem.info(`State change in ${this.constructor.name}`, {
action,
before,
after,
});
}
protected emitStateEvent(event: ModuleStateEvent): void {
this.eventSystem.emit(event);
}
}
Integración con el ecosistema
Dependencias fundamentales
- @libs-core/log-system: logging integrado en todos los componentes base
- @libs-core/event-system: eventos del ciclo de vida y comunicación
- @gograb-space/utilities: utilidades para validación y transformación
Uso en Module Integrator
El module-integrator utilizará module-base para:
- Validar que los módulos implementen
ModuleContract - Leer y procesar manifests de módulos
- Coordinar el ciclo de vida usando las interfaces estándar
Uso en módulos específicos
Los módulos del sistema implementarán:
@ModuleDecorator({
id: 'user-management',
version: '1.0.0',
})
export class UserManagementModule implements ModuleContract {
manifest = USER_MODULE_MANIFEST;
async initialize(context: ModuleInitializationContext): Promise<void> {
// Inicialización específica del módulo
}
// Implementar resto del contrato...
}
Patrones arquitectónicos
1. Inversión de dependencias
// Los módulos dependen de abstracciones, no de implementaciones
export abstract class BaseDataService<T> {
abstract findById(id: string): Promise<T>;
abstract create(entity: T): Promise<T>;
abstract update(id: string, changes: Partial<T>): Promise<T>;
abstract delete(id: string): Promise<void>;
}
2. Command Query Responsibility Segregation (CQRS)
// Separación de comandos y consultas
export interface ModuleCommandHandler<TCommand, TResult> {
handle(command: TCommand): Promise<CommandResult<TResult>>;
}
export interface ModuleQueryHandler<TQuery, TResult> {
handle(query: TQuery): Promise<QueryResult<TResult>>;
}
3. Domain Events
// Eventos de dominio tipados
export abstract class ModuleDomainEvent {
readonly aggregateId: string;
readonly occurredOn: Date;
readonly eventVersion: number;
constructor(aggregateId: string) {
this.aggregateId = aggregateId;
this.occurredOn = new Date();
this.eventVersion = 1;
}
}
Validación y cumplimiento
Contract Validation
// Validador de contratos de módulo
@Injectable()
export class ModuleContractValidator {
validateContract(module: any): ValidationResult {
const errors: string[] = [];
if (!this.implementsModuleContract(module)) {
errors.push('Module must implement ModuleContract');
}
if (!this.hasValidManifest(module.manifest)) {
errors.push('Module manifest is invalid');
}
return { isValid: errors.length === 0, errors };
}
}
Comandos de mantenimiento
- Ejecutar pruebas:
nx test module-base - Linting:
nx lint module-base - Build:
nx build module-base
Beneficios del diseño
- Consistencia: todos los módulos siguen la misma arquitectura
- Interoperabilidad: comunicación estándar entre módulos
- Mantenibilidad: cambios centralizados en la base afectan a todos
- Testabilidad: contratos claros facilitan testing y mocking
- Escalabilidad: nuevos módulos se integran automáticamente
- Documentación: contratos sirven como documentación viva
Consideraciones de migración
Para la implementación gradual:
- Definir contratos básicos primero
- Migrar módulos existentes uno a uno
- Mantener compatibilidad durante transición
- Validar cumplimiento en CI/CD
Notas de implementación para navegador
- Module Federation: contratos compatibles con Webpack 5 Module Federation
- Dynamic Loading: soporte para import() dinámico y lazy loading
- Browser APIs: integración con Performance API, Intersection Observer, etc.
- CSP Compliance: validación de Content Security Policy
- Memory Management: patrones para gestión eficiente de memoria en navegador
- Offline Support: contratos que soportan funcionamiento offline
- Progressive Enhancement: carga progresiva basada en capacidades del navegador
- Hot Module Replacement: soporte para HMR en desarrollo
- Tree Shaking: estructura optimizada para eliminación de código muerto
- Bundle Splitting: configuración para code splitting automático
- Service Worker Integration: contratos para cache y funcionalidad offline
- Web Worker Support: interfaces para procesamiento en background