Design

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

@libs-core/action-registry - Design & Architecture

Document Information


Table of Contents

  1. Architecture Overview
  2. Design Patterns
  3. Core Components
  4. Data Flow
  5. State Management
  6. Error Handling Strategy
  7. Performance Optimization

Architecture Overview

High-Level Architecture

┌──────────────────────────────────────────────────────────────┐
│                     CLIENT APPLICATION                        │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐ │
│  │   Components   │  │    Services    │  │     Guards     │ │
│  └────────┬───────┘  └────────┬───────┘  └────────┬───────┘ │
│           │                   │                    │          │
│           └───────────────────┼────────────────────┘          │
│                               ▼                               │
└───────────────────────────────────────────────────────────────┘
                                │
                ┌───────────────┴───────────────┐
                ▼                               ▼
        ┌──────────────────┐          ┌──────────────────┐
        │ ActionHandler    │          │ ActionRegistry   │
        │  Service         │◄─────────┤  Service         │
        │  (Invoker)       │          │  (Registry)      │
        └────────┬─────────┘          └──────────────────┘
                 │
                 ├──────┬──────────┬──────────┬──────────┐
                 ▼      ▼          ▼          ▼          ▼
            ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
            │Circuit │ │Action  │ │Action  │ │Event   │ │Logger  │
            │Breaker │ │State   │ │Discovery│ │Bus     │ │Service │
            └────────┘ └────────┘ └────────┘ └────────┘ └────────┘
                 │
                 ▼
          ┌──────────────────────────────────────────┐
          │         EXECUTABLE ACTIONS               │
          │  ┌──────────┐  ┌──────────┐  ┌────────┐ │
          │  │Component │  │ Service  │  │  HTTP  │ │
          │  │ Actions  │  │ Actions  │  │Actions │ │
          │  └──────────┘  └──────────┘  └────────┘ │
          └──────────────────────────────────────────┘

Layer Architecture

┌─────────────────────────────────────────────────────────┐
│  PRESENTATION LAYER                                     │
│  - Components, Directives, Pipes                        │
│  - ActionExecutorComponent                              │
└────────────┬────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────┐
│  ORCHESTRATION LAYER                                    │
│  - ActionHandlerService (Command Invoker)               │
│  - CircuitBreakerService (Resilience)                   │
│  - ActionStateService (State Management)                │
└────────────┬────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────┐
│  REGISTRY LAYER                                         │
│  - ActionRegistryService (Command Registry)             │
│  - ActionDiscoveryService (Module Discovery)            │
└────────────┬────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────┐
│  DOMAIN LAYER                                           │
│  - ExecutableAction implementations                     │
│  - Navigation, UI, Form, Data Actions                   │
│  - Module Action Contracts                              │
└────────────┬────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────┐
│  INFRASTRUCTURE LAYER                                   │
│  - LogSystem, EventSystem integration                   │
│  - Security, Utilities integration                      │
└─────────────────────────────────────────────────────────┘

Design Patterns

1. Command Pattern

Intent: Encapsulate action requests as objects with standardized execution interface.

Implementation:

// Command Interface
interface ExecutableAction<T> {
  execute(params: ActionParams, context: ActionContext): Observable<T>;
  validate?(params: ActionParams): Observable<boolean>;
  cleanup?(): void | Observable<void>;
}

// Concrete Command
@PublicAction({...})
@Injectable()
export class NavigateAction implements ExecutableAction<boolean> {
  execute(params, context): Observable<boolean> {
    return from(this.router.navigate([params.route]));
  }
}

// Invoker
export class ActionHandlerService {
  execute(actionId, params, context) {
    const action = this.registry.get(actionId);
    return action.execute(params, context);
  }
}

Benefits:


2. Registry Pattern

Intent: Centralized lookup and management of actions.

Implementation:

export class ActionRegistryService {
  private readonly actionsMap = signal<Map<string, ActionMetadata>>(new Map());

  register(config: ActionRegistrationConfig): void {
    const metadata = this.createMetadata(config);
    this.actionsMap.update(map => {
      const newMap = new Map(map);
      newMap.set(config.id, metadata);
      return newMap;
    });
  }

  getActionMetadata(id: string): ActionMetadata | undefined {
    return this.actionsMap().get(id);
  }
}

Benefits:


3. Circuit Breaker Pattern

Intent: Prevent cascading failures by breaking circuits on repeated failures.

States:

Implementation:

export class CircuitBreakerService {
  execute<T>(actionId: string, fn: () => Observable<T>): Observable<T> {
    const circuit = this.getCircuit(actionId);

    if (circuit.isOpen()) {
      return throwError(() => new Error('Circuit open'));
    }

    return fn().pipe(
      tap(() => circuit.onSuccess()),
      catchError(error => {
        circuit.onFailure();
        return throwError(() => error);
      })
    );
  }
}

export class CircuitBreakerState {
  private state = signal<CircuitState>('closed');
  private consecutiveFailures = signal(0);

  onFailure(): void {
    this.consecutiveFailures.update(n => n + 1);

    if (this.consecutiveFailures() >= this.config.failureThreshold) {
      this.transitionTo('open');
      this.scheduleReset();
    }
  }
}

Benefits:


4. Decorator Pattern

Intent: Attach metadata to action classes for discovery and configuration.

Implementation:

export function PublicAction(metadata: PublicActionMetadata) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    Reflect.defineMetadata('public-action', metadata, constructor);
    return constructor;
  };
}

// Usage
@PublicAction({
  id: 'navigate',
  name: 'Navigate',
  description: 'Navigates to a route',
  permissions: ['navigation.basic']
})
export class NavigateAction implements ExecutableAction<boolean> {
  // Implementation
}

Benefits:


5. Observer Pattern (via Events)

Intent: Notify interested parties of action lifecycle events.

Implementation:

// Event Definition
export class ActionExecutedEvent extends DomainEvent {
  public readonly eventName = 'action:executed';

  constructor(public readonly payload: {
    actionId: string;
    executionId: string;
    result: ActionResult;
  }) {
    super(payload, 'normal');
  }
}

// Publishing
this.eventBus.publish(new ActionExecutedEvent({
  actionId,
  executionId,
  result
})).subscribe();

// Subscribing (in any interested service/component)
this.eventBus
  .on(ActionExecutedEvent)
  .subscribe(event => {
    console.log('Action executed:', event.payload);
  });

Benefits:


Core Components

ActionRegistryService

Responsibility: Central registry for action metadata and lookup.

Key Features:

API:

class ActionRegistryService {
  // Registration
  register(config: ActionRegistrationConfig): void
  registerMany(configs: ActionRegistrationConfig[]): void
  unregister(actionId: string): boolean

  // Lookup
  getActionMetadata(actionId: string): ActionMetadata | undefined
  hasAction(actionId: string): boolean

  // Queries
  readonly availableActions: Signal<ActionMetadata[]>
  readonly getActionsByType: Signal<Map<string, ActionMetadata[]>>
  readonly getActionsByModule: Signal<Map<string, ActionMetadata[]>>
  searchByTags(tags: string[]): ActionMetadata[]
  searchByText(query: string): ActionMetadata[]
  getExecutableActions(userPermissions: string[]): ActionMetadata[]

  // State
  readonly registryState: Signal<ActionRegistryState>
  updateExecutionStats(actionId: string, success: boolean): void
  setExecutionState(state: ActionExecutionState): void

  // Utilities
  clear(): void
  exportActions(): ActionMetadata[]
  importActions(actions: ActionMetadata[]): void
}

ActionHandlerService

Responsibility: Execute actions with resilience, validation, and state management.

Key Features:

API:

class ActionHandlerService {
  // Single Execution
  execute<T>(
    actionId: string,
    parameters?: ActionParams,
    context?: ActionContext
  ): Observable<ActionResult<T>>

  // Batch Execution (Parallel)
  executeBatch<T>(
    requests: ActionBatchRequest[]
  ): Observable<ActionBatchResult<T>>

  // Sequential Execution
  executeSequence<T>(
    requests: ActionBatchRequest[]
  ): Observable<ActionResult<T>[]>

  // Transactional Execution
  executeTransaction<T>(
    requests: ActionTransactionRequest[]
  ): Observable<ActionTransactionResult<T>>

  // Execution Control
  cancelExecution(actionId: string): boolean
  cancelAllExecutions(): void
  getActiveExecutions(): Record<string, ActionExecutionState>

  // State
  readonly hasActiveExecutions: Signal<boolean>
}

Execution Flow:

1. Validate action exists
2. Update execution state → 'executing'
3. Track active execution
4. Check circuit breaker
5. Execute by type (component/service/http/custom)
6. Handle result:
   - Success → update stats, emit event, return result
   - Failure → update stats, emit error event, return error result
7. Untrack execution
8. Update state → 'success' or 'error'

CircuitBreakerService

Responsibility: Prevent cascading failures with intelligent circuit breaking.

Key Features:

API:

class CircuitBreakerService {
  // Execution
  execute<T>(
    actionId: string,
    fn: () => Observable<T>,
    config?: Partial<CircuitBreakerConfig>
  ): Observable<T>

  // Circuit Management
  getCircuit(actionId: string, config?: Partial<CircuitBreakerConfig>): CircuitBreakerState
  reset(actionId: string): void
  remove(actionId: string): void

  // Metrics
  getMetrics(actionId: string): CircuitBreakerMetrics | null
  getAllMetrics(): Record<string, CircuitBreakerMetrics>

  // State
  readonly circuitCount: Signal<number>
}

State Machine:

CLOSED (Normal)
  │
  ├─ failures < threshold → stay CLOSED
  ├─ failures ≥ threshold → transition to OPEN
  │
  ▼
OPEN (Rejecting)
  │
  ├─ wait resetTimeout → transition to HALF-OPEN
  │
  ▼
HALF-OPEN (Testing)
  │
  ├─ success count ≥ successThreshold → transition to CLOSED
  ├─ any failure → transition to OPEN

ActionStateService

Responsibility: Track action execution state and history.

Key Features:

API:

class ActionStateService {
  // State Signals
  readonly executionHistory: Signal<ExecutionRecord[]>
  readonly activeExecutions: Signal<Map<string, ExecutionInfo>>

  // State Management
  recordExecution(record: ExecutionRecord): void
  getExecutionHistory(actionId?: string): ExecutionRecord[]
  clearHistory(): void

  // Events
  readonly stateChanged$: Observable<ActionStateResetEvent>
}

ActionDiscoveryService

Responsibility: Discover actions from modules using contracts.

Key Features:

API:

class ActionDiscoveryService {
  // Discovery
  discoverModuleActions(
    moduleContract: ModuleActionContract
  ): Observable<ActionDiscoveryResult>

  discoverAllModules(
    modules: ModuleActionContract[]
  ): Observable<ActionDiscoveryResult[]>

  // Registration
  registerDiscoveredActions(
    result: ActionDiscoveryResult
  ): void

  // Validation
  validateContract(
    contract: any
  ): boolean
}

Data Flow

Action Registration Flow

Developer writes action
       │
       ▼
Marks with @PublicAction decorator
       │
       ▼
Calls ActionRegistryService.register()
       │
       ├─ Validate unique ID
       ├─ Create ActionMetadata
       ├─ Update actionsMap signal
       ├─ Emit ActionRegisteredEvent
       └─ Log registration
       │
       ▼
Action available for execution

Action Execution Flow

Client calls ActionHandlerService.execute(actionId, params)
       │
       ▼
Lookup metadata in ActionRegistryService
       │
       ├─ Action not found → return error
       │
       ▼
Check CircuitBreaker state
       │
       ├─ Circuit open → return error
       │
       ▼
Update execution state → 'executing'
       │
       ▼
Execute by type:
  ├─ Component → createComponent, execute, destroy
  ├─ Service → inject, execute
  ├─ HTTP → httpClient.post
  └─ Custom → new instance, execute
       │
       ▼
Handle result:
  ├─ Success → update stats, emit success event
  └─ Failure → update stats, emit failure event, trigger circuit breaker
       │
       ▼
Return Observable<ActionResult<T>>

Batch Execution Flow

Client calls executeBatch([{actionId1, params1}, {actionId2, params2}])
       │
       ▼
Map to individual execute() calls
       │
       ▼
forkJoin (parallel execution)
       │
       ├─ Action 1 → execute
       ├─ Action 2 → execute
       └─ Action N → execute
       │
       ▼
Collect all results (success + failures)
       │
       ▼
Return ActionBatchResult {
  total,
  successful,
  failed,
  results[],
  totalExecutionTime
}

Transaction Flow with Rollback

Client calls executeTransaction([
  { actionId1, params1, rollback: fn1 },
  { actionId2, params2, rollback: fn2 }
])
       │
       ▼
Execute actions sequentially (concatMap)
       │
  ┌────┴────┐
  │ Action1 │
  └────┬────┘
       ├─ Success → continue
       ├─ Failure → ROLLBACK
       │              │
       ▼              ▼
  ┌────┴────┐   Execute rollback functions
  │ Action2 │   in reverse order:
  └────┬────┘     fn2(), fn1()
       ├─ Success → continue            │
       ├─ Failure → ROLLBACK           │
       │                                │
       ▼                                ▼
All actions successful      Return { success: false, rolledBack: true }
       │
       ▼
Return { success: true, rolledBack: false }

State Management

Signals-based Reactivity

All core services use Angular 20 signals for reactive state management:

// ActionRegistryService
private readonly actionsMap = signal<Map<string, ActionMetadata>>(new Map());
readonly availableActions = computed(() => Array.from(this.actionsMap().values()));
readonly executionState = signal<ActionExecutionState>('idle');

// CircuitBreakerState
private readonly state = signal<CircuitState>('closed');
private readonly consecutiveFailures = signal<number>(0);
readonly isOpen = computed(() => this.state() === 'open');
readonly successRate = computed(() => {
  const total = this.totalCalls();
  return total === 0 ? 100 : (this.successfulCalls() / total) * 100;
});

Benefits:


Error Handling Strategy

Error Hierarchy

ActionError (Base)
  ├─ ActionNotFoundError
  ├─ ActionValidationError
  ├─ ActionPermissionError
  ├─ ActionExecutionError
  │   ├─ ActionTimeoutError
  │   ├─ ActionCircuitBreakerError
  │   └─ ActionRollbackError
  └─ ActionContractError

Error Handling Flow

Error occurs during execution
       │
       ▼
Caught by catchError operator
       │
       ├─ Log error with context
       ├─ Emit ActionExecutionFailedEvent
       ├─ Update circuit breaker (onFailure)
       ├─ Update execution stats
       └─ Create ActionResult with error
       │
       ▼
Return Observable<ActionResult> with success: false

Logging Strategy

// Error logging with full context
this.logger.error('Action execution failed', {
  actionId,
  params,
  context: context.toJSON(),
  error: {
    message: error.message,
    stack: error.stack,
    type: error.constructor.name
  },
  executionTime,
  circuitState
});

Performance Optimization

1. Signal-based Reactivity

2. Lazy Evaluation

3. Parallel Execution

4. Caching Strategy

5. Memory Management


Technology Stack


Best Practices

1. Always Return Observables

// ✅ Correct
execute(params, context): Observable<Result> {
  return from(this.router.navigate([...]));
}

// ❌ Wrong
execute(params, context): Observable<Result> {
  this.router.navigate([...]);  // Returns Promise!
  return of(true);
}

2. Use Signals for State

// ✅ Correct
private readonly count = signal(0);
readonly doubleCount = computed(() => this.count() * 2);

// ❌ Wrong
private count = 0;
get doubleCount() {  // Not reactive
  return this.count * 2;
}

3. Clean Up Resources

// ✅ Correct
execute(params, context): Observable<T> {
  const componentRef = createComponent(...);

  return this.doWork().pipe(
    finalize(() => componentRef.destroy())
  );
}

// ❌ Wrong - memory leak
execute(params, context): Observable<T> {
  const componentRef = createComponent(...);
  return this.doWork();  // Never destroyed!
}

4. Use Circuit Breaker for External Calls

// ✅ Correct
return this.circuitBreaker.execute(
  actionId,
  () => this.httpClient.post(url, data),
  { failureThreshold: 5 }
);

// ❌ Wrong - no protection
return this.httpClient.post(url, data);

Testing Strategy

Unit Tests

describe('ActionHandlerService', () => {
  it('should execute action successfully', (done) => {
    const service = TestBed.inject(ActionHandlerService);

    service.execute('test-action', {}).subscribe(result => {
      expect(result.success).toBe(true);
      done();
    });
  });
});

Integration Tests

describe('Action Registry Integration', () => {
  it('should register and execute action', (done) => {
    const registry = TestBed.inject(ActionRegistryService);
    const handler = TestBed.inject(ActionHandlerService);

    registry.register({...});

    handler.execute('test-action', {}).subscribe(result => {
      expect(result.success).toBe(true);
      done();
    });
  });
});

Document Status: Complete Implementation Status: Fully Implemented Last Review: 2025-10-14