32 Migration und Upgrade-Strategien

Stellen Sie sich vor, Sie müssten Ihr Zuhause renovieren, während Sie noch darin wohnen. Die Küche muss modernisiert werden, aber Sie können nicht wochenlang ohne kochen. Das Badezimmer braucht neue Leitungen, aber Sie benötigen täglich fließendes Wasser. Genau diese Herausforderung stellt sich bei der Migration und dem Upgrade von Software-Systemen - Sie müssen das System verbessern und modernisieren, während es gleichzeitig seinen täglichen Betrieb aufrechterhält.

Migration und Upgrade-Strategien sind wie sorgfältig geplante Renovierungsprojekte. Sie erfordern detaillierte Planung, schrittweise Umsetzung und die Gewissheit, dass Sie jederzeit zu einem funktionierenden Zustand zurückkehren können. In der Welt von NestJS begegnen Sie verschiedenen Arten von Migrationen: der Übergang von anderen Frameworks wie Express.js, Upgrades auf neuere NestJS-Versionen, Datenbank-Schema-Migrationen und die Herausforderung, Breaking Changes zu bewältigen, ohne den Betrieb zu unterbrechen.

Was macht Migrationen besonders komplex? Anders als bei einem Neubau müssen Sie mit bestehenden Strukturen arbeiten, die möglicherweise nicht den aktuellen Best Practices entsprechen. Alte Codebasen haben oft historisch gewachsene Abhängigkeiten, unorthodoxe Lösungen für damalige Probleme und eine Vielzahl von Integrationspunkten, die alle berücksichtigt werden müssen.

Denken Sie an eine Migration wie an das Umziehen einer Bibliothek. Sie können nicht einfach alle Bücher in Kisten werfen und hoffen, dass alles funktioniert. Stattdessen müssen Sie ein System entwickeln: Bücher kategorisieren, ihre Beziehungen zueinander verstehen, einen Plan für den Transport erstellen und sicherstellen, dass wichtige Referenzwerke während des gesamten Prozesses zugänglich bleiben. Genau diese systematische Herangehensweise benötigen Sie für erfolgreiche Software-Migrationen.

32.1 Von Express.js zu NestJS

Der Übergang von Express.js zu NestJS ist wie der Wechsel von einem handwerklichen Ansatz zu einem ingenieursmäßigen Vorgehen beim Hausbau. Express.js gibt Ihnen die Freiheit, alles nach Ihren Vorstellungen zu gestalten - was sowohl ein Segen als auch ein Fluch sein kann. NestJS hingegen bietet Ihnen ein bewährtes architektonisches Framework, das Struktur und Konsistenz gewährleistet.

Verstehen Sie zunächst die philosophischen Unterschiede zwischen den beiden Frameworks. Express.js folgt dem Prinzip der minimalen Abstraktion - es gibt Ihnen die grundlegenden Werkzeuge und lässt Sie entscheiden, wie Sie diese verwenden. NestJS hingegen basiert auf bewährten Prinzipien der objektorientierten Programmierung und bietet Ihnen Patterns wie Dependency Injection, Decorators und modulare Architektur von Haus aus.

Diese Migration ist nicht nur eine technische Übung, sondern auch eine architektonische Transformation. Sie bewegen sich von einem funktionalen, middleware-basierten Ansatz zu einem objektorientierten, service-basierten Modell. Das bedeutet, dass Sie nicht nur Code übersetzen, sondern Ihre Denkweise über die Anwendungsstruktur ändern müssen.

// migration/express-to-nestjs/src/strategies/migration-strategy.service.ts
// Ein systematischer Ansatz für die Migration von Express.js zu NestJS
import { Injectable, Logger } from '@nestjs/common';

interface ExpressRoute {
  method: string;
  path: string;
  handlers: string[];
  middleware: string[];
  originalCode: string;
}

interface MigrationPlan {
  phase: number;
  description: string;
  expressComponents: ExpressRoute[];
  nestjsEquivalent: {
    controllerClass: string;
    serviceClasses: string[];
    dtoClasses: string[];
    guardClasses: string[];
    interceptorClasses: string[];
  };
  dependencies: string[];
  testStrategy: string;
  rollbackPlan: string;
}

@Injectable()
export class MigrationStrategyService {
  private readonly logger = new Logger(MigrationStrategyService.name);

  /**
   * Analysiert eine bestehende Express.js-Anwendung und erstellt einen Migrationsplan
   * Diese Methode ist der erste Schritt jeder erfolgreichen Migration
   */
  async analyzeExpressApplication(expressAppPath: string): Promise<{
    routes: ExpressRoute[];
    middleware: string[];
    dependencies: string[];
    complexity: 'low' | 'medium' | 'high';
    estimatedEffort: string;
    recommendedStrategy: 'big-bang' | 'strangler-fig' | 'parallel-run';
  }> {
    this.logger.log('Starting Express.js application analysis...');

    // Diese Analyse würde in einer echten Implementierung den Express.js-Code parsen
    // Hier zeigen wir die konzeptuelle Herangehensweise
    const analysisResult = await this.performCodeAnalysis(expressAppPath);
    
    const complexity = this.assessComplexity(analysisResult);
    const recommendedStrategy = this.determineOptimalStrategy(complexity, analysisResult);
    
    this.logger.log(`Analysis complete. Complexity: ${complexity}, Strategy: ${recommendedStrategy}`);
    
    return {
      routes: analysisResult.routes,
      middleware: analysisResult.middleware,
      dependencies: analysisResult.dependencies,
      complexity,
      estimatedEffort: this.calculateEffortEstimate(complexity, analysisResult.routes.length),
      recommendedStrategy,
    };
  }

  /**
   * Erstellt einen detaillierten, phasenweisen Migrationsplan
   * Denken Sie daran: Ein guter Plan ist die Hälfte der Arbeit
   */
  createMigrationPlan(analysisResult: any): MigrationPlan[] {
    const migrationPhases: MigrationPlan[] = [];

    // Phase 1: Infrastruktur und Basis-Setup
    migrationPhases.push({
      phase: 1,
      description: 'Setup NestJS-Infrastruktur und grundlegende Konfiguration',
      expressComponents: [],
      nestjsEquivalent: {
        controllerClass: 'N/A',
        serviceClasses: ['ConfigService', 'LoggerService'],
        dtoClasses: [],
        guardClasses: [],
        interceptorClasses: ['LoggingInterceptor'],
      },
      dependencies: ['@nestjs/core', '@nestjs/common', '@nestjs/config'],
      testStrategy: 'Integration tests for basic NestJS setup',
      rollbackPlan: 'Remove NestJS dependencies, revert to Express.js',
    });

    // Phase 2: Authentifizierung und Sicherheit
    migrationPhases.push({
      phase: 2,
      description: 'Migration der Authentifizierungs- und Sicherheits-Middleware',
      expressComponents: analysisResult.routes.filter((route: ExpressRoute) => 
        route.middleware.some((mw: string) => mw.includes('auth') || mw.includes('security'))
      ),
      nestjsEquivalent: {
        controllerClass: 'AuthController',
        serviceClasses: ['AuthService', 'JwtService'],
        dtoClasses: ['LoginDto', 'RegisterDto'],
        guardClasses: ['JwtAuthGuard', 'RolesGuard'],
        interceptorClasses: [],
      },
      dependencies: ['@nestjs/jwt', '@nestjs/passport', 'passport-jwt'],
      testStrategy: 'Unit tests for auth services, E2E tests for auth flows',
      rollbackPlan: 'Revert to Express.js auth middleware, disable NestJS guards',
    });

    // Phase 3: Core Business Logic
    migrationPhases.push({
      phase: 3,
      description: 'Migration der Haupt-Business-Logic und API-Endpoints',
      expressComponents: analysisResult.routes.filter((route: ExpressRoute) => 
        !route.middleware.some((mw: string) => mw.includes('auth'))
      ),
      nestjsEquivalent: {
        controllerClass: 'Multiple controllers based on domain',
        serviceClasses: ['UserService', 'ProductService', 'OrderService'],
        dtoClasses: ['CreateUserDto', 'UpdateProductDto', 'OrderDto'],
        guardClasses: [],
        interceptorClasses: ['ValidationInterceptor', 'TransformInterceptor'],
      },
      dependencies: ['class-validator', 'class-transformer'],
      testStrategy: 'Comprehensive unit and integration testing',
      rollbackPlan: 'Feature flags to switch between Express and NestJS endpoints',
    });

    return migrationPhases;
  }

  /**
   * Konvertiert Express.js-Routen zu NestJS-Controllern
   * Dies ist oft der komplexeste Teil der Migration
   */
  convertExpressRouteToNestJS(expressRoute: ExpressRoute): {
    controllerCode: string;
    serviceCode: string;
    dtoCode: string;
  } {
    // Analysiere die Express.js-Route und ihre Middleware
    const routeAnalysis = this.analyzeExpressRoute(expressRoute);
    
    // Generiere entsprechenden NestJS-Controller-Code
    const controllerCode = this.generateControllerCode(routeAnalysis);
    
    // Extrahiere Business Logic in Services
    const serviceCode = this.generateServiceCode(routeAnalysis);
    
    // Erstelle DTOs für Request/Response-Validierung
    const dtoCode = this.generateDtoCode(routeAnalysis);
    
    return {
      controllerCode,
      serviceCode,
      dtoCode,
    };
  }

  private async performCodeAnalysis(appPath: string): Promise<{
    routes: ExpressRoute[];
    middleware: string[];
    dependencies: string[];
  }> {
    // In einer echten Implementierung würden Sie hier:
    // 1. package.json parsen für Dependencies
    // 2. Express.js-Routen-Definitionen analysieren
    // 3. Middleware-Stack verstehen
    // 4. Datenbank-Verbindungen und Models identifizieren
    
    // Simulierte Analyse-Ergebnisse für Demonstration
    return {
      routes: [
        {
          method: 'GET',
          path: '/api/users',
          handlers: ['authMiddleware', 'getUsersHandler'],
          middleware: ['cors', 'helmet', 'auth'],
          originalCode: 'app.get("/api/users", authMiddleware, getUsersHandler)'
        },
        {
          method: 'POST',
          path: '/api/users',
          handlers: ['authMiddleware', 'validateUser', 'createUserHandler'],
          middleware: ['cors', 'helmet', 'auth', 'validation'],
          originalCode: 'app.post("/api/users", authMiddleware, validateUser, createUserHandler)'
        }
      ],
      middleware: ['cors', 'helmet', 'morgan', 'auth', 'validation'],
      dependencies: ['express', 'cors', 'helmet', 'morgan', 'jsonwebtoken']
    };
  }

  private assessComplexity(analysisResult: any): 'low' | 'medium' | 'high' {
    let complexityScore = 0;
    
    // Faktoren für Komplexität
    if (analysisResult.routes.length > 50) complexityScore += 2;
    if (analysisResult.middleware.length > 10) complexityScore += 1;
    if (analysisResult.dependencies.length > 20) complexityScore += 1;
    
    // Spezielle Komplexitäts-Indikatoren
    const hasCustomMiddleware = analysisResult.middleware.some((mw: string) => 
      !['cors', 'helmet', 'morgan'].includes(mw)
    );
    if (hasCustomMiddleware) complexityScore += 2;
    
    if (complexityScore <= 2) return 'low';
    if (complexityScore <= 4) return 'medium';
    return 'high';
  }

  private determineOptimalStrategy(
    complexity: string,
    analysisResult: any
  ): 'big-bang' | 'strangler-fig' | 'parallel-run' {
    // Big Bang: Komplett ersetzen (nur für kleine, einfache Apps)
    if (complexity === 'low' && analysisResult.routes.length < 20) {
      return 'big-bang';
    }
    
    // Strangler Fig: Schrittweise ersetzen (für mittlere Komplexität)
    if (complexity === 'medium') {
      return 'strangler-fig';
    }
    
    // Parallel Run: Beide Systeme gleichzeitig (für hohe Komplexität)
    return 'parallel-run';
  }

  private calculateEffortEstimate(complexity: string, routeCount: number): string {
    const baseHours = routeCount * 2; // 2 Stunden pro Route als Basis
    const complexityMultiplier = {
      'low': 1,
      'medium': 1.5,
      'high': 2.5
    };
    
    const totalHours = baseHours * complexityMultiplier[complexity as keyof typeof complexityMultiplier];
    const weeks = Math.ceil(totalHours / 40); // 40 Stunden pro Woche
    
    return `${totalHours} Stunden (ca. ${weeks} Wochen)`;
  }

  private analyzeExpressRoute(route: ExpressRoute): any {
    // Analysiere Express.js-Route für Konvertierung
    return {
      httpMethod: route.method,
      path: route.path,
      hasAuthentication: route.middleware.includes('auth'),
      hasValidation: route.middleware.includes('validation'),
      businessLogic: this.extractBusinessLogic(route.originalCode),
      responseType: this.inferResponseType(route.originalCode),
    };
  }

  private generateControllerCode(routeAnalysis: any): string {
    const authDecorator = routeAnalysis.hasAuthentication ? '@UseGuards(JwtAuthGuard)\n  ' : '';
    const validationPipe = routeAnalysis.hasValidation ? '@UsePipes(ValidationPipe)\n  ' : '';
    
    return `
@Controller('${routeAnalysis.path.split('/')[1]}')
export class ${this.toPascalCase(routeAnalysis.path.split('/')[1])}Controller {
  constructor(private readonly ${routeAnalysis.path.split('/')[1]}Service: ${this.toPascalCase(routeAnalysis.path.split('/')[1])}Service) {}

  ${authDecorator}${validationPipe}@${routeAnalysis.httpMethod}('${routeAnalysis.path.split('/').slice(2).join('/')}')
  async handle${this.toPascalCase(routeAnalysis.httpMethod)}Request(
    @Body() dto: ${this.toPascalCase(routeAnalysis.httpMethod)}${this.toPascalCase(routeAnalysis.path.split('/')[1])}Dto
  ): Promise<${routeAnalysis.responseType}> {
    return this.${routeAnalysis.path.split('/')[1]}Service.${routeAnalysis.httpMethod.toLowerCase()}Operation(dto);
  }
}`;
  }

  private generateServiceCode(routeAnalysis: any): string {
    return `
@Injectable()
export class ${this.toPascalCase(routeAnalysis.path.split('/')[1])}Service {
  constructor(
    // Inject necessary dependencies here
  ) {}

  async ${routeAnalysis.httpMethod.toLowerCase()}Operation(dto: any): Promise<${routeAnalysis.responseType}> {
    // Business logic migrated from Express.js handler
    ${routeAnalysis.businessLogic}
  }
}`;
  }

  private generateDtoCode(routeAnalysis: any): string {
    return `
export class ${this.toPascalCase(routeAnalysis.httpMethod)}${this.toPascalCase(routeAnalysis.path.split('/')[1])}Dto {
  // Define request/response structure with validation decorators
  @IsString()
  @IsNotEmpty()
  readonly field: string;
  
  // Add more fields based on route analysis
}`;
  }

  private extractBusinessLogic(originalCode: string): string {
    // In einer echten Implementierung würden Sie hier den Express.js-Code parsen
    // und die Business Logic extrahieren
    return '// Migrated business logic from Express.js handler';
  }

  private inferResponseType(originalCode: string): string {
    // Analysiere Response-Typen aus Express.js-Code
    if (originalCode.includes('res.json')) return 'object';
    if (originalCode.includes('res.send')) return 'string';
    return 'any';
  }

  private toPascalCase(str: string): string {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
}

Der Schlüssel zu einer erfolgreichen Express.js-zu-NestJS-Migration liegt in der schrittweisen Herangehensweise. Beginnen Sie nicht mit dem komplexesten Teil Ihrer Anwendung, sondern wählen Sie einfache, isolierte Komponenten für den Anfang. Dies gibt Ihnen die Möglichkeit, den Migrationsprozess zu verfeinern und Vertrauen in die neue Architektur aufzubauen.

Ein wichtiger Aspekt ist das Konzept der “Strangler Fig”-Migration, benannt nach einer Pflanzenart, die andere Bäume langsam umwächst und schließlich ersetzt. Bei dieser Strategie implementieren Sie neue Features in NestJS, während Sie bestehende Express.js-Routen schrittweise ersetzen. Dies minimiert das Risiko und ermöglicht es Ihnen, aus dem Migrationsprozess zu lernen.

32.2 NestJS Version Upgrades

NestJS-Version-Upgrades sind wie das Renovieren eines Hauses, das Sie bereits lieben, aber das neue Standards und verbesserte Funktionen benötigt. Im Gegensatz zu einer kompletten Neugestaltung arbeiten Sie hier mit einer Struktur, die bereits Ihren Anforderungen entspricht, aber modernisiert werden muss.

Version-Upgrades bringen sowohl Chancen als auch Herausforderungen mit sich. Neue Features können Ihre Produktivität steigern und Performance verbessern, aber sie können auch Breaking Changes einführen, die sorgfältige Planung und Tests erfordern. Das NestJS-Team bemüht sich um Rückwärtskompatibilität, aber wie in jedem sich entwickelnden Framework sind Breaking Changes manchmal unvermeidlich.

Verstehen Sie zunächst die Versionierungsstrategie von NestJS. Das Framework folgt der Semantic Versioning (SemVer) Konvention: Major.Minor.Patch. Major-Updates können Breaking Changes enthalten, Minor-Updates fügen neue Features hinzu (ohne Breaking Changes), und Patch-Updates beheben Bugs. Diese Information hilft Ihnen dabei, das Risiko und den Aufwand eines Upgrades einzuschätzen.

// upgrade-strategy/src/services/version-upgrade-manager.service.ts
// Ein systematischer Ansatz für NestJS-Version-Upgrades
import { Injectable, Logger } from '@nestjs/common';
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';

interface UpgradeAnalysis {
  currentVersion: string;
  targetVersion: string;
  upgradeType: 'major' | 'minor' | 'patch';
  breakingChanges: BreakingChange[];
  deprecatedFeatures: DeprecatedFeature[];
  newFeatures: NewFeature[];
  estimatedEffort: string;
  riskLevel: 'low' | 'medium' | 'high';
  recommendedStrategy: 'immediate' | 'staged' | 'gradual';
}

interface BreakingChange {
  component: string;
  description: string;
  impact: 'high' | 'medium' | 'low';
  migrationGuide: string;
  codeExample?: {
    before: string;
    after: string;
  };
}

interface DeprecatedFeature {
  feature: string;
  deprecatedIn: string;
  removedIn: string;
  replacement: string;
  migrationPath: string;
}

interface NewFeature {
  feature: string;
  description: string;
  benefit: string;
  adoptionComplexity: 'low' | 'medium' | 'high';
}

@Injectable()
export class VersionUpgradeManagerService {
  private readonly logger = new Logger(VersionUpgradeManagerService.name);

  /**
   * Analysiert eine geplante Version-Upgrade und bewertet Auswirkungen
   * Dies ist der wichtigste erste Schritt für jedes Upgrade
   */
  async analyzeUpgrade(
    currentVersion: string,
    targetVersion: string
  ): Promise<UpgradeAnalysis> {
    this.logger.log(`Analyzing upgrade from ${currentVersion} to ${targetVersion}`);

    // Bestimme Upgrade-Typ basierend auf Version-Differenz
    const upgradeType = this.determineUpgradeType(currentVersion, targetVersion);
    
    // Lade Breaking Changes für dieses Upgrade
    const breakingChanges = await this.loadBreakingChanges(currentVersion, targetVersion);
    
    // Identifiziere veraltete Features
    const deprecatedFeatures = await this.loadDeprecatedFeatures(currentVersion, targetVersion);
    
    // Sammle Information über neue Features
    const newFeatures = await this.loadNewFeatures(currentVersion, targetVersion);
    
    // Bewerte Risiko und Aufwand
    const riskLevel = this.assessRiskLevel(upgradeType, breakingChanges);
    const estimatedEffort = this.calculateUpgradeEffort(upgradeType, breakingChanges, deprecatedFeatures);
    const recommendedStrategy = this.determineUpgradeStrategy(riskLevel, upgradeType);

    const analysis: UpgradeAnalysis = {
      currentVersion,
      targetVersion,
      upgradeType,
      breakingChanges,
      deprecatedFeatures,
      newFeatures,
      estimatedEffort,
      riskLevel,
      recommendedStrategy,
    };

    this.logger.log(`Upgrade analysis complete. Risk: ${riskLevel}, Strategy: ${recommendedStrategy}`);
    return analysis;
  }

  /**
   * Erstellt einen detaillierten Upgrade-Plan mit Checkpoints
   * Ein guter Plan reduziert das Risiko erheblich
   */
  createUpgradePlan(analysis: UpgradeAnalysis): {
    phases: UpgradePhase[];
    rollbackStrategy: string;
    testingStrategy: string;
    communicationPlan: string;
  } {
    const phases: UpgradePhase[] = [];

    // Phase 1: Vorbereitung und Backup
    phases.push({
      phase: 1,
      name: 'Vorbereitung und Sicherung',
      description: 'Erstelle Backups und bereite Upgrade-Umgebung vor',
      tasks: [
        'Code-Repository auf separaten Branch erstellen',
        'Vollständige Test-Suite ausführen (Baseline)',
        'Abhängigkeiten dokumentieren',
        'Rollback-Plan erstellen',
        'Team über Upgrade informieren'
      ],
      estimatedTime: '2-4 Stunden',
      prerequisites: [],
      successCriteria: ['Alle Tests grün', 'Backup erstellt', 'Branch erstellt'],
      rollbackTrigger: 'Backup-Fehler oder fehlende Tests'
    });

    // Phase 2: Abhängigkeiten aktualisieren
    phases.push({
      phase: 2,
      name: 'Abhängigkeiten-Update',
      description: 'Aktualisiere NestJS und verwandte Pakete',
      tasks: [
        'package.json aktualisieren',
        'Peer-Dependencies prüfen',
        'npm install ausführen',
        'Potentielle Konflikte lösen',
        'Lock-Datei validieren'
      ],
      estimatedTime: '1-2 Stunden',
      prerequisites: ['Phase 1 abgeschlossen'],
      successCriteria: ['Installation erfolgreich', 'Keine Dependency-Konflikte'],
      rollbackTrigger: 'Unlösbare Dependency-Konflikte'
    });

    // Phase 3: Breaking Changes beheben
    if (analysis.breakingChanges.length > 0) {
      phases.push({
        phase: 3,
        name: 'Breaking Changes Migration',
        description: 'Behebe alle identifizierten Breaking Changes',
        tasks: analysis.breakingChanges.map(change => 
          `Behebe: ${change.component} - ${change.description}`
        ),
        estimatedTime: this.calculateBreakingChangesTime(analysis.breakingChanges),
        prerequisites: ['Phase 2 abgeschlossen'],
        successCriteria: ['Alle Breaking Changes behoben', 'Code kompiliert'],
        rollbackTrigger: 'Unlösbare Breaking Changes'
      });
    }

    // Phase 4: Deprecated Features migrieren
    if (analysis.deprecatedFeatures.length > 0) {
      phases.push({
        phase: phases.length + 1,
        name: 'Deprecated Features Migration',
        description: 'Migriere von veralteten zu neuen APIs',
        tasks: analysis.deprecatedFeatures.map(feature => 
          `Migriere: ${feature.feature}${feature.replacement}`
        ),
        estimatedTime: this.calculateDeprecationTime(analysis.deprecatedFeatures),
        prerequisites: [`Phase ${phases.length} abgeschlossen`],
        successCriteria: ['Keine Deprecation-Warnings', 'Neue APIs implementiert'],
        rollbackTrigger: 'Migration zu komplex oder fehleranfällig'
      });
    }

    // Phase 5: Testing und Validierung
    phases.push({
      phase: phases.length + 1,
      name: 'Testing und Validierung',
      description: 'Umfassendes Testing der upgegradeten Anwendung',
      tasks: [
        'Unit-Tests ausführen',
        'Integration-Tests ausführen',
        'E2E-Tests ausführen',
        'Performance-Tests durchführen',
        'Manuelle Verifikation kritischer Flows'
      ],
      estimatedTime: '4-8 Stunden',
      prerequisites: [`Phase ${phases.length} abgeschlossen`],
      successCriteria: ['Alle Tests bestehen', 'Performance-Ziele erreicht'],
      rollbackTrigger: 'Kritische Tests schlagen fehl'
    });

    return {
      phases,
      rollbackStrategy: this.createRollbackStrategy(analysis),
      testingStrategy: this.createTestingStrategy(analysis),
      communicationPlan: this.createCommunicationPlan(analysis),
    };
  }

  /**
   * Führt automatische Code-Transformationen für bekannte Breaking Changes durch
   * Automatisierung reduziert Fehler und spart Zeit
   */
  async performAutomaticMigrations(
    projectPath: string,
    breakingChanges: BreakingChange[]
  ): Promise<{
    automatedMigrations: string[];
    manualMigrations: string[];
    warnings: string[];
  }> {
    const automatedMigrations: string[] = [];
    const manualMigrations: string[] = [];
    const warnings: string[] = [];

    for (const change of breakingChanges) {
      try {
        if (this.canAutomate(change)) {
          await this.performAutomaticMigration(projectPath, change);
          automatedMigrations.push(`${change.component}: ${change.description}`);
        } else {
          manualMigrations.push(`${change.component}: ${change.description}`);
        }
      } catch (error) {
        warnings.push(`Failed to automate ${change.component}: ${error.message}`);
        manualMigrations.push(`${change.component}: ${change.description} (automation failed)`);
      }
    }

    this.logger.log(`Migration complete: ${automatedMigrations.length} automated, ${manualMigrations.length} manual`);

    return {
      automatedMigrations,
      manualMigrations,
      warnings,
    };
  }

  /**
   * Validiert ein Upgrade durch umfassende Tests
   * Validation ist entscheidend für ein erfolgreiches Upgrade
   */
  async validateUpgrade(projectPath: string): Promise<{
    isValid: boolean;
    testResults: TestResult[];
    performanceImpact: PerformanceImpact;
    recommendations: string[];
  }> {
    this.logger.log('Starting upgrade validation...');

    // Führe verschiedene Test-Kategorien aus
    const testResults: TestResult[] = [];
    
    // Unit Tests
    const unitTestResult = await this.runUnitTests(projectPath);
    testResults.push(unitTestResult);

    // Integration Tests
    const integrationTestResult = await this.runIntegrationTests(projectPath);
    testResults.push(integrationTestResult);

    // E2E Tests
    const e2eTestResult = await this.runE2ETests(projectPath);
    testResults.push(e2eTestResult);

    // Performance Tests
    const performanceImpact = await this.analyzePerformanceImpact(projectPath);

    // Bestimme Gesamt-Validierungsstatus
    const isValid = testResults.every(result => result.passed) && 
                   performanceImpact.degradation < 10; // Max 10% Performance-Verlust

    // Generiere Empfehlungen basierend auf Ergebnissen
    const recommendations = this.generateUpgradeRecommendations(testResults, performanceImpact);

    return {
      isValid,
      testResults,
      performanceImpact,
      recommendations,
    };
  }

  private determineUpgradeType(currentVersion: string, targetVersion: string): 'major' | 'minor' | 'patch' {
    const current = this.parseVersion(currentVersion);
    const target = this.parseVersion(targetVersion);

    if (target.major > current.major) return 'major';
    if (target.minor > current.minor) return 'minor';
    return 'patch';
  }

  private parseVersion(version: string): { major: number; minor: number; patch: number } {
    const parts = version.replace(/^[v^~]/, '').split('.').map(Number);
    return {
      major: parts[0] || 0,
      minor: parts[1] || 0,
      patch: parts[2] || 0,
    };
  }

  private async loadBreakingChanges(currentVersion: string, targetVersion: string): Promise<BreakingChange[]> {
    // In einer echten Implementierung würden Sie Breaking Changes aus
    // Release Notes, Migration Guides oder einer Datenbank laden
    return [
      {
        component: '@UseGuards()',
        description: 'Guard-Reihenfolge wurde geändert',
        impact: 'medium',
        migrationGuide: 'Überprüfe Guard-Reihenfolge in Controllers',
        codeExample: {
          before: '@UseGuards(RoleGuard, AuthGuard)',
          after: '@UseGuards(AuthGuard, RoleGuard)'
        }
      },
      {
        component: 'ValidationPipe',
        description: 'Standard-Optionen wurden geändert',
        impact: 'low',
        migrationGuide: 'Explizite Konfiguration von ValidationPipe-Optionen',
        codeExample: {
          before: 'new ValidationPipe()',
          after: 'new ValidationPipe({ transform: true, whitelist: true })'
        }
      }
    ];
  }

  private async loadDeprecatedFeatures(currentVersion: string, targetVersion: string): Promise<DeprecatedFeature[]> {
    return [
      {
        feature: 'forRootAsync() with useClass',
        deprecatedIn: '8.0.0',
        removedIn: '10.0.0',
        replacement: 'forRootAsync() with useFactory',
        migrationPath: 'Konvertiere useClass zu useFactory in Modul-Konfiguration'
      }
    ];
  }

  private async loadNewFeatures(currentVersion: string, targetVersion: string): Promise<NewFeature[]> {
    return [
      {
        feature: 'Enhanced Swagger Support',
        description: 'Verbesserte automatische API-Dokumentation',
        benefit: 'Reduziert manuellen Aufwand für API-Dokumentation',
        adoptionComplexity: 'low'
      },
      {
        feature: 'Native ESM Support',
        description: 'Unterstützung für ECMAScript-Module',
        benefit: 'Bessere Performance und Tree-Shaking',
        adoptionComplexity: 'medium'
      }
    ];
  }

  private assessRiskLevel(upgradeType: string, breakingChanges: BreakingChange[]): 'low' | 'medium' | 'high' {
    if (upgradeType === 'patch') return 'low';
    
    const highImpactChanges = breakingChanges.filter(change => change.impact === 'high').length;
    
    if (upgradeType === 'major' && highImpactChanges > 3) return 'high';
    if (upgradeType === 'major' || highImpactChanges > 0) return 'medium';
    
    return 'low';
  }

  private calculateUpgradeEffort(
    upgradeType: string,
    breakingChanges: BreakingChange[],
    deprecatedFeatures: DeprecatedFeature[]
  ): string {
    let baseHours = upgradeType === 'major' ? 16 : upgradeType === 'minor' ? 8 : 4;
    
    // Zusätzliche Zeit für Breaking Changes
    baseHours += breakingChanges.length * 2;
    
    // Zusätzliche Zeit für Deprecated Features
    baseHours += deprecatedFeatures.length * 1;
    
    return `${baseHours} Stunden (${Math.ceil(baseHours / 8)} Arbeitstage)`;
  }

  private determineUpgradeStrategy(riskLevel: string, upgradeType: string): 'immediate' | 'staged' | 'gradual' {
    if (riskLevel === 'low' && upgradeType === 'patch') return 'immediate';
    if (riskLevel === 'medium') return 'staged';
    return 'gradual';
  }

  // Weitere private Methoden für spezifische Upgrade-Funktionen...
  private canAutomate(change: BreakingChange): boolean {
    // Bestimme ob eine Breaking Change automatisch migriert werden kann
    return change.codeExample !== undefined && change.impact !== 'high';
  }

  private async performAutomaticMigration(projectPath: string, change: BreakingChange): Promise<void> {
    // Implementierung der automatischen Code-Transformation
    // Dies würde AST-Manipulation oder Regex-basierte Ersetzungen verwenden
  }

  private createRollbackStrategy(analysis: UpgradeAnalysis): string {
    return `Git-Branch-basierter Rollback mit automatischen Tests zur Validierung`;
  }

  private createTestingStrategy(analysis: UpgradeAnalysis): string {
    return `Umfassende Test-Suite mit ${analysis.riskLevel}-Risiko-spezifischen Zusatztests`;
  }

  private createCommunicationPlan(analysis: UpgradeAnalysis): string {
    return `Team-Benachrichtigung mit ${analysis.estimatedEffort} Zeitplan und Rollback-Optionen`;
  }

  // Platzhalter für weitere Test-Methoden
  private async runUnitTests(projectPath: string): Promise<TestResult> {
    return { category: 'unit', passed: true, details: 'All unit tests passed' };
  }

  private async runIntegrationTests(projectPath: string): Promise<TestResult> {
    return { category: 'integration', passed: true, details: 'All integration tests passed' };
  }

  private async runE2ETests(projectPath: string): Promise<TestResult> {
    return { category: 'e2e', passed: true, details: 'All E2E tests passed' };
  }

  private async analyzePerformanceImpact(projectPath: string): Promise<PerformanceImpact> {
    return { degradation: 5, improvement: ['Memory usage optimized'] };
  }

  private generateUpgradeRecommendations(testResults: TestResult[], performanceImpact: PerformanceImpact): string[] {
    return ['Monitor performance post-upgrade', 'Update documentation'];
  }

  private calculateBreakingChangesTime(changes: BreakingChange[]): string {
    const hours = changes.reduce((total, change) => {
      const multiplier = { high: 4, medium: 2, low: 1 };
      return total + multiplier[change.impact];
    }, 0);
    return `${hours} Stunden`;
  }

  private calculateDeprecationTime(features: DeprecatedFeature[]): string {
    return `${features.length * 2} Stunden`;
  }
}

// Hilfsdatenstrukturen
interface UpgradePhase {
  phase: number;
  name: string;
  description: string;
  tasks: string[];
  estimatedTime: string;
  prerequisites: string[];
  successCriteria: string[];
  rollbackTrigger: string;
}

interface TestResult {
  category: string;
  passed: boolean;
  details: string;
}

interface PerformanceImpact {
  degradation: number; // Prozent
  improvement: string[];
}

Ein erfolgreiches NestJS-Upgrade erfordert mehr als nur das Aktualisieren der package.json. Sie müssen die Auswirkungen verstehen, Ihre Anwendung auf Kompatibilität testen und einen klaren Rollback-Plan haben. Der Schlüssel liegt darin, das Upgrade als einen Prozess zu betrachten, nicht als ein einzelnes Ereignis.

Denken Sie daran, dass ein Upgrade auch eine Gelegenheit ist. Während Sie Breaking Changes beheben, können Sie gleichzeitig technische Schulden abbauen, veraltete Patterns modernisieren und neue Features einführen, die Ihre Anwendung verbessern.

32.3 Database Schema Migrations

Datenbank-Schema-Migrationen sind wie die Renovierung der Fundamente eines Hauses, während das Haus noch bewohnt ist. Sie müssen die Struktur ändern, ohne die darauf aufbauenden Systeme zu beschädigen oder den Betrieb zu unterbrechen. Diese Art von Migration erfordert besondere Sorgfalt, da Daten das wertvollste Asset vieler Anwendungen sind.

Im Gegensatz zu Code-Migrationen sind Datenbank-Migrationen oft irreversibel oder schwer rückgängig zu machen. Ein gelöschtes Feld oder eine falsch ausgeführte Datenkonvertierung kann zu dauerhaftem Datenverlust führen. Deshalb erfordern Schema-Migrationen eine besonders sorgfältige Planung, umfassende Tests und robuste Backup-Strategien.

Verstehen Sie zunächst die verschiedenen Arten von Schema-Änderungen und ihre jeweiligen Risiken. Hinzufügen neuer Spalten oder Tabellen ist in der Regel sicher und rückwärtskompatibel. Das Entfernen oder Umbenennen von Spalten hingegen kann bestehende Anwendungen beschädigen und erfordert koordinierte Deployments. Datentyp-Änderungen können Konvertierungen erfordern, die bei großen Datensätzen erhebliche Zeit in Anspruch nehmen.

// database-migrations/src/services/schema-migration-manager.service.ts
// Ein sicherer und umfassender Ansatz für Datenbank-Schema-Migrationen
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, QueryRunner } from 'typeorm';

interface MigrationPlan {
  migrationId: string;
  description: string;
  type: 'schema' | 'data' | 'combined';
  riskLevel: 'low' | 'medium' | 'high' | 'critical';
  estimatedDuration: string;
  backwardCompatible: boolean;
  rollbackStrategy: 'automatic' | 'manual' | 'not-possible';
  prerequisites: string[];
  steps: MigrationStep[];
  validationQueries: string[];
  postMigrationTasks: string[];
}

interface MigrationStep {
  stepNumber: number;
  description: string;
  sql: string;
  expectedAffectedRows?: number;
  rollbackSql?: string;
  validationQuery?: string;
  isReversible: boolean;
  executionTime?: 'fast' | 'medium' | 'slow';
}

interface MigrationResult {
  success: boolean;
  executedSteps: number;
  totalSteps: number;
  executionTime: number;
  errors: string[];
  warnings: string[];
  rollbackAvailable: boolean;
}

@Injectable()
export class SchemaMigrationManagerService {
  private readonly logger = new Logger(SchemaMigrationManagerService.name);

  constructor(private readonly dataSource: DataSource) {}

  /**
   * Analysiert und plant eine Datenbank-Schema-Migration
   * Umfassende Planung ist der Schlüssel zu sicheren Migrationen
   */
  async planMigration(
    fromSchema: string,
    toSchema: string,
    migrationName: string
  ): Promise<MigrationPlan> {
    this.logger.log(`Planning migration: ${migrationName}`);

    // Analysiere Schema-Unterschiede
    const schemaDiff = await this.analyzeSchemaChanges(fromSchema, toSchema);
    
    // Bewerte Risiko basierend auf Änderungstypen
    const riskLevel = this.assessMigrationRisk(schemaDiff);
    
    // Schätze Ausführungszeit
    const estimatedDuration = await this.estimateMigrationDuration(schemaDiff);
    
    // Bestimme Rückwärtskompatibilität
    const backwardCompatible = this.isBackwardCompatible(schemaDiff);
    
    // Generiere Migration-Steps
    const steps = await this.generateMigrationSteps(schemaDiff);
    
    // Erstelle Validierungsabfragen
    const validationQueries = this.generateValidationQueries(schemaDiff);

    const migrationPlan: MigrationPlan = {
      migrationId: this.generateMigrationId(migrationName),
      description: `Schema migration: ${migrationName}`,
      type: this.determineMigrationType(schemaDiff),
      riskLevel,
      estimatedDuration,
      backwardCompatible,
      rollbackStrategy: this.determineRollbackStrategy(steps),
      prerequisites: this.identifyPrerequisites(schemaDiff),
      steps,
      validationQueries,
      postMigrationTasks: this.generatePostMigrationTasks(schemaDiff),
    };

    this.logger.log(`Migration plan created: ${riskLevel} risk, ${steps.length} steps`);
    return migrationPlan;
  }

  /**
   * Führt eine geplante Migration sicher aus
   * Implementiert umfassende Sicherheitsmaßnahmen und Logging
   */
  async executeMigration(plan: MigrationPlan): Promise<MigrationResult> {
    this.logger.log(`Starting migration execution: ${plan.migrationId}`);
    
    const startTime = Date.now();
    let executedSteps = 0;
    const errors: string[] = [];
    const warnings: string[] = [];
    let queryRunner: QueryRunner | undefined;

    try {
      // Erstelle Backup vor Migration (für kritische Migrationen)
      if (plan.riskLevel === 'critical' || plan.riskLevel === 'high') {
        await this.createBackup(plan.migrationId);
      }

      // Initialisiere Transaktion für Schema-Migrationen
      queryRunner = this.dataSource.createQueryRunner();
      await queryRunner.connect();
      await queryRunner.startTransaction();

      // Führe Pre-Migration-Validierung durch
      const preValidation = await this.validatePreMigration(queryRunner, plan);
      if (!preValidation.isValid) {
        throw new Error(`Pre-migration validation failed: ${preValidation.errors.join(', ')}`);
      }

      // Führe Migration-Steps aus
      for (const step of plan.steps) {
        try {
          this.logger.debug(`Executing step ${step.stepNumber}: ${step.description}`);
          
          await this.executeStep(queryRunner, step);
          executedSteps++;
          
          // Validiere Step-Ergebnis wenn Validierungsabfrage vorhanden
          if (step.validationQuery) {
            const validation = await this.validateStep(queryRunner, step);
            if (!validation.isValid) {
              warnings.push(`Step ${step.stepNumber} validation warning: ${validation.message}`);
            }
          }
          
        } catch (stepError) {
          errors.push(`Step ${step.stepNumber} failed: ${stepError.message}`);
          
          // Bei kritischen Fehlern: sofortiger Rollback
          if (step.isReversible && plan.rollbackStrategy === 'automatic') {
            await this.rollbackStep(queryRunner, step);
          }
          
          throw stepError;
        }
      }

      // Post-Migration-Validierung
      const postValidation = await this.validatePostMigration(queryRunner, plan);
      if (!postValidation.isValid) {
        throw new Error(`Post-migration validation failed: ${postValidation.errors.join(', ')}`);
      }

      // Commit bei erfolgreichem Abschluss
      await queryRunner.commitTransaction();
      
      // Führe Post-Migration-Tasks aus (außerhalb der Transaktion)
      await this.executePostMigrationTasks(plan);

      const executionTime = Date.now() - startTime;
      
      this.logger.log(`Migration completed successfully in ${executionTime}ms`);
      
      return {
        success: true,
        executedSteps,
        totalSteps: plan.steps.length,
        executionTime,
        errors,
        warnings,
        rollbackAvailable: plan.rollbackStrategy !== 'not-possible',
      };

    } catch (error) {
      // Rollback bei Fehlern
      if (queryRunner && queryRunner.isTransactionActive) {
        await queryRunner.rollbackTransaction();
      }

      const executionTime = Date.now() - startTime;
      errors.push(error.message);

      this.logger.error(`Migration failed after ${executionTime}ms: ${error.message}`);

      return {
        success: false,
        executedSteps,
        totalSteps: plan.steps.length,
        executionTime,
        errors,
        warnings,
        rollbackAvailable: plan.rollbackStrategy !== 'not-possible',
      };

    } finally {
      if (queryRunner) {
        await queryRunner.release();
      }
    }
  }

  /**
   * Implementiert Zero-Downtime-Migration-Strategien
   * Ermöglicht Schema-Änderungen ohne Service-Unterbrechung
   */
  async executeZeroDowntimeMigration(plan: MigrationPlan): Promise<MigrationResult> {
    this.logger.log(`Starting zero-downtime migration: ${plan.migrationId}`);

    // Zero-Downtime-Migrationen folgen speziellen Patterns:
    // 1. Expand-Contract Pattern
    // 2. Blue-Green Database Pattern
    // 3. Shadow Column Pattern

    if (!this.supportsZeroDowntime(plan)) {
      throw new Error('Migration does not support zero-downtime execution');
    }

    // Implementiere Expand-Phase
    const expandPlan = this.createExpandPhasePlan(plan);
    const expandResult = await this.executeMigration(expandPlan);
    
    if (!expandResult.success) {
      throw new Error('Expand phase failed');
    }

    // Warte auf Application-Deployment (externe Koordination erforderlich)
    await this.waitForApplicationDeployment();

    // Implementiere Contract-Phase
    const contractPlan = this.createContractPhasePlan(plan);
    const contractResult = await this.executeMigration(contractPlan);

    return {
      success: expandResult.success && contractResult.success,
      executedSteps: expandResult.executedSteps + contractResult.executedSteps,
      totalSteps: expandResult.totalSteps + contractResult.totalSteps,
      executionTime: expandResult.executionTime + contractResult.executionTime,
      errors: [...expandResult.errors, ...contractResult.errors],
      warnings: [...expandResult.warnings, ...contractResult.warnings],
      rollbackAvailable: false, // Zero-downtime Migrationen erfordern spezielle Rollback-Strategien
    };
  }

  /**
   * Validiert eine Migration durch Trockenläufe und Tests
   * Reduziert das Risiko durch umfassende Vorab-Tests
   */
  async validateMigration(plan: MigrationPlan): Promise<{
    isValid: boolean;
    errors: string[];
    warnings: string[];
    performanceImpact: string;
    recommendedAdjustments: string[];
  }> {
    this.logger.log(`Validating migration: ${plan.migrationId}`);

    const errors: string[] = [];
    const warnings: string[] = [];
    const recommendedAdjustments: string[] = [];

    try {
      // Erstelle Test-Datenbank-Kopie
      const testDb = await this.createTestDatabase();
      
      // Führe Migration im Test-Modus durch
      const testResult = await this.executeTestMigration(testDb, plan);
      
      if (!testResult.success) {
        errors.push(...testResult.errors);
      }

      // Performance-Impact-Analyse
      const performanceImpact = await this.analyzePerformanceImpact(testDb, plan);
      
      // Validiere Datenintegrität
      const integrityCheck = await this.validateDataIntegrity(testDb, plan);
      if (!integrityCheck.isValid) {
        errors.push(...integrityCheck.errors);
      }

      // Backward-Compatibility-Tests
      if (!plan.backwardCompatible) {
        warnings.push('Migration is not backward compatible - coordinate with application deployment');
      }

      // Rollback-Tests
      if (plan.rollbackStrategy === 'automatic') {
        const rollbackTest = await this.testRollback(testDb, plan);
        if (!rollbackTest.success) {
          errors.push('Rollback test failed');
          recommendedAdjustments.push('Improve rollback SQL statements');
        }
      }

      // Cleanup Test-Datenbank
      await this.cleanupTestDatabase(testDb);

      return {
        isValid: errors.length === 0,
        errors,
        warnings,
        performanceImpact,
        recommendedAdjustments,
      };

    } catch (error) {
      errors.push(`Validation failed: ${error.message}`);
      return {
        isValid: false,
        errors,
        warnings,
        performanceImpact: 'Unknown',
        recommendedAdjustments: ['Fix validation errors before proceeding'],
      };
    }
  }

  private async analyzeSchemaChanges(fromSchema: string, toSchema: string): Promise<any> {
    // Implementierung für Schema-Differenz-Analyse
    // Dies würde Schema-Vergleich, Tabellen-Strukturen, Indizes, etc. analysieren
    return {
      tablesAdded: ['user_preferences'],
      tablesRemoved: [],
      columnsAdded: [{ table: 'users', column: 'last_login_at', type: 'timestamp' }],
      columnsRemoved: [{ table: 'users', column: 'deprecated_field', type: 'varchar' }],
      indexesAdded: [{ table: 'users', columns: ['email'], unique: true }],
      constraintsAdded: [],
      dataTransformations: []
    };
  }

  private assessMigrationRisk(schemaDiff: any): 'low' | 'medium' | 'high' | 'critical' {
    let riskScore = 0;
    
    // Risiko-Faktoren bewerten
    if (schemaDiff.tablesRemoved.length > 0) riskScore += 10;
    if (schemaDiff.columnsRemoved.length > 0) riskScore += 5;
    if (schemaDiff.dataTransformations.length > 0) riskScore += 3;
    
    if (riskScore >= 10) return 'critical';
    if (riskScore >= 5) return 'high';
    if (riskScore >= 2) return 'medium';
    return 'low';
  }

  private async estimateMigrationDuration(schemaDiff: any): Promise<string> {
    // Schätze basierend auf Änderungstypen und Datenmenge
    let estimatedMinutes = 5; // Basis-Zeit
    
    estimatedMinutes += schemaDiff.tablesAdded.length * 2;
    estimatedMinutes += schemaDiff.columnsAdded.length * 1;
    estimatedMinutes += schemaDiff.indexesAdded.length * 10; // Indizes können lange dauern
    
    return `${estimatedMinutes} Minuten`;
  }

  private isBackwardCompatible(schemaDiff: any): boolean {
    // Nicht rückwärtskompatibel wenn Tabellen oder Spalten entfernt werden
    return schemaDiff.tablesRemoved.length === 0 && schemaDiff.columnsRemoved.length === 0;
  }

  private async generateMigrationSteps(schemaDiff: any): Promise<MigrationStep[]> {
    const steps: MigrationStep[] = [];
    let stepNumber = 1;

    // Schritt 1: Neue Tabellen hinzufügen
    for (const table of schemaDiff.tablesAdded) {
      steps.push({
        stepNumber: stepNumber++,
        description: `Create table: ${table}`,
        sql: `CREATE TABLE ${table} (...)`, // Vereinfacht
        rollbackSql: `DROP TABLE ${table}`,
        isReversible: true,
        executionTime: 'fast',
      });
    }

    // Schritt 2: Neue Spalten hinzufügen
    for (const column of schemaDiff.columnsAdded) {
      steps.push({
        stepNumber: stepNumber++,
        description: `Add column: ${column.table}.${column.column}`,
        sql: `ALTER TABLE ${column.table} ADD COLUMN ${column.column} ${column.type}`,
        rollbackSql: `ALTER TABLE ${column.table} DROP COLUMN ${column.column}`,
        isReversible: true,
        executionTime: 'fast',
      });
    }

    // Schritt 3: Indizes hinzufügen (kann länger dauern)
    for (const index of schemaDiff.indexesAdded) {
      steps.push({
        stepNumber: stepNumber++,
        description: `Create index on: ${index.table}(${index.columns.join(', ')})`,
        sql: `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX idx_${index.table}_${index.columns.join('_')} ON ${index.table} (${index.columns.join(', ')})`,
        rollbackSql: `DROP INDEX idx_${index.table}_${index.columns.join('_')}`,
        isReversible: true,
        executionTime: 'slow',
      });
    }

    return steps;
  }

  private generateValidationQueries(schemaDiff: any): string[] {
    const queries: string[] = [];
    
    // Validiere dass neue Tabellen existieren
    for (const table of schemaDiff.tablesAdded) {
      queries.push(`SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '${table}'`);
    }
    
    // Validiere dass neue Spalten existieren
    for (const column of schemaDiff.columnsAdded) {
      queries.push(`SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '${column.table}' AND column_name = '${column.column}'`);
    }
    
    return queries;
  }

  // Weitere private Methoden für spezifische Migration-Funktionen...
  private generateMigrationId(name: string): string {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    return `${timestamp}_${name.replace(/\s+/g, '_').toLowerCase()}`;
  }

  private determineMigrationType(schemaDiff: any): 'schema' | 'data' | 'combined' {
    const hasSchemaChanges = schemaDiff.tablesAdded.length > 0 || schemaDiff.columnsAdded.length > 0;
    const hasDataChanges = schemaDiff.dataTransformations.length > 0;
    
    if (hasSchemaChanges && hasDataChanges) return 'combined';
    if (hasDataChanges) return 'data';
    return 'schema';
  }

  private determineRollbackStrategy(steps: MigrationStep[]): 'automatic' | 'manual' | 'not-possible' {
    const allReversible = steps.every(step => step.isReversible && step.rollbackSql);
    return allReversible ? 'automatic' : 'manual';
  }

  private identifyPrerequisites(schemaDiff: any): string[] {
    const prerequisites: string[] = [];
    
    if (schemaDiff.columnsRemoved.length > 0) {
      prerequisites.push('Ensure application no longer uses removed columns');
    }
    
    if (schemaDiff.indexesAdded.length > 0) {
      prerequisites.push('Consider maintenance window for index creation');
    }
    
    return prerequisites;
  }

  private generatePostMigrationTasks(schemaDiff: any): string[] {
    const tasks: string[] = [];
    
    if (schemaDiff.indexesAdded.length > 0) {
      tasks.push('Analyze query performance with new indexes');
    }
    
    tasks.push('Update application configuration if needed');
    tasks.push('Monitor database performance');
    
    return tasks;
  }

  // Platzhalter für weitere Implementierungen
  private async createBackup(migrationId: string): Promise<void> {
    this.logger.log(`Creating backup for migration: ${migrationId}`);
  }

  private async validatePreMigration(queryRunner: QueryRunner, plan: MigrationPlan): Promise<{ isValid: boolean; errors: string[] }> {
    return { isValid: true, errors: [] };
  }

  private async executeStep(queryRunner: QueryRunner, step: MigrationStep): Promise<void> {
    await queryRunner.query(step.sql);
  }

  private async validateStep(queryRunner: QueryRunner, step: MigrationStep): Promise<{ isValid: boolean; message: string }> {
    if (step.validationQuery) {
      const result = await queryRunner.query(step.validationQuery);
      return { isValid: true, message: 'Validation passed' };
    }
    return { isValid: true, message: 'No validation query provided' };
  }

  private async rollbackStep(queryRunner: QueryRunner, step: MigrationStep): Promise<void> {
    if (step.rollbackSql) {
      await queryRunner.query(step.rollbackSql);
    }
  }

  private async validatePostMigration(queryRunner: QueryRunner, plan: MigrationPlan): Promise<{ isValid: boolean; errors: string[] }> {
    return { isValid: true, errors: [] };
  }

  private async executePostMigrationTasks(plan: MigrationPlan): Promise<void> {
    for (const task of plan.postMigrationTasks) {
      this.logger.log(`Executing post-migration task: ${task}`);
    }
  }

  private supportsZeroDowntime(plan: MigrationPlan): boolean {
    // Bestimme ob Migration Zero-Downtime-fähig ist
    return plan.backwardCompatible && plan.riskLevel !== 'critical';
  }

  private createExpandPhasePlan(plan: MigrationPlan): MigrationPlan {
    // Erstelle Plan für Expand-Phase (nur additive Änderungen)
    const expandSteps = plan.steps.filter(step => 
      step.description.includes('CREATE') || step.description.includes('ADD')
    );
    
    return { ...plan, steps: expandSteps };
  }

  private createContractPhasePlan(plan: MigrationPlan): MigrationPlan {
    // Erstelle Plan für Contract-Phase (Remove-Operationen)
    const contractSteps = plan.steps.filter(step => 
      step.description.includes('DROP') || step.description.includes('REMOVE')
    );
    
    return { ...plan, steps: contractSteps };
  }

  private async waitForApplicationDeployment(): Promise<void> {
    // In einer echten Implementierung würden Sie hier auf
    // externe Signale warten oder APIs aufrufen
    this.logger.log('Waiting for application deployment...');
  }

  // Weitere Implementierungen für Test- und Validierungsmethoden...
  private async createTestDatabase(): Promise<any> {
    return {};
  }

  private async executeTestMigration(testDb: any, plan: MigrationPlan): Promise<MigrationResult> {
    return { success: true, executedSteps: 0, totalSteps: 0, executionTime: 0, errors: [], warnings: [], rollbackAvailable: true };
  }

  private async analyzePerformanceImpact(testDb: any, plan: MigrationPlan): Promise<string> {
    return 'Minimal performance impact expected';
  }

  private async validateDataIntegrity(testDb: any, plan: MigrationPlan): Promise<{ isValid: boolean; errors: string[] }> {
    return { isValid: true, errors: [] };
  }

  private async testRollback(testDb: any, plan: MigrationPlan): Promise<{ success: boolean }> {
    return { success: true };
  }

  private async cleanupTestDatabase(testDb: any): Promise<void> {
    // Aufräumen der Test-Datenbank
  }
}

Die Verwaltung von Datenbank-Schema-Migrationen erfordert ein Gleichgewicht zwischen Sicherheit und Agilität. Während Sie vorsichtig genug sein müssen, um Datenverlust zu vermeiden, dürfen Sie nicht so vorsichtig werden, dass Innovationen blockiert werden. Der Schlüssel liegt in automatisierten Tests, umfassenden Validierungen und der Fähigkeit, bei Problemen schnell und sicher zurückzusetzen.

Denken Sie daran: Eine gut geplante und getestete Migration ist wie eine Versicherungspolice - Sie hoffen, dass Sie sie nie brauchen werden, aber wenn Sie sie brauchen, sind Sie froh, dass Sie sie haben. Die Zeit, die Sie in Planung und Tests investieren, zahlt sich durch vermiedene Ausfälle und Datenverluste mehrfach aus.

32.4 Breaking Changes Management

Breaking Changes Management ist wie das Navigieren durch ein Minenfeld, während Sie eine Gruppe von Menschen sicher ans Ziel führen. Jeder falsche Schritt kann Schäden verursachen, aber mit der richtigen Vorbereitung, Kommunikation und schrittweisen Herangehensweise können Sie sicher durch die gefährlichsten Veränderungen navigieren.

Breaking Changes sind unvermeidlich in der Software-Entwicklung. Sie entstehen, wenn APIs geändert werden, wenn veraltete Features entfernt werden oder wenn fundamentale Architekturentscheidungen überarbeitet werden müssen. Das Ziel ist nicht, Breaking Changes zu vermeiden, sondern sie so zu verwalten, dass der Schaden minimiert und der Nutzen maximiert wird.

Der erste Schritt im Breaking Changes Management ist die Identifikation und Kategorisierung. Nicht alle Breaking Changes sind gleich geschaffen. Einige betreffen nur wenige Entwickler und können schnell behoben werden. Andere können weitreichende Auswirkungen haben und erfordern koordinierte Anstrengungen über Wochen oder Monate.

// breaking-changes/src/services/breaking-changes-manager.service.ts
// Ein systematischer Ansatz für das Management von Breaking Changes
import { Injectable, Logger } from '@nestjs/common';

interface BreakingChange {
  id: string;
  title: string;
  description: string;
  impact: 'low' | 'medium' | 'high' | 'critical';
  category: 'api' | 'config' | 'dependency' | 'architecture' | 'data';
  introducedIn: string;
  affectedComponents: string[];
  migrationPath: string;
  automatable: boolean;
  estimatedMigrationTime: string;
  examples?: {
    before: string;
    after: string;
    explanation: string;
  };
  workarounds?: string[];
  deprecationPhase?: {
    deprecatedIn: string;
    warningPeriod: string;
    removedIn: string;
  };
}

interface MigrationStrategy {
  name: string;
  description: string;
  phases: MigrationPhase[];
  timeline: string;
  riskLevel: 'low' | 'medium' | 'high';
  communicationPlan: CommunicationPlan;
  supportLevel: 'basic' | 'extended' | 'premium';
}

interface MigrationPhase {
  phase: number;
  name: string;
  duration: string;
  activities: string[];
  deliverables: string[];
  exitCriteria: string[];
}

interface CommunicationPlan {
  announcementDate: Date;
  deprecationWarnings: Date;
  migrationGuidesRelease: Date;
  supportCutoff: Date;
  channels: string[];
  targetAudiences: string[];
}

@Injectable()
export class BreakingChangesManagerService {
  private readonly logger = new Logger(BreakingChangesManagerService.name);

  /**
   * Analysiert geplante Breaking Changes und bewertet ihre Auswirkungen
   * Diese Analyse ist fundamental für alle weiteren Entscheidungen
   */
  async analyzeBreakingChanges(
    currentVersion: string,
    targetVersion: string,
    proposedChanges: any[]
  ): Promise<{
    breakingChanges: BreakingChange[];
    impactAssessment: {
      totalChanges: number;
      criticalChanges: number;
      affectedUsers: number;
      estimatedMigrationEffort: string;
    };
    recommendedStrategy: string;
  }> {
    this.logger.log(`Analyzing breaking changes from ${currentVersion} to ${targetVersion}`);

    const breakingChanges: BreakingChange[] = [];
    
    // Analysiere jeden vorgeschlagenen Change
    for (const change of proposedChanges) {
      const breakingChange = await this.analyzeIndividualChange(change, targetVersion);
      if (breakingChange) {
        breakingChanges.push(breakingChange);
      }
    }

    // Bewerte Gesamtauswirkungen
    const impactAssessment = this.assessOverallImpact(breakingChanges);
    
    // Empfehle Strategie basierend auf Impact
    const recommendedStrategy = this.recommendMigrationStrategy(breakingChanges, impactAssessment);

    return {
      breakingChanges,
      impactAssessment,
      recommendedStrategy,
    };
  }

  /**
   * Entwickelt eine umfassende Migrationsstrategie für Breaking Changes
   * Eine gute Strategie minimiert Störungen und maximiert den Adoptionserfolg
   */
  createMigrationStrategy(
    breakingChanges: BreakingChange[],
    constraints: {
      timeline: string;
      resourceLimitations: string[];
      businessPriorities: string[];
    }
  ): MigrationStrategy {
    this.logger.log('Creating migration strategy for breaking changes');

    // Gruppiere Changes nach Komplexität und Impact
    const changeGroups = this.groupChangesByComplexity(breakingChanges);
    
    // Entwickle phasenweise Herangehensweise
    const phases = this.createMigrationPhases(changeGroups, constraints);
    
    // Erstelle Kommunikationsplan
    const communicationPlan = this.createCommunicationPlan(breakingChanges, phases);
    
    // Bestimme Risiko-Level
    const riskLevel = this.assessStrategyRisk(breakingChanges, phases);

    return {
      name: `Migration Strategy v${Date.now()}`,
      description: 'Comprehensive strategy for managing breaking changes',
      phases,
      timeline: this.calculateOverallTimeline(phases),
      riskLevel,
      communicationPlan,
      supportLevel: this.determineSupportLevel(breakingChanges),
    };
  }

  /**
   * Implementiert schrittweise Deprecation-Warnings
   * Gibt Entwicklern Zeit, sich auf Changes vorzubereiten
   */
  async implementDeprecationWarnings(
    breakingChanges: BreakingChange[]
  ): Promise<{
    warningsImplemented: number;
    codeChangesRequired: string[];
    documentationUpdates: string[];
  }> {
    this.logger.log('Implementing deprecation warnings for breaking changes');

    const warningsImplemented: number[] = [];
    const codeChangesRequired: string[] = [];
    const documentationUpdates: string[] = [];

    for (const change of breakingChanges) {
      if (change.deprecationPhase) {
        // Implementiere Code-Level-Warnings
        const codeWarnings = await this.addCodeDeprecationWarnings(change);
        codeChangesRequired.push(...codeWarnings);

        // Aktualisiere Dokumentation
        const docUpdates = await this.updateDeprecationDocumentation(change);
        documentationUpdates.push(...docUpdates);

        warningsImplemented.push(1);
      }
    }

    return {
      warningsImplemented: warningsImplemented.length,
      codeChangesRequired,
      documentationUpdates,
    };
  }

  /**
   * Generiert automatische Migration-Tools und -Scripts
   * Automatisierung reduziert den Aufwand für Entwickler erheblich
   */
  async generateMigrationTools(
    breakingChanges: BreakingChange[]
  ): Promise<{
    automationTools: AutomationTool[];
    manualMigrationGuides: MigrationGuide[];
    testingSuites: TestSuite[];
  }> {
    this.logger.log('Generating migration tools for breaking changes');

    const automationTools: AutomationTool[] = [];
    const manualMigrationGuides: MigrationGuide[] = [];
    const testingSuites: TestSuite[] = [];

    for (const change of breakingChanges) {
      if (change.automatable) {
        // Erstelle Automatisierungs-Tool
        const tool = await this.createAutomationTool(change);
        automationTools.push(tool);
      } else {
        // Erstelle manuellen Migration-Guide
        const guide = await this.createMigrationGuide(change);
        manualMigrationGuides.push(guide);
      }

      // Erstelle Test-Suite für Validierung
      const testSuite = await this.createTestSuite(change);
      testingSuites.push(testSuite);
    }

    return {
      automationTools,
      manualMigrationGuides,
      testingSuites,
    };
  }

  /**
   * Koordiniert die Ausführung einer Breaking Change Migration
   * Überwacht Fortschritt und verwaltet Kommunikation
   */
  async executeMigrationStrategy(
    strategy: MigrationStrategy
  ): Promise<{
    overallSuccess: boolean;
    completedPhases: number;
    remainingPhases: number;
    issues: string[];
    adoptionRate: number;
    feedbackSummary: string;
  }> {
    this.logger.log(`Executing migration strategy: ${strategy.name}`);

    let completedPhases = 0;
    const issues: string[] = [];
    let adoptionRate = 0;

    try {
      // Sende initiale Ankündigung
      await this.sendMigrationAnnouncement(strategy);

      // Führe jede Phase aus
      for (const phase of strategy.phases) {
        this.logger.log(`Starting phase ${phase.phase}: ${phase.name}`);

        try {
          // Führe Phase-Aktivitäten aus
          await this.executePhaseActivities(phase);

          // Prüfe Exit-Kriterien
          const phaseSuccess = await this.validatePhaseCompletion(phase);
          
          if (phaseSuccess) {
            completedPhases++;
            
            // Messe Adoption-Rate
            adoptionRate = await this.measureAdoptionRate(strategy);
            
            // Sammle Feedback
            await this.collectPhaseFeedback(phase);
            
          } else {
            issues.push(`Phase ${phase.phase} did not meet exit criteria`);
          }

        } catch (phaseError) {
          issues.push(`Phase ${phase.phase} execution failed: ${phaseError.message}`);
          
          // Entscheide ob fortfahren oder abbrechen
          if (phase.phase <= 2) { // Kritische frühe Phasen
            throw phaseError;
          }
        }
      }

      // Sammle finales Feedback
      const feedbackSummary = await this.generateFeedbackSummary(strategy);

      return {
        overallSuccess: issues.length === 0 && completedPhases === strategy.phases.length,
        completedPhases,
        remainingPhases: strategy.phases.length - completedPhases,
        issues,
        adoptionRate,
        feedbackSummary,
      };

    } catch (error) {
      this.logger.error(`Migration strategy execution failed: ${error.message}`);
      
      return {
        overallSuccess: false,
        completedPhases,
        remainingPhases: strategy.phases.length - completedPhases,
        issues: [...issues, error.message],
        adoptionRate,
        feedbackSummary: 'Migration failed - no feedback collected',
      };
    }
  }

  private async analyzeIndividualChange(change: any, targetVersion: string): Promise<BreakingChange | null> {
    // Analysiere ob Change tatsächlich breaking ist
    if (!this.isBreakingChange(change)) {
      return null;
    }

    // Bewerte Impact
    const impact = this.assessChangeImpact(change);
    
    // Bestimme betroffene Komponenten
    const affectedComponents = this.identifyAffectedComponents(change);
    
    // Prüfe Automatisierbarkeit
    const automatable = this.canAutomate(change);

    return {
      id: this.generateChangeId(change),
      title: change.title,
      description: change.description,
      impact,
      category: this.categorizeChange(change),
      introducedIn: targetVersion,
      affectedComponents,
      migrationPath: this.generateMigrationPath(change),
      automatable,
      estimatedMigrationTime: this.estimateMigrationTime(change),
      examples: this.generateExamples(change),
    };
  }

  private assessOverallImpact(breakingChanges: BreakingChange[]): any {
    const totalChanges = breakingChanges.length;
    const criticalChanges = breakingChanges.filter(c => c.impact === 'critical').length;
    
    // Schätze betroffene Benutzer basierend auf Component-Popularität
    const affectedUsers = this.estimateAffectedUsers(breakingChanges);
    
    // Berechne Gesamt-Migrationsaufwand
    const totalEffort = breakingChanges.reduce((sum, change) => {
      const hours = this.parseEffortHours(change.estimatedMigrationTime);
      return sum + hours;
    }, 0);

    return {
      totalChanges,
      criticalChanges,
      affectedUsers,
      estimatedMigrationEffort: `${totalEffort} hours (${Math.ceil(totalEffort / 8)} days)`,
    };
  }

  private recommendMigrationStrategy(breakingChanges: BreakingChange[], impact: any): string {
    if (impact.criticalChanges > 3 || impact.affectedUsers > 10000) {
      return 'gradual-migration-with-extended-support';
    }
    
    if (impact.criticalChanges > 0 || impact.affectedUsers > 1000) {
      return 'phased-migration-with-deprecation-period';
    }
    
    return 'standard-migration-with-warnings';
  }

  private groupChangesByComplexity(breakingChanges: BreakingChange[]): any {
    return {
      simple: breakingChanges.filter(c => c.impact === 'low' && c.automatable),
      moderate: breakingChanges.filter(c => c.impact === 'medium' || (c.impact === 'low' && !c.automatable)),
      complex: breakingChanges.filter(c => c.impact === 'high' || c.impact === 'critical'),
    };
  }

  private createMigrationPhases(changeGroups: any, constraints: any): MigrationPhase[] {
    const phases: MigrationPhase[] = [];

    // Phase 1: Vorbereitung und Kommunikation
    phases.push({
      phase: 1,
      name: 'Preparation and Communication',
      duration: '2 weeks',
      activities: [
        'Announce breaking changes',
        'Release migration guides',
        'Implement deprecation warnings',
        'Set up support channels'
      ],
      deliverables: [
        'Migration documentation',
        'Deprecation warnings in code',
        'Community announcements'
      ],
      exitCriteria: [
        'All documentation published',
        'Deprecation warnings active',
        'Community informed'
      ],
    });

    // Phase 2: Automation und Tools
    phases.push({
      phase: 2,
      name: 'Automation and Tools',
      duration: '3 weeks',
      activities: [
        'Release migration automation tools',
        'Provide code examples',
        'Host migration workshops',
        'Answer community questions'
      ],
      deliverables: [
        'Automated migration scripts',
        'Example repositories',
        'Video tutorials'
      ],
      exitCriteria: [
        'Migration tools tested',
        'Examples verified',
        'Workshop feedback positive'
      ],
    });

    // Phase 3: Graduelle Migration
    if (changeGroups.complex.length > 0) {
      phases.push({
        phase: 3,
        name: 'Gradual Migration',
        duration: '8 weeks',
        activities: [
          'Support parallel implementation',
          'Monitor adoption metrics',
          'Provide individual support',
          'Adjust timeline based on feedback'
        ],
        deliverables: [
          'Adoption metrics dashboard',
          'Extended support documentation',
          'Migration success stories'
        ],
        exitCriteria: [
          '80% adoption rate achieved',
          'No critical issues reported',
          'Community feedback positive'
        ],
      });
    }

    return phases;
  }

  private createCommunicationPlan(breakingChanges: BreakingChange[], phases: MigrationPhase[]): CommunicationPlan {
    const now = new Date();
    const oneWeekFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
    const twoWeeksFromNow = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
    const threeMonthsFromNow = new Date(now.getTime() + 90 * 24 * 60 * 60 * 1000);

    return {
      announcementDate: oneWeekFromNow,
      deprecationWarnings: twoWeeksFromNow,
      migrationGuidesRelease: twoWeeksFromNow,
      supportCutoff: threeMonthsFromNow,
      channels: ['GitHub Issues', 'Documentation', 'Community Discord', 'Blog Posts'],
      targetAudiences: ['Core Contributors', 'Active Users', 'Enterprise Customers', 'Community'],
    };
  }

  // Weitere private Hilfsmethoden...
  private isBreakingChange(change: any): boolean {
    // Logik zur Bestimmung ob Change breaking ist
    return change.type === 'breaking' || change.compatibility === 'breaking';
  }

  private assessChangeImpact(change: any): 'low' | 'medium' | 'high' | 'critical' {
    // Impact-Bewertung basierend auf Change-Eigenschaften
    if (change.affectsCore || change.mandatory) return 'critical';
    if (change.affectsAPI || change.affectsConfig) return 'high';
    if (change.affectsHelpers) return 'medium';
    return 'low';
  }

  private identifyAffectedComponents(change: any): string[] {
    // Identifiziere welche Komponenten betroffen sind
    return change.components || [];
  }

  private canAutomate(change: any): boolean {
    // Bestimme ob Migration automatisiert werden kann
    return change.automation === 'possible' || change.pattern === 'simple';
  }

  private generateChangeId(change: any): string {
    return `bc_${Date.now()}_${change.title.replace(/\s+/g, '_').toLowerCase()}`;
  }

  private categorizeChange(change: any): 'api' | 'config' | 'dependency' | 'architecture' | 'data' {
    if (change.affects?.includes('api')) return 'api';
    if (change.affects?.includes('config')) return 'config';
    if (change.affects?.includes('deps')) return 'dependency';
    if (change.affects?.includes('arch')) return 'architecture';
    return 'data';
  }

  private generateMigrationPath(change: any): string {
    return `See migration guide: ${change.migrationGuideUrl || 'TBD'}`;
  }

  private estimateMigrationTime(change: any): string {
    const baseTime = change.complexity === 'high' ? 8 : change.complexity === 'medium' ? 4 : 2;
    return `${baseTime} hours`;
  }

  private generateExamples(change: any): any {
    if (change.examples) {
      return {
        before: change.examples.before,
        after: change.examples.after,
        explanation: change.examples.explanation,
      };
    }
    return undefined;
  }

  private estimateAffectedUsers(breakingChanges: BreakingChange[]): number {
    // Schätze basierend auf Component-Popularität
    return breakingChanges.reduce((sum, change) => {
      return sum + (change.affectedComponents.length * 100); // Vereinfachte Schätzung
    }, 0);
  }

  private parseEffortHours(effortString: string): number {
    const match = effortString.match(/(\d+)\s*hours?/);
    return match ? parseInt(match[1], 10) : 0;
  }

  // Weitere Implementierungen für spezifische Funktionen...
  private async addCodeDeprecationWarnings(change: BreakingChange): Promise<string[]> {
    return [`Add deprecation warning for ${change.title}`];
  }

  private async updateDeprecationDocumentation(change: BreakingChange): Promise<string[]> {
    return [`Update docs for ${change.title}`];
  }

  private async createAutomationTool(change: BreakingChange): Promise<AutomationTool> {
    return {
      name: `Migration tool for ${change.title}`,
      script: 'migration-script.js',
      description: change.migrationPath,
    };
  }

  private async createMigrationGuide(change: BreakingChange): Promise<MigrationGuide> {
    return {
      title: `Migration guide: ${change.title}`,
      content: change.migrationPath,
      examples: change.examples,
    };
  }

  private async createTestSuite(change: BreakingChange): Promise<TestSuite> {
    return {
      name: `Test suite for ${change.title}`,
      tests: ['before-migration.test.js', 'after-migration.test.js'],
    };
  }

  // Weitere Implementierungen für Execution-Methoden...
  private async sendMigrationAnnouncement(strategy: MigrationStrategy): Promise<void> {
    this.logger.log(`Sending migration announcement for ${strategy.name}`);
  }

  private async executePhaseActivities(phase: MigrationPhase): Promise<void> {
    for (const activity of phase.activities) {
      this.logger.log(`Executing activity: ${activity}`);
    }
  }

  private async validatePhaseCompletion(phase: MigrationPhase): Promise<boolean> {
    // Prüfe ob alle Exit-Kriterien erfüllt sind
    return true;
  }

  private async measureAdoptionRate(strategy: MigrationStrategy): Promise<number> {
    // Messe wie viele Benutzer die Migration durchgeführt haben
    return 85; // Beispiel: 85% Adoption
  }

  private async collectPhaseFeedback(phase: MigrationPhase): Promise<void> {
    this.logger.log(`Collecting feedback for phase ${phase.phase}`);
  }

  private async generateFeedbackSummary(strategy: MigrationStrategy): Promise<string> {
    return 'Overall positive feedback with some concerns about timeline';
  }

  private assessStrategyRisk(breakingChanges: BreakingChange[], phases: MigrationPhase[]): 'low' | 'medium' | 'high' {
    const criticalChanges = breakingChanges.filter(c => c.impact === 'critical').length;
    if (criticalChanges > 2) return 'high';
    if (criticalChanges > 0) return 'medium';
    return 'low';
  }

  private calculateOverallTimeline(phases: MigrationPhase[]): string {
    const totalWeeks = phases.reduce((sum, phase) => {
      const weeks = parseInt(phase.duration.split(' ')[0], 10);
      return sum + weeks;
    }, 0);
    return `${totalWeeks} weeks`;
  }

  private determineSupportLevel(breakingChanges: BreakingChange[]): 'basic' | 'extended' | 'premium' {
    const criticalChanges = breakingChanges.filter(c => c.impact === 'critical').length;
    if (criticalChanges > 3) return 'premium';
    if (criticalChanges > 0) return 'extended';
    return 'basic';
  }
}

// Hilfsdatenstrukturen
interface AutomationTool {
  name: string;
  script: string;
  description: string;
}

interface MigrationGuide {
  title: string;
  content: string;
  examples?: any;
}

interface TestSuite {
  name: string;
  tests: string[];
}

Breaking Changes Management ist mehr als nur technische Implementierung - es ist auch Change Management und Kommunikation. Die beste technische Lösung hilft nichts, wenn die Benutzer nicht verstehen, warum sie migrieren müssen oder wie sie es tun sollen. Erfolgreiche Breaking Changes zeichnen sich durch klare Kommunikation, ausreichende Vorlaufzeit und umfassende Unterstützung aus.

Denken Sie daran: Breaking Changes sind Gelegenheiten zur Verbesserung, aber nur wenn sie richtig gehandhabt werden. Eine schlecht verwaltete Breaking Change kann das Vertrauen der Community beschädigen und die Adoption Ihres Frameworks verlangsamen. Eine gut verwaltete Breaking Change hingegen kann zu einem saubereren, besseren Framework führen und das Vertrauen in Ihre Fähigkeit stärken, schwierige Entscheidungen zu treffen.

32.5 Zero-Downtime Deployments

Zero-Downtime Deployments sind wie das Wechseln der Reifen eines fahrenden Autos - es scheint unmöglich, aber mit der richtigen Technik und Vorbereitung ist es nicht nur möglich, sondern auch sicher und routinemäßig durchführbar. In einer Welt, in der Benutzer 24/7-Verfügbarkeit erwarten und jede Minute Ausfallzeit Geld kostet, sind Zero-Downtime Deployments nicht mehr nur ein Nice-to-have, sondern ein Muss.

Das Konzept ist einfach: Sie möchten neue Versionen Ihrer Anwendung deployen, ohne dass Benutzer eine Unterbrechung des Service bemerken. Die Umsetzung jedoch erfordert sorgfältige Planung, robuste Infrastruktur und bewährte Deployment-Strategien. Es geht nicht nur darum, die Anwendung am Laufen zu halten, sondern auch sicherzustellen, dass sie während des gesamten Deployment-Prozesses korrekt funktioniert.

Die größte Herausforderung bei Zero-Downtime Deployments liegt in der Koordination. Sie müssen Anwendungscode, Datenbank-Schemas, Konfigurationen und möglicherweise externe Services synchronisieren. Ein falsch getimtes Update oder eine inkompatible Zwischenversion kann zu subtilen Fehlern führen, die schwer zu diagnostizieren sind.

// zero-downtime/src/services/deployment-orchestrator.service.ts
// Ein umfassendes System für Zero-Downtime-Deployments
import { Injectable, Logger } from '@nestjs/common';

interface DeploymentPlan {
  deploymentId: string;
  strategy: 'blue-green' | 'rolling' | 'canary' | 'feature-toggle';
  version: {
    current: string;
    target: string;
  };
  phases: DeploymentPhase[];
  rollbackStrategy: RollbackStrategy;
  healthChecks: HealthCheck[];
  migrationSteps: MigrationStep[];
  monitoringConfig: MonitoringConfig;
  estimatedDuration: string;
  riskAssessment: RiskAssessment;
}

interface DeploymentPhase {
  phase: number;
  name: string;
  description: string;
  duration: string;
  actions: DeploymentAction[];
  prerequisites: string[];
  successCriteria: string[];
  rollbackTriggers: string[];
}

interface DeploymentAction {
  actionId: string;
  type: 'deploy' | 'migrate' | 'validate' | 'monitor' | 'switch-traffic';
  description: string;
  targetEnvironment: string;
  parameters: Record<string, any>;
  timeout: number;
  retryCount: number;
}

interface RollbackStrategy {
  type: 'automatic' | 'manual' | 'hybrid';
  triggers: string[];
  maxRollbackTime: number;
  preserveData: boolean;
  notificationChannels: string[];
}

interface HealthCheck {
  name: string;
  endpoint: string;
  expectedStatus: number;
  timeout: number;
  retryCount: number;
  failureThreshold: number;
  dependencies: string[];
}

interface MonitoringConfig {
  metrics: string[];
  alertThresholds: Record<string, number>;
  dashboardUrl: string;
  slackChannel: string;
  escalationPolicy: string;
}

interface RiskAssessment {
  level: 'low' | 'medium' | 'high' | 'critical';
  factors: string[];
  mitigations: string[];
  approvalRequired: boolean;
}

@Injectable()
export class DeploymentOrchestratorService {
  private readonly logger = new Logger(DeploymentOrchestratorService.name);
  
  // Aktive Deployments tracken
  private readonly activeDeployments = new Map<string, DeploymentExecution>();

  /**
   * Plant ein Zero-Downtime-Deployment basierend auf Änderungsanalyse
   * Die Planung ist entscheidend für den Erfolg des Deployments
   */
  async planZeroDowntimeDeployment(
    currentVersion: string,
    targetVersion: string,
    changeAnalysis: any
  ): Promise<DeploymentPlan> {
    this.logger.log(`Planning zero-downtime deployment from ${currentVersion} to ${targetVersion}`);

    // Analysiere Änderungen und bewerte Risiko
    const riskAssessment = this.assessDeploymentRisk(changeAnalysis);
    
    // Wähle optimale Deployment-Strategie
    const strategy = this.selectDeploymentStrategy(riskAssessment, changeAnalysis);
    
    // Erstelle Migration-Steps
    const migrationSteps = await this.generateMigrationSteps(changeAnalysis);
    
    // Konfiguriere Health Checks
    const healthChecks = this.generateHealthChecks(changeAnalysis);
    
    // Erstelle Deployment-Phasen
    const phases = this.createDeploymentPhases(strategy, migrationSteps, healthChecks);
    
    // Konfiguriere Rollback-Strategie
    const rollbackStrategy = this.createRollbackStrategy(riskAssessment);

    const deploymentPlan: DeploymentPlan = {
      deploymentId: this.generateDeploymentId(),
      strategy,
      version: { current: currentVersion, target: targetVersion },
      phases,
      rollbackStrategy,
      healthChecks,
      migrationSteps,
      monitoringConfig: this.createMonitoringConfig(riskAssessment),
      estimatedDuration: this.calculateDeploymentDuration(phases),
      riskAssessment,
    };

    this.logger.log(`Deployment plan created: ${strategy} strategy, ${phases.length} phases, ${riskAssessment.level} risk`);
    return deploymentPlan;
  }

  /**
   * Führt ein Zero-Downtime-Deployment aus
   * Orchestriert alle Phasen und überwacht kontinuierlich den Zustand
   */
  async executeZeroDowntimeDeployment(plan: DeploymentPlan): Promise<{
    success: boolean;
    completedPhases: number;
    totalPhases: number;
    executionTime: number;
    issues: string[];
    rollbackExecuted: boolean;
    metrics: DeploymentMetrics;
  }> {
    this.logger.log(`Starting zero-downtime deployment: ${plan.deploymentId}`);
    
    const execution: DeploymentExecution = {
      deploymentId: plan.deploymentId,
      startTime: new Date(),
      currentPhase: 0,
      status: 'running',
      metrics: this.initializeMetrics(),
    };
    
    this.activeDeployments.set(plan.deploymentId, execution);
    
    try {
      // Pre-deployment Validierung
      await this.validatePreDeployment(plan);
      
      // Starte kontinuierliches Monitoring
      const monitoringHandle = this.startContinuousMonitoring(plan, execution);
      
      // Führe jede Phase aus
      for (let i = 0; i < plan.phases.length; i++) {
        const phase = plan.phases[i];
        execution.currentPhase = i + 1;
        
        this.logger.log(`Executing phase ${phase.phase}: ${phase.name}`);
        
        try {
          // Prüfe Prerequisites
          await this.validatePhasePrerequisites(phase);
          
          // Führe Phase-Actions aus
          await this.executePhaseActions(phase, execution);
          
          // Validiere Success-Kriterien
          const phaseSuccess = await this.validatePhaseSuccess(phase, execution);
          
          if (!phaseSuccess) {
            throw new Error(`Phase ${phase.phase} success criteria not met`);
          }
          
          // Aktualisiere Metriken
          execution.metrics.completedPhases++;
          
        } catch (phaseError) {
          this.logger.error(`Phase ${phase.phase} failed: ${phaseError.message}`);
          
          // Prüfe Rollback-Triggers
          if (this.shouldTriggerRollback(phase, phaseError, execution)) {
            this.logger.warn('Triggering automatic rollback');
            await this.executeRollback(plan, execution);
            execution.rollbackExecuted = true;
            break;
          } else {
            // Sammle Error aber fahre fort (abhängig von Phase-Konfiguration)
            execution.metrics.warnings.push(`Phase ${phase.phase}: ${phaseError.message}`);
          }
        }
      }
      
      // Stoppe Monitoring
      clearInterval(monitoringHandle);
      
      // Post-deployment Validierung
      await this.validatePostDeployment(plan, execution);
      
      execution.endTime = new Date();
      execution.status = execution.rollbackExecuted ? 'rolled-back' : 'completed';
      
      const result = {
        success: !execution.rollbackExecuted && execution.metrics.errors.length === 0,
        completedPhases: execution.metrics.completedPhases,
        totalPhases: plan.phases.length,
        executionTime: execution.endTime.getTime() - execution.startTime.getTime(),
        issues: [...execution.metrics.errors, ...execution.metrics.warnings],
        rollbackExecuted: execution.rollbackExecuted || false,
        metrics: execution.metrics,
      };
      
      this.logger.log(`Deployment ${execution.status}: ${plan.deploymentId}`);
      return result;
      
    } catch (error) {
      execution.status = 'failed';
      execution.endTime = new Date();
      
      this.logger.error(`Deployment failed: ${error.message}`);
      
      // Emergency rollback
      try {
        await this.executeEmergencyRollback(plan, execution);
        execution.rollbackExecuted = true;
      } catch (rollbackError) {
        this.logger.error(`Emergency rollback failed: ${rollbackError.message}`);
        execution.metrics.errors.push(`Rollback failed: ${rollbackError.message}`);
      }
      
      return {
        success: false,
        completedPhases: execution.metrics.completedPhases,
        totalPhases: plan.phases.length,
        executionTime: execution.endTime.getTime() - execution.startTime.getTime(),
        issues: [...execution.metrics.errors, ...execution.metrics.warnings, error.message],
        rollbackExecuted: execution.rollbackExecuted || false,
        metrics: execution.metrics,
      };
      
    } finally {
      this.activeDeployments.delete(plan.deploymentId);
    }
  }

  /**
   * Implementiert Blue-Green Deployment-Strategie
   * Minimiert Risiko durch parallele Umgebungen
   */
  async executeBlueGreenDeployment(plan: DeploymentPlan): Promise<void> {
    this.logger.log('Executing Blue-Green deployment strategy');
    
    // Phase 1: Bereite Green Environment vor
    await this.prepareGreenEnvironment(plan);
    
    // Phase 2: Deploye neue Version in Green
    await this.deployToGreenEnvironment(plan);
    
    // Phase 3: Validiere Green Environment
    const greenValidation = await this.validateGreenEnvironment(plan);
    if (!greenValidation.isValid) {
      throw new Error(`Green environment validation failed: ${greenValidation.errors.join(', ')}`);
    }
    
    // Phase 4: Warm-up Green Environment
    await this.warmUpGreenEnvironment(plan);
    
    // Phase 5: Schalte Traffic schrittweise um
    await this.switchTrafficToGreen(plan);
    
    // Phase 6: Überwache nach Traffic-Switch
    await this.monitorPostSwitch(plan);
    
    // Phase 7: Cleanup Blue Environment (nach Bestätigung)
    await this.cleanupBlueEnvironment(plan);
  }

  /**
   * Implementiert Rolling Deployment-Strategie
   * Aktualisiert Instanzen schrittweise
   */
  async executeRollingDeployment(plan: DeploymentPlan): Promise<void> {
    this.logger.log('Executing Rolling deployment strategy');
    
    const instances = await this.getApplicationInstances();
    const batchSize = this.calculateRollingBatchSize(instances.length);
    
    for (let i = 0; i < instances.length; i += batchSize) {
      const batch = instances.slice(i, i + batchSize);
      
      this.logger.log(`Updating batch ${Math.floor(i / batchSize) + 1}: ${batch.length} instances`);
      
      // Entferne Instanzen aus Load Balancer
      await this.removeInstancesFromLoadBalancer(batch);
      
      // Warte auf Drain
      await this.waitForConnectionDrain(batch);
      
      // Update Instanzen
      await this.updateInstances(batch, plan);
      
      // Validiere Updated Instanzen
      const validation = await this.validateUpdatedInstances(batch, plan);
      if (!validation.isValid) {
        // Rollback dieser Batch
        await this.rollbackInstanceBatch(batch);
        throw new Error(`Instance batch validation failed: ${validation.errors.join(', ')}`);
      }
      
      // Füge Instanzen zurück zu Load Balancer hinzu
      await this.addInstancesToLoadBalancer(batch);
      
      // Überwache für kurze Zeit vor nächster Batch
      await this.monitorBatchHealth(batch, 30000); // 30 Sekunden
    }
  }

  /**
   * Implementiert Canary Deployment-Strategie
   * Testet neue Version mit kleinem Traffic-Anteil
   */
  async executeCanaryDeployment(plan: DeploymentPlan): Promise<void> {
    this.logger.log('Executing Canary deployment strategy');
    
    // Phase 1: Deploye Canary-Instanz
    const canaryInstance = await this.deployCanaryInstance(plan);
    
    // Phase 2: Route kleinen Traffic-Anteil zu Canary (z.B. 5%)
    await this.routeTrafficToCanary(canaryInstance, 5);
    
    // Phase 3: Überwache Canary-Metriken
    const canaryMetrics = await this.monitorCanaryMetrics(canaryInstance, 300000); // 5 Minuten
    
    if (!this.isCanaryHealthy(canaryMetrics)) {
      await this.removeCanaryInstance(canaryInstance);
      throw new Error('Canary instance failed health checks');
    }
    
    // Phase 4: Erhöhe Traffic schrittweise (5% -> 25% -> 50% -> 100%)
    const trafficSteps = [25, 50, 100];
    
    for (const trafficPercentage of trafficSteps) {
      await this.routeTrafficToCanary(canaryInstance, trafficPercentage);
      
      const stepMetrics = await this.monitorCanaryMetrics(canaryInstance, 180000); // 3 Minuten
      
      if (!this.isCanaryHealthy(stepMetrics)) {
        await this.rollbackCanaryTraffic();
        throw new Error(`Canary failed at ${trafficPercentage}% traffic`);
      }
    }
    
    // Phase 5: Vervollständige Deployment zu allen Instanzen
    await this.promoteCanaryToProduction(plan, canaryInstance);
  }

  private assessDeploymentRisk(changeAnalysis: any): RiskAssessment {
    let riskLevel: 'low' | 'medium' | 'high' | 'critical' = 'low';
    const factors: string[] = [];
    const mitigations: string[] = [];
    
    // Bewerte verschiedene Risiko-Faktoren
    if (changeAnalysis.databaseChanges?.breaking) {
      riskLevel = 'high';
      factors.push('Breaking database schema changes');
      mitigations.push('Use database migration with backward compatibility');
    }
    
    if (changeAnalysis.apiChanges?.breaking) {
      riskLevel = 'high';
      factors.push('Breaking API changes');
      mitigations.push('Implement API versioning and gradual migration');
    }
    
    if (changeAnalysis.configChanges?.required) {
      if (riskLevel === 'low') riskLevel = 'medium';
      factors.push('Configuration changes required');
      mitigations.push('Validate configuration before deployment');
    }
    
    if (changeAnalysis.dependencies?.major) {
      if (riskLevel !== 'high') riskLevel = 'medium';
      factors.push('Major dependency updates');
      mitigations.push('Extensive testing and gradual rollout');
    }
    
    return {
      level: riskLevel,
      factors,
      mitigations,
      approvalRequired: riskLevel === 'high' || riskLevel === 'critical',
    };
  }

  private selectDeploymentStrategy(
    riskAssessment: RiskAssessment,
    changeAnalysis: any
  ): 'blue-green' | 'rolling' | 'canary' | 'feature-toggle' {
    // Wähle Strategie basierend auf Risiko und Änderungstyp
    if (riskAssessment.level === 'critical') {
      return 'blue-green'; // Safest for critical changes
    }
    
    if (riskAssessment.level === 'high') {
      return 'canary'; // Gradual validation for high-risk changes
    }
    
    if (changeAnalysis.databaseChanges?.present) {
      return 'blue-green'; // Best for database changes
    }
    
    if (changeAnalysis.size === 'small') {
      return 'rolling'; // Efficient for small changes
    }
    
    return 'canary'; // Default safe choice
  }

  private async generateMigrationSteps(changeAnalysis: any): Promise<MigrationStep[]> {
    const steps: MigrationStep[] = [];
    
    if (changeAnalysis.databaseChanges?.present) {
      steps.push({
        stepNumber: 1,
        description: 'Execute database schema migration',
        sql: changeAnalysis.databaseChanges.migrationSql,
        expectedAffectedRows: changeAnalysis.databaseChanges.estimatedAffectedRows,
        rollbackSql: changeAnalysis.databaseChanges.rollbackSql,
        validationQuery: changeAnalysis.databaseChanges.validationQuery,
        isReversible: true,
        executionTime: 'medium',
      });
    }
    
    if (changeAnalysis.configChanges?.required) {
      steps.push({
        stepNumber: steps.length + 1,
        description: 'Update application configuration',
        sql: '', // Not applicable for config changes
        isReversible: true,
        executionTime: 'fast',
      });
    }
    
    return steps;
  }

  private generateHealthChecks(changeAnalysis: any): HealthCheck[] {
    const healthChecks: HealthCheck[] = [
      // Standard health checks
      {
        name: 'Application Health',
        endpoint: '/health',
        expectedStatus: 200,
        timeout: 5000,
        retryCount: 3,
        failureThreshold: 2,
        dependencies: [],
      },
      {
        name: 'Database Connectivity',
        endpoint: '/health/database',
        expectedStatus: 200,
        timeout: 10000,
        retryCount: 3,
        failureThreshold: 1,
        dependencies: ['database'],
      },
    ];
    
    // Zusätzliche Health Checks basierend auf Änderungen
    if (changeAnalysis.externalIntegrations?.modified) {
      healthChecks.push({
        name: 'External Integrations',
        endpoint: '/health/integrations',
        expectedStatus: 200,
        timeout: 15000,
        retryCount: 2,
        failureThreshold: 1,
        dependencies: ['external-apis'],
      });
    }
    
    return healthChecks;
  }

  private createDeploymentPhases(
    strategy: string,
    migrationSteps: MigrationStep[],
    healthChecks: HealthCheck[]
  ): DeploymentPhase[] {
    const phases: DeploymentPhase[] = [];
    
    // Pre-deployment Phase
    phases.push({
      phase: 1,
      name: 'Pre-deployment Validation',
      description: 'Validate prerequisites and prepare environment',
      duration: '5 minutes',
      actions: [
        {
          actionId: 'validate-prerequisites',
          type: 'validate',
          description: 'Check all prerequisites',
          targetEnvironment: 'current',
          parameters: {},
          timeout: 60000,
          retryCount: 0,
        },
      ],
      prerequisites: [],
      successCriteria: ['All health checks pass', 'Prerequisites validated'],
      rollbackTriggers: ['Health check failures', 'Prerequisite validation failures'],
    });
    
    // Migration Phase (if needed)
    if (migrationSteps.length > 0) {
      phases.push({
        phase: 2,
        name: 'Database Migration',
        description: 'Execute database schema and data migrations',
        duration: '10 minutes',
        actions: migrationSteps.map((step, index) => ({
          actionId: `migration-step-${index + 1}`,
          type: 'migrate',
          description: step.description,
          targetEnvironment: 'database',
          parameters: { sql: step.sql, rollbackSql: step.rollbackSql },
          timeout: 600000, // 10 minutes
          retryCount: 0, // No retries for migrations
        })),
        prerequisites: ['Phase 1 completed'],
        successCriteria: ['All migration steps executed', 'Database validation passed'],
        rollbackTriggers: ['Migration step failure', 'Database validation failure'],
      });
    }
    
    // Deployment Phase
    phases.push({
      phase: phases.length + 1,
      name: 'Application Deployment',
      description: `Deploy using ${strategy} strategy`,
      duration: strategy === 'blue-green' ? '15 minutes' : '20 minutes',
      actions: [
        {
          actionId: 'deploy-application',
          type: 'deploy',
          description: `Execute ${strategy} deployment`,
          targetEnvironment: 'application',
          parameters: { strategy },
          timeout: 1200000, // 20 minutes
          retryCount: 1,
        },
      ],
      prerequisites: ['Previous phases completed'],
      successCriteria: ['Deployment completed', 'All health checks pass'],
      rollbackTriggers: ['Deployment failure', 'Health check failures', 'Performance degradation'],
    });
    
    // Traffic Switch Phase (for strategies that need it)
    if (strategy === 'blue-green' || strategy === 'canary') {
      phases.push({
        phase: phases.length + 1,
        name: 'Traffic Switch',
        description: 'Gradually switch traffic to new version',
        duration: '10 minutes',
        actions: [
          {
            actionId: 'switch-traffic',
            type: 'switch-traffic',
            description: 'Route traffic to new version',
            targetEnvironment: 'load-balancer',
            parameters: { strategy },
            timeout: 600000, // 10 minutes
            retryCount: 0,
          },
        ],
        prerequisites: ['Application deployment completed'],
        successCriteria: ['Traffic successfully switched', 'No error rate increase'],
        rollbackTriggers: ['High error rate', 'Performance issues', 'Health check failures'],
      });
    }
    
    // Post-deployment Phase
    phases.push({
      phase: phases.length + 1,
      name: 'Post-deployment Validation',
      description: 'Validate deployment success and monitor metrics',
      duration: '15 minutes',
      actions: [
        {
          actionId: 'validate-deployment',
          type: 'validate',
          description: 'Run comprehensive validation tests',
          targetEnvironment: 'application',
          parameters: { healthChecks },
          timeout: 900000, // 15 minutes
          retryCount: 2,
        },
        {
          actionId: 'monitor-metrics',
          type: 'monitor',
          description: 'Monitor key metrics for anomalies',
          targetEnvironment: 'monitoring',
          parameters: { duration: 900000 },
          timeout: 900000,
          retryCount: 0,
        },
      ],
      prerequisites: ['Deployment completed'],
      successCriteria: ['All validation tests pass', 'Metrics within normal ranges'],
      rollbackTriggers: ['Validation test failures', 'Metric anomalies'],
    });
    
    return phases;
  }

  // Weitere private Methoden für spezifische Deployment-Funktionen...
  private generateDeploymentId(): string {
    return `deploy_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private createRollbackStrategy(riskAssessment: RiskAssessment): RollbackStrategy {
    return {
      type: riskAssessment.level === 'low' ? 'automatic' : 'hybrid',
      triggers: [
        'Health check failures',
        'High error rate',
        'Performance degradation',
        'Manual trigger'
      ],
      maxRollbackTime: 300000, // 5 minutes
      preserveData: true,
      notificationChannels: ['slack', 'email', 'pagerduty'],
    };
  }

  private createMonitoringConfig(riskAssessment: RiskAssessment): MonitoringConfig {
    return {
      metrics: [
        'response_time',
        'error_rate',
        'throughput',
        'cpu_usage',
        'memory_usage',
        'database_connections'
      ],
      alertThresholds: {
        response_time: riskAssessment.level === 'high' ? 500 : 1000,
        error_rate: riskAssessment.level === 'high' ? 1 : 5,
        cpu_usage: 80,
        memory_usage: 85,
      },
      dashboardUrl: 'https://monitoring.example.com/deployment-dashboard',
      slackChannel: '#deployments',
      escalationPolicy: 'deployment-escalation',
    };
  }

  private calculateDeploymentDuration(phases: DeploymentPhase[]): string {
    const totalMinutes = phases.reduce((sum, phase) => {
      const minutes = parseInt(phase.duration.split(' ')[0], 10);
      return sum + minutes;
    }, 0);
    return `${totalMinutes} minutes`;
  }

  private initializeMetrics(): DeploymentMetrics {
    return {
      completedPhases: 0,
      errors: [],
      warnings: [],
      startTime: new Date(),
      healthCheckResults: [],
      performanceMetrics: {},
    };
  }

  // Weitere Implementierungen für Execution-Methoden...
  private async validatePreDeployment(plan: DeploymentPlan): Promise<void> {
    this.logger.log('Validating pre-deployment conditions');
    
    // Validiere Health Checks
    for (const healthCheck of plan.healthChecks) {
      const result = await this.executeHealthCheck(healthCheck);
      if (!result.passed) {
        throw new Error(`Pre-deployment health check failed: ${healthCheck.name}`);
      }
    }
  }

  private startContinuousMonitoring(plan: DeploymentPlan, execution: DeploymentExecution): NodeJS.Timeout {
    return setInterval(async () => {
      try {
        const metrics = await this.collectCurrentMetrics(plan);
        execution.metrics.performanceMetrics = { ...execution.metrics.performanceMetrics, ...metrics };
        
        // Prüfe Alert-Thresholds
        const alerts = this.checkAlertThresholds(metrics, plan.monitoringConfig);
        if (alerts.length > 0) {
          execution.metrics.warnings.push(...alerts);
        }
        
      } catch (error) {
        this.logger.error('Monitoring error:', error);
      }
    }, 30000); // Alle 30 Sekunden
  }

  // Weitere Implementierungen...
}

// Zusätzliche Interfaces für Deployment-Management
interface DeploymentExecution {
  deploymentId: string;
  startTime: Date;
  endTime?: Date;
  currentPhase: number;
  status: 'running' | 'completed' | 'failed' | 'rolled-back';
  rollbackExecuted?: boolean;
  metrics: DeploymentMetrics;
}

interface DeploymentMetrics {
  completedPhases: number;
  errors: string[];
  warnings: string[];
  startTime: Date;
  healthCheckResults: any[];
  performanceMetrics: Record<string, any>;
}

Zero-Downtime Deployments sind wie eine Choreographie - jeder Schritt muss perfekt getimed und koordiniert sein. Die Kombination aus bewährten Deployment-Strategien, umfassendem Monitoring und robusten Rollback-Mechanismen ermöglicht es, selbst komplexe Anwendungen ohne Serviceunterbrechung zu aktualisieren.

Der Schlüssel liegt darin, die richtige Strategie für Ihre spezifische Situation zu wählen. Blue-Green Deployments bieten maximale Sicherheit, erfordern aber doppelte Ressourcen. Rolling Deployments sind ressourcenschonend, aber komplexer zu koordinieren. Canary Deployments ermöglichen schrittweise Validierung, benötigen aber sophisticated Monitoring.

Denken Sie daran: Zero-Downtime ist nicht nur eine technische Anforderung, sondern auch ein Geschäftsvorteil. Jede vermiedene Ausfallzeit bedeutet zufriedenere Benutzer, weniger Support-Anfragen und letztendlich bessere Geschäftsergebnisse. Die Investition in robuste Deployment-Strategien zahlt sich durch reduzierte Risiken und erhöhte Agilität aus.

Diese umfassende Betrachtung von Migration und Upgrade-Strategien zeigt, dass erfolgreiche Transformationen mehr erfordern als nur technische Implementierung. Sie benötigen sorgfältige Planung, systematische Herangehensweise und die Fähigkeit, mit Veränderungen umzugehen, ohne bestehende Systeme zu gefährden.

Von der Migration von Express.js zu NestJS über Version-Upgrades bis hin zu komplexen Datenbank-Schema-Migrationen und Zero-Downtime Deployments - jede Art von Migration hat ihre eigenen Herausforderungen und bewährten Praktiken. Der gemeinsame Nenner ist die Notwendigkeit einer durchdachten Strategie, umfassender Tests und der Bereitschaft, bei Problemen schnell zu reagieren.

Wie ein erfahrener Architekt, der ein historisches Gebäude renoviert, müssen Sie Respekt vor dem Bestehenden zeigen, während Sie gleichzeitig die Zukunft im Blick behalten. Jede Migration ist eine Gelegenheit, nicht nur zu aktualisieren, sondern auch zu verbessern und zu optimieren. Mit den richtigen Strategien und Werkzeugen können Sie diese Gelegenheiten nutzen, um stärkere, bessere und zukunftssicherere Systeme zu schaffen.

Die wirkliche Stärke zeigt sich bei Refactoring-Operationen. Wenn Sie eine Eigenschaft zu einem User-Model hinzufügen, wird TypeScript Sie an allen Stellen warnen, wo diese neue Eigenschaft verwendet werden könnte. Dies macht großangelegte Änderungen sicherer und schneller, da Sie Vertrauen haben, dass der Compiler alle betroffenen Stellen identifiziert hat.

32.6 Monorepo-Strategien mit Nx

Monorepo-Strategien mit Nx sind wie das Entwerfen einer modernen Smart City - alle Komponenten sind miteinander verbunden, teilen Infrastruktur und Ressourcen, können aber dennoch unabhängig funktionieren und entwickelt werden. Nx ist mehr als nur ein Build-Tool; es ist ein vollständiges Entwicklungsökosystem, das speziell für die Herausforderungen moderner Full-Stack-Anwendungen entwickelt wurde.

Der traditionelle Ansatz mit separaten Repositories für Frontend und Backend führt oft zu Fragmentierung und Inkonsistenzen. Code-Sharing wird schwierig, gemeinsame Standards sind schwer durchzusetzen, und die Koordination zwischen Teams wird zu einem organisatorischen Alptraum. Nx löst diese Probleme durch ein intelligentes Monorepo-System, das sowohl Isolation als auch Integration ermöglicht.

// tools/nx-workspace/src/generators/full-stack-feature.generator.ts
// Generator für komplette Full-Stack-Features mit Nx
import { Tree, formatFiles, generateFiles, joinPathFragments } from '@nrwl/devkit';
import { libraryGenerator } from '@nrwl/workspace/generators';

interface FeatureGeneratorSchema {
  name: string;
  domain: string;
  includeApi: boolean;
  includeUi: boolean;
  includeE2e: boolean;
  styleLibrary: 'angular-material' | 'tailwind' | 'bootstrap';
}

export default async function (tree: Tree, options: FeatureGeneratorSchema) {
  const normalizedOptions = normalizeOptions(options);
  
  // Generiere Shared Domain Library
  await generateDomainLibrary(tree, normalizedOptions);
  
  // Generiere Frontend Feature Library
  if (options.includeUi) {
    await generateFrontendFeature(tree, normalizedOptions);
  }
  
  // Generiere Backend API Module
  if (options.includeApi) {
    await generateBackendFeature(tree, normalizedOptions);
  }
  
  // Generiere E2E Tests
  if (options.includeE2e) {
    await generateE2eTests(tree, normalizedOptions);
  }
  
  // Update Workspace-Konfiguration
  await updateWorkspaceConfig(tree, normalizedOptions);
  
  await formatFiles(tree);
}

async function generateDomainLibrary(tree: Tree, options: any) {
  const domainLibPath = `libs/${options.domain}/${options.name}-domain`;
  
  // Generiere Domain Models
  const modelsTs = `
export interface ${options.pascalName} {
  id: string;
  createdAt: Date;
  updatedAt: Date;
  // TODO: Add domain-specific properties
}

export interface Create${options.pascalName}Dto {
  // TODO: Add creation properties
}

export interface Update${options.pascalName}Dto {
  // TODO: Add update properties
}

export enum ${options.pascalName}Status {
  ACTIVE = 'active',
  INACTIVE = 'inactive',
  PENDING = 'pending',
}

// Domain Events
export interface ${options.pascalName}CreatedEvent {
  type: '${options.name}-created';
  payload: {
    ${options.camelName}: ${options.pascalName};
    userId: string;
    timestamp: Date;
  };
}

export interface ${options.pascalName}UpdatedEvent {
  type: '${options.name}-updated';
  payload: {
    ${options.camelName}: ${options.pascalName};
    previousValues: Partial<${options.pascalName}>;
    userId: string;
    timestamp: Date;
  };
}

export type ${options.pascalName}DomainEvent = 
  | ${options.pascalName}CreatedEvent 
  | ${options.pascalName}UpdatedEvent;
`;
  
  tree.write(`${domainLibPath}/src/lib/models.ts`, modelsTs);
  
  // Generiere Business Rules
  const businessRulesTs = `
import { ${options.pascalName}, Create${options.pascalName}Dto, Update${options.pascalName}Dto } from './models';

export class ${options.pascalName}BusinessRules {
  static validateCreation(dto: Create${options.pascalName}Dto): string[] {
    const errors: string[] = [];
    
    // TODO: Implement business validation rules
    // Example:
    // if (!dto.name || dto.name.length < 2) {
    //   errors.push('Name must be at least 2 characters long');
    // }
    
    return errors;
  }

  static validateUpdate(
    existing: ${options.pascalName}, 
    dto: Update${options.pascalName}Dto
  ): string[] {
    const errors: string[] = [];
    
    // TODO: Implement update validation rules
    
    return errors;
  }

  static canDelete(entity: ${options.pascalName}): boolean {
    // TODO: Implement deletion business rules
    return true;
  }

  static calculateStatus(entity: ${options.pascalName}): ${options.pascalName}Status {
    // TODO: Implement status calculation logic
    return ${options.pascalName}Status.ACTIVE;
  }
}
`;
  
  tree.write(`${domainLibPath}/src/lib/business-rules.ts`, businessRulesTs);
  
  // Index file
  const indexTs = `
export * from './lib/models';
export * from './lib/business-rules';
`;
  
  tree.write(`${domainLibPath}/src/index.ts`, indexTs);
}

async function generateFrontendFeature(tree: Tree, options: any) {
  const featurePath = `libs/${options.domain}/${options.name}-feature`;
  
  // Generiere Angular Feature Module
  const featureModuleTs = `
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';

// Material UI Imports
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatTableModule } from '@angular/material/table';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

// Feature Components
import { ${options.pascalName}ListComponent } from './components/${options.name}-list/${options.name}-list.component';
import { ${options.pascalName}DetailComponent } from './components/${options.name}-detail/${options.name}-detail.component';
import { ${options.pascalName}FormComponent } from './components/${options.name}-form/${options.name}-form.component';
import { ${options.pascalName}DialogComponent } from './components/${options.name}-dialog/${options.name}-dialog.component';

// Services
import { ${options.pascalName}Service } from './services/${options.name}.service';
import { ${options.pascalName}StateService } from './services/${options.name}-state.service';

const routes = [
  {
    path: '',
    component: ${options.pascalName}ListComponent,
  },
  {
    path: 'new',
    component: ${options.pascalName}FormComponent,
  },
  {
    path: ':id',
    component: ${options.pascalName}DetailComponent,
  },
  {
    path: ':id/edit',
    component: ${options.pascalName}FormComponent,
  },
];

@NgModule({
  declarations: [
    ${options.pascalName}ListComponent,
    ${options.pascalName}DetailComponent,
    ${options.pascalName}FormComponent,
    ${options.pascalName}DialogComponent,
  ],
  imports: [
    CommonModule,
    ReactiveFormsModule,
    RouterModule.forChild(routes),
    
    // Material UI
    MatCardModule,
    MatButtonModule,
    MatInputModule,
    MatTableModule,
    MatDialogModule,
    MatSnackBarModule,
    MatProgressSpinnerModule,
  ],
  providers: [
    ${options.pascalName}Service,
    ${options.pascalName}StateService,
  ],
})
export class ${options.pascalName}FeatureModule {}
`;
  
  tree.write(`${featurePath}/src/lib/${options.name}-feature.module.ts`, featureModuleTs);
  
  // Generiere Service
  const serviceTs = `
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { 
  ${options.pascalName}, 
  Create${options.pascalName}Dto, 
  Update${options.pascalName}Dto 
} from '@workspace/${options.domain}-${options.name}-domain';
import { TypeSafeApiService, ApiResult } from '@workspace/shared-type-safety';

@Injectable()
export class ${options.pascalName}Service {
  private readonly basePath = '/${options.domain}/${options.name}';

  constructor(private api: TypeSafeApiService) {}

  getAll(): Observable<ApiResult<${options.pascalName}[]>> {
    return this.api.get<${options.pascalName}[]>(this.basePath);
  }

  getById(id: string): Observable<ApiResult<${options.pascalName}>> {
    return this.api.get<${options.pascalName}>(\`\${this.basePath}/\${id}\`);
  }

  create(dto: Create${options.pascalName}Dto): Observable<ApiResult<${options.pascalName}>> {
    return this.api.post<Create${options.pascalName}Dto, ${options.pascalName}>(this.basePath, dto);
  }

  update(id: string, dto: Update${options.pascalName}Dto): Observable<ApiResult<${options.pascalName}>> {
    return this.api.put<Update${options.pascalName}Dto, ${options.pascalName}>(\`\${this.basePath}/\${id}\`, dto);
  }

  delete(id: string): Observable<ApiResult<void>> {
    return this.api.delete<void>(\`\${this.basePath}/\${id}\`);
  }
}
`;
  
  tree.write(`${featurePath}/src/lib/services/${options.name}.service.ts`, serviceTs);
  
  // Generiere State Service (vereinfachtes State Management)
  const stateServiceTs = `
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { ${options.pascalName} } from '@workspace/${options.domain}-${options.name}-domain';

export interface ${options.pascalName}State {
  items: ${options.pascalName}[];
  selectedItem: ${options.pascalName} | null;
  loading: boolean;
  error: string | null;
}

@Injectable()
export class ${options.pascalName}StateService {
  private itemsSubject = new BehaviorSubject<${options.pascalName}[]>([]);
  private selectedItemSubject = new BehaviorSubject<${options.pascalName} | null>(null);
  private loadingSubject = new BehaviorSubject<boolean>(false);
  private errorSubject = new BehaviorSubject<string | null>(null);

  // Public observables
  items$ = this.itemsSubject.asObservable();
  selectedItem$ = this.selectedItemSubject.asObservable();
  loading$ = this.loadingSubject.asObservable();
  error$ = this.errorSubject.asObservable();

  // Combined state observable
  state$: Observable<${options.pascalName}State> = combineLatest([
    this.items$,
    this.selectedItem$,
    this.loading$,
    this.error$,
  ]).pipe(
    map(([items, selectedItem, loading, error]) => ({
      items,
      selectedItem,
      loading,
      error,
    }))
  );

  // State mutations
  setItems(items: ${options.pascalName}[]): void {
    this.itemsSubject.next(items);
  }

  addItem(item: ${options.pascalName}): void {
    const currentItems = this.itemsSubject.value;
    this.itemsSubject.next([...currentItems, item]);
  }

  updateItem(updatedItem: ${options.pascalName}): void {
    const currentItems = this.itemsSubject.value;
    const index = currentItems.findIndex(item => item.id === updatedItem.id);
    if (index !== -1) {
      const newItems = [...currentItems];
      newItems[index] = updatedItem;
      this.itemsSubject.next(newItems);
    }
  }

  removeItem(id: string): void {
    const currentItems = this.itemsSubject.value;
    this.itemsSubject.next(currentItems.filter(item => item.id !== id));
  }

  selectItem(item: ${options.pascalName} | null): void {
    this.selectedItemSubject.next(item);
  }

  setLoading(loading: boolean): void {
    this.loadingSubject.next(loading);
  }

  setError(error: string | null): void {
    this.errorSubject.next(error);
  }

  clearError(): void {
    this.setError(null);
  }
}
`;
  
  tree.write(`${featurePath}/src/lib/services/${options.name}-state.service.ts`, stateServiceTs);
}

async function generateBackendFeature(tree: Tree, options: any) {
  const apiPath = `libs/${options.domain}/${options.name}-api`;
  
  // Generiere NestJS Module
  const moduleTs = `
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ${options.pascalName}Controller } from './${options.name}.controller';
import { ${options.pascalName}Service } from './${options.name}.service';
import { ${options.pascalName}Entity } from './entities/${options.name}.entity';
import { ${options.pascalName}Repository } from './repositories/${options.name}.repository';

@Module({
  imports: [
    TypeOrmModule.forFeature([${options.pascalName}Entity]),
  ],
  controllers: [${options.pascalName}Controller],
  providers: [
    ${options.pascalName}Service,
    ${options.pascalName}Repository,
  ],
  exports: [
    ${options.pascalName}Service,
  ],
})
export class ${options.pascalName}Module {}
`;
  
  tree.write(`${apiPath}/src/lib/${options.name}.module.ts`, moduleTs);
  
  // Generiere Entity
  const entityTs = `
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import { ${options.pascalName}Status } from '@workspace/${options.domain}-${options.name}-domain';

@Entity('${options.name}')
export class ${options.pascalName}Entity {
  @ApiProperty({ description: 'Unique identifier' })
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ApiProperty({ description: 'Creation timestamp' })
  @CreateDateColumn()
  createdAt: Date;

  @ApiProperty({ description: 'Last update timestamp' })
  @UpdateDateColumn()
  updatedAt: Date;

  @ApiProperty({ description: 'Status', enum: ${options.pascalName}Status })
  @Column({
    type: 'enum',
    enum: ${options.pascalName}Status,
    default: ${options.pascalName}Status.ACTIVE,
  })
  status: ${options.pascalName}Status;

  // TODO: Add domain-specific columns
  // Example:
  // @ApiProperty({ description: 'Name' })
  // @Column()
  // name: string;
}
`;
  
  tree.write(`${apiPath}/src/lib/entities/${options.name}.entity.ts`, entityTs);
  
  // Generiere Controller
  const controllerTs = `
import { 
  Controller, 
  Get, 
  Post, 
  Put, 
  Delete, 
  Body, 
  Param, 
  Query,
  UseGuards,
  HttpCode,
  HttpStatus,
} from '@nestjs/common';
import { 
  ApiTags, 
  ApiOperation, 
  ApiResponse, 
  ApiBearerAuth,
  ApiQuery,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '@workspace/shared-auth';
import { 
  Create${options.pascalName}Dto, 
  Update${options.pascalName}Dto,
  ${options.pascalName},
} from '@workspace/${options.domain}-${options.name}-domain';
import { ApiResult } from '@workspace/shared-type-safety';
import { ${options.pascalName}Service } from './${options.name}.service';

@ApiTags('${options.domain}/${options.name}')
@Controller('${options.domain}/${options.name}')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class ${options.pascalName}Controller {
  constructor(private readonly ${options.camelName}Service: ${options.pascalName}Service) {}

  @Get()
  @ApiOperation({ summary: 'Get all ${options.name} items' })
  @ApiResponse({ status: 200, description: 'Success' })
  @ApiQuery({ name: 'page', required: false, type: 'number' })
  @ApiQuery({ name: 'limit', required: false, type: 'number' })
  async findAll(
    @Query('page') page?: number,
    @Query('limit') limit?: number,
  ): Promise<ApiResult<${options.pascalName}[]>> {
    return this.${options.camelName}Service.findAll({ page, limit });
  }

  @Get(':id')
  @ApiOperation({ summary: 'Get ${options.name} by ID' })
  @ApiResponse({ status: 200, description: 'Success' })
  @ApiResponse({ status: 404, description: 'Not found' })
  async findById(@Param('id') id: string): Promise<ApiResult<${options.pascalName}>> {
    return this.${options.camelName}Service.findById(id);
  }

  @Post()
  @ApiOperation({ summary: 'Create new ${options.name}' })
  @ApiResponse({ status: 201, description: 'Created' })
  @ApiResponse({ status: 400, description: 'Bad request' })
  async create(@Body() dto: Create${options.pascalName}Dto): Promise<ApiResult<${options.pascalName}>> {
    return this.${options.camelName}Service.create(dto);
  }

  @Put(':id')
  @ApiOperation({ summary: 'Update ${options.name}' })
  @ApiResponse({ status: 200, description: 'Updated' })
  @ApiResponse({ status: 404, description: 'Not found' })
  async update(
    @Param('id') id: string,
    @Body() dto: Update${options.pascalName}Dto,
  ): Promise<ApiResult<${options.pascalName}>> {
    return this.${options.camelName}Service.update(id, dto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  @ApiOperation({ summary: 'Delete ${options.name}' })
  @ApiResponse({ status: 204, description: 'Deleted' })
  @ApiResponse({ status: 404, description: 'Not found' })
  async delete(@Param('id') id: string): Promise<void> {
    return this.${options.camelName}Service.delete(id);
  }
}
`;
  
  tree.write(`${apiPath}/src/lib/${options.name}.controller.ts`, controllerTs);
  
  // Generiere Service
  const apiServiceTs = `
import { Injectable, NotFoundException } from '@nestjs/common';
import { 
  Create${options.pascalName}Dto, 
  Update${options.pascalName}Dto,
  ${options.pascalName},
  ${options.pascalName}BusinessRules,
} from '@workspace/${options.domain}-${options.name}-domain';
import { ApiResult, ApiResponseUtil } from '@workspace/shared-utils';
import { ${options.pascalName}Repository } from './repositories/${options.name}.repository';
import { EventBus } from '@nestjs/cqrs';

@Injectable()
export class ${options.pascalName}Service {
  constructor(
    private readonly ${options.camelName}Repository: ${options.pascalName}Repository,
    private readonly eventBus: EventBus,
  ) {}

  async findAll(options: { page?: number; limit?: number } = {}): Promise<ApiResult<${options.pascalName}[]>> {
    try {
      const items = await this.${options.camelName}Repository.findAll(options);
      return ApiResponseUtil.success(items);
    } catch (error) {
      return ApiResponseUtil.error('Failed to fetch ${options.name} items', [error.message]);
    }
  }

  async findById(id: string): Promise<ApiResult<${options.pascalName}>> {
    try {
      const item = await this.${options.camelName}Repository.findById(id);
      if (!item) {
        throw new NotFoundException(\`${options.pascalName} with ID \${id} not found\`);
      }
      return ApiResponseUtil.success(item);
    } catch (error) {
      return ApiResponseUtil.error('Failed to fetch ${options.name}', [error.message]);
    }
  }

  async create(dto: Create${options.pascalName}Dto): Promise<ApiResult<${options.pascalName}>> {
    try {
      // Business rules validation
      const validationErrors = ${options.pascalName}BusinessRules.validateCreation(dto);
      if (validationErrors.length > 0) {
        return ApiResponseUtil.error('Validation failed', validationErrors);
      }

      const item = await this.${options.camelName}Repository.create(dto);
      
      // Publish domain event
      // await this.eventBus.publish(new ${options.pascalName}CreatedEvent(...));
      
      return ApiResponseUtil.success(item);
    } catch (error) {
      return ApiResponseUtil.error('Failed to create ${options.name}', [error.message]);
    }
  }

  async update(id: string, dto: Update${options.pascalName}Dto): Promise<ApiResult<${options.pascalName}>> {
    try {
      const existing = await this.${options.camelName}Repository.findById(id);
      if (!existing) {
        throw new NotFoundException(\`${options.pascalName} with ID \${id} not found\`);
      }

      // Business rules validation
      const validationErrors = ${options.pascalName}BusinessRules.validateUpdate(existing, dto);
      if (validationErrors.length > 0) {
        return ApiResponseUtil.error('Validation failed', validationErrors);
      }

      const updated = await this.${options.camelName}Repository.update(id, dto);
      
      // Publish domain event
      // await this.eventBus.publish(new ${options.pascalName}UpdatedEvent(...));
      
      return ApiResponseUtil.success(updated);
    } catch (error) {
      return ApiResponseUtil.error('Failed to update ${options.name}', [error.message]);
    }
  }

  async delete(id: string): Promise<void> {
    const existing = await this.${options.camelName}Repository.findById(id);
    if (!existing) {
      throw new NotFoundException(\`${options.pascalName} with ID \${id} not found\`);
    }

    // Business rules validation
    if (!${options.pascalName}BusinessRules.canDelete(existing)) {
      throw new Error('${options.pascalName} cannot be deleted due to business rules');
    }

    await this.${options.camelName}Repository.delete(id);
  }
}
`;
  
  tree.write(`${apiPath}/src/lib/${options.name}.service.ts`, apiServiceTs);
}

async function generateE2eTests(tree: Tree, options: any) {
  const e2ePath = `apps/${options.name}-e2e`;
  
  const e2eSpecTs = `
import { test, expect } from '@playwright/test';

test.describe('${options.pascalName} Feature', () => {
  test.beforeEach(async ({ page }) => {
    // Setup: Login, navigate to feature, etc.
    await page.goto('/${options.domain}/${options.name}');
  });

  test('should display ${options.name} list', async ({ page }) => {
    await expect(page.locator('[data-testid="${options.name}-list"]')).toBeVisible();
  });

  test('should create new ${options.name}', async ({ page }) => {
    await page.click('[data-testid="create-${options.name}-button"]');
    
    // Fill form
    await page.fill('[data-testid="${options.name}-name-input"]', 'Test ${options.pascalName}');
    
    // Submit
    await page.click('[data-testid="submit-button"]');
    
    // Verify creation
    await expect(page.locator('text=Test ${options.pascalName}')).toBeVisible();
  });

  test('should update existing ${options.name}', async ({ page }) => {
    // Assume we have a test ${options.name} to work with
    await page.click('[data-testid="${options.name}-item"]:first-child [data-testid="edit-button"]');
    
    // Update form
    await page.fill('[data-testid="${options.name}-name-input"]', 'Updated ${options.pascalName}');
    
    // Submit
    await page.click('[data-testid="submit-button"]');
    
    // Verify update
    await expect(page.locator('text=Updated ${options.pascalName}')).toBeVisible();
  });

  test('should delete ${options.name}', async ({ page }) => {
    // Click delete button
    await page.click('[data-testid="${options.name}-item"]:first-child [data-testid="delete-button"]');
    
    // Confirm deletion
    await page.click('[data-testid="confirm-delete-button"]');
    
    // Verify deletion
    await expect(page.locator('[data-testid="${options.name}-item"]:first-child')).not.toBeVisible();
  });
});
`;
  
  tree.write(`${e2ePath}/src/${options.name}.spec.ts`, e2eSpecTs);
}

function normalizeOptions(options: FeatureGeneratorSchema): any {
  return {
    ...options,
    camelName: toCamelCase(options.name),
    pascalName: toPascalCase(options.name),
  };
}

function toCamelCase(str: string): string {
  return str.charAt(0).toLowerCase() + str.slice(1);
}

function toPascalCase(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

Dieser Nx-Generator zeigt die Macht eines integrierten Monorepo-Ansatzes. Mit einem einzigen Befehl können Sie eine vollständige Full-Stack-Feature erstellen, die alle notwendigen Komponenten umfasst: Domain Models, Frontend-Komponenten, Backend-APIs und E2E-Tests. Alle Teile sind automatisch miteinander verbunden und verwenden geteilte Types und Utilities.

32.7 Gemeinsame CI/CD-Pipelines

Gemeinsame CI/CD-Pipelines für Angular-NestJS-Anwendungen sind wie die Orchestrierung einer Symphonie - jedes Instrument (Frontend, Backend, Tests, Deployments) muss zur richtigen Zeit spielen und harmonisch zusammenarbeiten. Der Vorteil eines Monorepos zeigt sich besonders deutlich in der CI/CD-Pipeline, wo Sie intelligente Build-Optimierungen und koordinierte Deployments implementieren können.

// .github/workflows/ci-cd.yml (als TypeScript-Konfiguration dargestellt)
// Intelligente CI/CD-Pipeline für Angular-NestJS Monorepo
interface CICDConfig {
  name: string;
  triggers: {
    push: string[];
    pullRequest: string[];
  };
  jobs: Record<string, CICDJob>;
}

interface CICDJob {
  runsOn: string;
  needs?: string[];
  strategy?: {
    matrix: Record<string, any[]>;
  };
  steps: CICDStep[];
  outputs?: Record<string, string>;
}

interface CICDStep {
  name: string;
  uses?: string;
  run?: string;
  with?: Record<string, any>;
  env?: Record<string, string>;
  if?: string;
}

const cicdConfig: CICDConfig = {
  name: 'Full-Stack CI/CD Pipeline',
  triggers: {
    push: ['main', 'develop'],
    pullRequest: ['main', 'develop'],
  },
  jobs: {
    // Job 1: Setup und Dependency-Installation
    setup: {
      runsOn: 'ubuntu-latest',
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
          with: {
            'fetch-depth': 0, // Benötigt für Nx affected
          },
        },
        {
          name: 'Setup Node.js',
          uses: 'actions/setup-node@v4',
          with: {
            'node-version': '18',
            cache: 'npm',
          },
        },
        {
          name: 'Install Dependencies',
          run: 'npm ci --legacy-peer-deps',
        },
        {
          name: 'Cache Dependencies',
          uses: 'actions/cache@v3',
          with: {
            path: 'node_modules',
            key: 'npm-${{ runner.os }}-${{ hashFiles(\'package-lock.json\') }}',
          },
        },
      ],
      outputs: {
        'dependency-cache-hit': 'steps.cache.outputs.cache-hit',
      },
    },

    // Job 2: Linting und Code Quality
    lint: {
      runsOn: 'ubuntu-latest',
      needs: ['setup'],
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
          with: {
            'fetch-depth': 0,
          },
        },
        {
          name: 'Restore Dependencies',
          uses: 'actions/cache@v3',
          with: {
            path: 'node_modules',
            key: 'npm-${{ runner.os }}-${{ hashFiles(\'package-lock.json\') }}',
          },
        },
        {
          name: 'Lint Affected Projects',
          run: 'npx nx affected:lint --base=origin/main --parallel=3',
        },
        {
          name: 'Type Check',
          run: 'npx nx run-many --target=type-check --all --parallel=3',
        },
        {
          name: 'Check Formatting',
          run: 'npx nx format:check --base=origin/main',
        },
      ],
    },

    // Job 3: Unit Tests
    test: {
      runsOn: 'ubuntu-latest',
      needs: ['setup'],
      strategy: {
        matrix: {
          shard: [1, 2, 3], // Parallele Test-Ausführung
        },
      },
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
          with: {
            'fetch-depth': 0,
          },
        },
        {
          name: 'Restore Dependencies',
          uses: 'actions/cache@v3',
          with: {
            path: 'node_modules',
            key: 'npm-${{ runner.os }}-${{ hashFiles(\'package-lock.json\') }}',
          },
        },
        {
          name: 'Run Unit Tests',
          run: 'npx nx affected:test --base=origin/main --parallel=1 --shard=${{ matrix.shard }}/3',
          env: {
            CI: 'true',
          },
        },
        {
          name: 'Upload Coverage Reports',
          uses: 'codecov/codecov-action@v3',
          with: {
            token: '${{ secrets.CODECOV_TOKEN }}',
            flags: 'unittests-shard-${{ matrix.shard }}',
          },
        },
      ],
    },

    // Job 4: Build Applications
    build: {
      runsOn: 'ubuntu-latest',
      needs: ['lint'],
      strategy: {
        matrix: {
          project: ['frontend', 'backend'],
        },
      },
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
          with: {
            'fetch-depth': 0,
          },
        },
        {
          name: 'Restore Dependencies',
          uses: 'actions/cache@v3',
          with: {
            path: 'node_modules',
            key: 'npm-${{ runner.os }}-${{ hashFiles(\'package-lock.json\') }}',
          },
        },
        {
          name: 'Build Application',
          run: 'npx nx build ${{ matrix.project }} --configuration=production',
        },
        {
          name: 'Upload Build Artifacts',
          uses: 'actions/upload-artifact@v3',
          with: {
            name: '${{ matrix.project }}-build',
            path: 'dist/apps/${{ matrix.project }}',
            'retention-days': 7,
          },
        },
      ],
    },

    // Job 5: Integration Tests
    'integration-test': {
      runsOn: 'ubuntu-latest',
      needs: ['build'],
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
        },
        {
          name: 'Setup Test Database',
          run: 'docker-compose -f docker-compose.test.yml up -d postgres redis',
        },
        {
          name: 'Download Backend Build',
          uses: 'actions/download-artifact@v3',
          with: {
            name: 'backend-build',
            path: 'dist/apps/backend',
          },
        },
        {
          name: 'Run Database Migrations',
          run: 'npm run migration:run',
          env: {
            DATABASE_URL: 'postgresql://test:test@localhost:5432/testdb',
          },
        },
        {
          name: 'Start Backend for Integration Tests',
          run: 'npm run start:test:backend &',
          env: {
            NODE_ENV: 'test',
            DATABASE_URL: 'postgresql://test:test@localhost:5432/testdb',
            REDIS_URL: 'redis://localhost:6379',
          },
        },
        {
          name: 'Wait for Backend',
          run: 'npx wait-on http://localhost:3333/api/health --timeout=60000',
        },
        {
          name: 'Run Integration Tests',
          run: 'npx nx affected:test --base=origin/main --tag=integration',
        },
        {
          name: 'Cleanup Test Environment',
          run: 'docker-compose -f docker-compose.test.yml down',
          if: 'always()',
        },
      ],
    },

    // Job 6: E2E Tests
    'e2e-test': {
      runsOn: 'ubuntu-latest',
      needs: ['build'],
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
        },
        {
          name: 'Download Build Artifacts',
          uses: 'actions/download-artifact@v3',
          with: {
            name: 'frontend-build',
            path: 'dist/apps/frontend',
          },
        },
        {
          name: 'Download Backend Build',
          uses: 'actions/download-artifact@v3',
          with: {
            name: 'backend-build',
            path: 'dist/apps/backend',
          },
        },
        {
          name: 'Setup E2E Environment',
          run: 'docker-compose -f docker-compose.e2e.yml up -d',
        },
        {
          name: 'Install Playwright',
          run: 'npx playwright install chromium',
        },
        {
          name: 'Run E2E Tests',
          run: 'npx nx affected:e2e --base=origin/main',
          env: {
            BASE_URL: 'http://localhost:4200',
            API_URL: 'http://localhost:3333/api',
          },
        },
        {
          name: 'Upload E2E Test Results',
          uses: 'actions/upload-artifact@v3',
          with: {
            name: 'e2e-test-results',
            path: 'dist/apps/frontend-e2e/videos',
          },
          if: 'failure()',
        },
        {
          name: 'Cleanup E2E Environment',
          run: 'docker-compose -f docker-compose.e2e.yml down',
          if: 'always()',
        },
      ],
    },

    // Job 7: Security Scanning
    security: {
      runsOn: 'ubuntu-latest',
      needs: ['setup'],
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
        },
        {
          name: 'Run Dependency Audit',
          run: 'npm audit --audit-level=moderate',
        },
        {
          name: 'Run Snyk Security Scan',
          uses: 'snyk/actions/node@master',
          env: {
            SNYK_TOKEN: '${{ secrets.SNYK_TOKEN }}',
          },
          with: {
            args: '--severity-threshold=high',
          },
        },
        {
          name: 'CodeQL Analysis',
          uses: 'github/codeql-action/analyze@v2',
          with: {
            languages: 'javascript',
          },
        },
      ],
    },

    // Job 8: Container Building
    'build-containers': {
      runsOn: 'ubuntu-latest',
      needs: ['test', 'integration-test'],
      if: 'github.ref == \'refs/heads/main\' || github.ref == \'refs/heads/develop\'',
      steps: [
        {
          name: 'Checkout Repository',
          uses: 'actions/checkout@v4',
        },
        {
          name: 'Setup Docker Buildx',
          uses: 'docker/setup-buildx-action@v2',
        },
        {
          name: 'Login to Container Registry',
          uses: 'docker/login-action@v2',
          with: {
            registry: 'ghcr.io',
            username: '${{ github.actor }}',
            password: '${{ secrets.GITHUB_TOKEN }}',
          },
        },
        {
          name: 'Download Build Artifacts',
          uses: 'actions/download-artifact@v3',
          with: {
            name: 'backend-build',
            path: 'dist/apps/backend',
          },
        },
        {
          name: 'Build and Push Backend Container',
          uses: 'docker/build-push-action@v4',
          with: {
            context: '.',
            file: 'apps/backend/Dockerfile',
            push: true,
            tags: 'ghcr.io/${{ github.repository }}/backend:${{ github.sha }},ghcr.io/${{ github.repository }}/backend:latest',
            'cache-from': 'type=gha',
            'cache-to': 'type=gha,mode=max',
          },
        },
        {
          name: 'Build and Push Frontend Container',
          uses: 'docker/build-push-action@v4',
          with: {
            context: '.',
            file: 'apps/frontend/Dockerfile',
            push: true,
            tags: 'ghcr.io/${{ github.repository }}/frontend:${{ github.sha }},ghcr.io/${{ github.repository }}/frontend:latest',
            'cache-from': 'type=gha',
            'cache-to': 'type=gha,mode=max',
          },
        },
      ],
    },

    // Job 9: Deployment
    deploy: {
      runsOn: 'ubuntu-latest',
      needs: ['build-containers', 'e2e-test', 'security'],
      if: 'github.ref == \'refs/heads/main\'',
      steps: [
        {
          name: 'Deploy to Staging',
          run: 'echo "Deploying to staging environment"',
          env: {
            KUBE_CONFIG: '${{ secrets.KUBE_CONFIG_STAGING }}',
            BACKEND_IMAGE: 'ghcr.io/${{ github.repository }}/backend:${{ github.sha }}',
            FRONTEND_IMAGE: 'ghcr.io/${{ github.repository }}/frontend:${{ github.sha }}',
          },
        },
        {
          name: 'Run Smoke Tests',
          run: 'npx nx run frontend-e2e:smoke-test',
          env: {
            BASE_URL: 'https://staging.example.com',
          },
        },
        {
          name: 'Deploy to Production',
          run: 'echo "Deploying to production environment"',
          env: {
            KUBE_CONFIG: '${{ secrets.KUBE_CONFIG_PRODUCTION }}',
          },
          if: 'success()',
        },
      ],
    },
  },
};

// TypeScript-basierte Pipeline-Konfiguration (Beispiel-Implementation)
export class CICDPipeline {
  static generateGitHubActions(config: CICDConfig): string {
    let yaml = `name: ${config.name}\n\n`;
    
    // Triggers
    yaml += 'on:\n';
    yaml += '  push:\n';
    yaml += `    branches: [${config.triggers.push.map(b => `'${b}'`).join(', ')}]\n`;
    yaml += '  pull_request:\n';
    yaml += `    branches: [${config.triggers.pullRequest.map(b => `'${b}'`).join(', ')}]\n\n`;
    
    // Jobs
    yaml += 'jobs:\n';
    Object.entries(config.jobs).forEach(([jobName, job]) => {
      yaml += `  ${jobName}:\n`;
      yaml += `    runs-on: ${job.runsOn}\n`;
      
      if (job.needs) {
        yaml += `    needs: [${job.needs.map(n => `'${n}'`).join(', ')}]\n`;
      }
      
      if (job.strategy) {
        yaml += '    strategy:\n';
        yaml += '      matrix:\n';
        Object.entries(job.strategy.matrix).forEach(([key, values]) => {
          yaml += `        ${key}: [${values.map(v => `'${v}'`).join(', ')}]\n`;
        });
      }
      
      yaml += '    steps:\n';
      job.steps.forEach(step => {
        yaml += `      - name: ${step.name}\n`;
        if (step.uses) yaml += `        uses: ${step.uses}\n`;
        if (step.run) yaml += `        run: ${step.run}\n`;
        if (step.with) {
          yaml += '        with:\n';
          Object.entries(step.with).forEach(([key, value]) => {
            yaml += `          ${key}: ${value}\n`;
          });
        }
        if (step.env) {
          yaml += '        env:\n';
          Object.entries(step.env).forEach(([key, value]) => {
            yaml += `          ${key}: ${value}\n`;
          });
        }
        if (step.if) yaml += `        if: ${step.if}\n`;
      });
      yaml += '\n';
    });
    
    return yaml;
  }
}

Diese intelligente CI/CD-Pipeline nutzt Nx’s “affected”-Funktionalität, um nur die Teile der Anwendung zu testen und zu builden, die tatsächlich geändert wurden. Dies reduziert Build-Zeiten erheblich und macht die Pipeline effizienter.

32.8 Security zwischen Frontend und Backend

Security in einer Angular-NestJS-Architektur ist wie das Design eines Sicherheitssystems für ein Hochsicherheitsgebäude - jede Ebene muss geschützt sein, aber die Sicherheitsmaßnahmen müssen nahtlos zusammenarbeiten, ohne die Benutzererfahrung zu beeinträchtigen. Die Integration von Frontend und Backend schafft einzigartige Sicherheitsmöglichkeiten, aber auch neue Herausforderungen.

// libs/shared/security/src/lib/security-manager.ts
// Umfassendes Security-Management für Angular-NestJS-Integration
import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, timer } from 'rxjs';
import { map, switchMap, catchError } from 'rxjs/operators';

export interface SecurityContext {
  user: AuthenticatedUser | null;
  permissions: string[];
  roles: string[];
  sessionId: string;
  csrfToken: string;
  tokenExpiry: Date;
  mfaRequired: boolean;
  lastActivity: Date;
}

export interface AuthenticatedUser {
  id: string;
  email: string;
  firstName: string;
  lastName: string;
  roles: string[];
  permissions: string[];
  emailVerified: boolean;
  mfaEnabled: boolean;
  lastLoginAt: Date;
}

export interface SecurityPolicy {
  sessionTimeout: number; // Millisekunden
  tokenRefreshThreshold: number; // Sekunden vor Ablauf
  maxConcurrentSessions: number;
  passwordPolicy: PasswordPolicy;
  mfaPolicy: MFAPolicy;
  ipWhitelistEnabled: boolean;
  allowedOrigins: string[];
}

export interface PasswordPolicy {
  minLength: number;
  requireUppercase: boolean;
  requireLowercase: boolean;
  requireNumbers: boolean;
  requireSpecialChars: boolean;
  preventReuse: number; // Anzahl der letzten Passwörter, die nicht wiederverwendet werden dürfen
  maxAge: number; // Tage
}

export interface MFAPolicy {
  required: boolean;
  methods: ('totp' | 'sms' | 'email')[];
  backupCodesEnabled: boolean;
  trustDeviceDays: number;
}

@Injectable({
  providedIn: 'root'
})
export class SecurityManager {
  private securityContextSubject = new BehaviorSubject<SecurityContext | null>(null);
  private securityPolicy: SecurityPolicy;
  private sessionTimer: any;

  public securityContext$ = this.securityContextSubject.asObservable();

  constructor() {
    this.initializeSecurityPolicy();
    this.startSecurityMonitoring();
  }

  /**
   * Initialisiert den Security Context nach erfolgreicher Authentifizierung
   */
  initializeSecurityContext(authResponse: any): void {
    const context: SecurityContext = {
      user: authResponse.user,
      permissions: authResponse.user.permissions,
      roles: authResponse.user.roles,
      sessionId: authResponse.sessionId,
      csrfToken: authResponse.csrfToken,
      tokenExpiry: new Date(authResponse.tokenExpiry),
      mfaRequired: authResponse.mfaRequired,
      lastActivity: new Date(),
    };

    this.securityContextSubject.next(context);
    this.startSessionManagement(context);
    this.scheduleTokenRefresh(context);
  }

  /**
   * Überprüft ob der Benutzer eine bestimmte Permission hat
   */
  hasPermission(permission: string): boolean {
    const context = this.securityContextSubject.value;
    return context?.permissions.includes(permission) || false;
  }

  /**
   * Überprüft ob der Benutzer eine bestimmte Rolle hat
   */
  hasRole(role: string): boolean {
    const context = this.securityContextSubject.value;
    return context?.roles.includes(role) || false;
  }

  /**
   * Überprüft mehrere Permissions (UND-Verknüpfung)
   */
  hasAllPermissions(permissions: string[]): boolean {
    return permissions.every(permission => this.hasPermission(permission));
  }

  /**
   * Überprüft ob der Benutzer mindestens eine der angegebenen Permissions hat
   */
  hasAnyPermission(permissions: string[]): boolean {
    return permissions.some(permission => this.hasPermission(permission));
  }

  /**
   * Validiert Passwort gegen Security Policy
   */
  validatePassword(password: string): { isValid: boolean; errors: string[] } {
    const errors: string[] = [];
    const policy = this.securityPolicy.passwordPolicy;

    if (password.length < policy.minLength) {
      errors.push(`Password must be at least ${policy.minLength} characters long`);
    }

    if (policy.requireUppercase && !/[A-Z]/.test(password)) {
      errors.push('Password must contain at least one uppercase letter');
    }

    if (policy.requireLowercase && !/[a-z]/.test(password)) {
      errors.push('Password must contain at least one lowercase letter');
    }

    if (policy.requireNumbers && !/\d/.test(password)) {
      errors.push('Password must contain at least one number');
    }

    if (policy.requireSpecialChars && !/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
      errors.push('Password must contain at least one special character');
    }

    return {
      isValid: errors.length === 0,
      errors,
    };
  }

  /**
   * Generiert CSRF-Token für sichere Formular-Übertragungen
   */
  generateCSRFToken(): string {
    const context = this.securityContextSubject.value;
    if (!context) {
      throw new Error('No security context available');
    }
    return context.csrfToken;
  }

  /**
   * Validiert CSRF-Token
   */
  validateCSRFToken(token: string): boolean {
    const context = this.securityContextSubject.value;
    return context?.csrfToken === token;
  }

  /**
   * Aktualisiert die letzte Aktivität (für Session Timeout)
   */
  updateLastActivity(): void {
    const context = this.securityContextSubject.value;
    if (context) {
      context.lastActivity = new Date();
      this.securityContextSubject.next(context);
    }
  }

  /**
   * Überprüft ob die aktuelle Session noch gültig ist
   */
  isSessionValid(): boolean {
    const context = this.securityContextSubject.value;
    if (!context) return false;

    const now = new Date();
    const sessionAge = now.getTime() - context.lastActivity.getTime();
    const maxAge = this.securityPolicy.sessionTimeout;

    return sessionAge < maxAge && now < context.tokenExpiry;
  }

  /**
   * Invalidiert die aktuelle Session
   */
  invalidateSession(): void {
    this.securityContextSubject.next(null);
    this.clearSessionStorage();
    if (this.sessionTimer) {
      clearInterval(this.sessionTimer);
    }
  }

  /**
   * Implementiert Content Security Policy Headers
   */
  getCSPHeaders(): Record<string, string> {
    return {
      'Content-Security-Policy': [
        "default-src 'self'",
        "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com",
        "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
        "font-src 'self' https://fonts.gstatic.com",
        "img-src 'self' data: https:",
        "connect-src 'self' " + this.securityPolicy.allowedOrigins.join(' '),
        "frame-ancestors 'none'",
        "base-uri 'self'",
        "form-action 'self'",
      ].join('; '),
      'X-Content-Type-Options': 'nosniff',
      'X-Frame-Options': 'DENY',
      'X-XSS-Protection': '1; mode=block',
      'Referrer-Policy': 'strict-origin-when-cross-origin',
      'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
    };
  }

  /**
   * Implementiert sichere Cookie-Einstellungen
   */
  getSecureCookieOptions(): any {
    return {
      httpOnly: true,
      secure: true, // Nur über HTTPS
      sameSite: 'strict',
      maxAge: this.securityPolicy.sessionTimeout / 1000, // Sekunden
      domain: window.location.hostname,
      path: '/',
    };
  }

  /**
   * Überwacht verdächtige Aktivitäten
   */
  private startSecurityMonitoring(): void {
    // Überwache mehrfache gescheiterte Login-Versuche
    this.monitorFailedLogins();
    
    // Überwache ungewöhnliche IP-Adressen
    this.monitorIPAddresses();
    
    // Überwache Session-Anomalien
    this.monitorSessionAnomalies();
  }

  private initializeSecurityPolicy(): void {
    this.securityPolicy = {
      sessionTimeout: 30 * 60 * 1000, // 30 Minuten
      tokenRefreshThreshold: 300, // 5 Minuten vor Ablauf
      maxConcurrentSessions: 3,
      passwordPolicy: {
        minLength: 12,
        requireUppercase: true,
        requireLowercase: true,
        requireNumbers: true,
        requireSpecialChars: true,
        preventReuse: 5,
        maxAge: 90, // 90 Tage
      },
      mfaPolicy: {
        required: true,
        methods: ['totp', 'sms'],
        backupCodesEnabled: true,
        trustDeviceDays: 30,
      },
      ipWhitelistEnabled: false,
      allowedOrigins: ['https://api.example.com', 'wss://api.example.com'],
    };
  }

  private startSessionManagement(context: SecurityContext): void {
    // Session Timeout Timer
    this.sessionTimer = setInterval(() => {
      if (!this.isSessionValid()) {
        this.invalidateSession();
        // Redirect to login or show session expired message
      }
    }, 60000); // Prüfe jede Minute
  }

  private scheduleTokenRefresh(context: SecurityContext): void {
    const now = new Date();
    const expiryTime = context.tokenExpiry.getTime();
    const refreshTime = expiryTime - (this.securityPolicy.tokenRefreshThreshold * 1000);
    const delay = refreshTime - now.getTime();

    if (delay > 0) {
      timer(delay).subscribe(() => {
        this.refreshAuthToken();
      });
    }
  }

  private async refreshAuthToken(): Promise<void> {
    try {
      // API-Call zum Token-Refresh
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRF-Token': this.generateCSRFToken(),
        },
        credentials: 'include',
      });

      if (response.ok) {
        const data = await response.json();
        this.updateTokenInContext(data.accessToken, data.expiresIn);
      } else {
        this.invalidateSession();
      }
    } catch (error) {
      console.error('Token refresh failed:', error);
      this.invalidateSession();
    }
  }

  private updateTokenInContext(newToken: string, expiresIn: number): void {
    const context = this.securityContextSubject.value;
    if (context) {
      context.tokenExpiry = new Date(Date.now() + (expiresIn * 1000));
      this.securityContextSubject.next(context);
      this.scheduleTokenRefresh(context);
    }
  }

  private clearSessionStorage(): void {
    // Clear sensitive data from storage
    sessionStorage.clear();
    
    // Clear specific localStorage items if needed
    const sensitiveKeys = ['authToken', 'refreshToken', 'userPermissions'];
    sensitiveKeys.forEach(key => localStorage.removeItem(key));
  }

  private monitorFailedLogins(): void {
    // Implementation für Failed Login Monitoring
    // Würde mit Backend koordiniert werden
  }

  private monitorIPAddresses(): void {
    // Implementation für IP-Address Monitoring
    // Würde verdächtige IP-Adressen an Backend melden
  }

  private monitorSessionAnomalies(): void {
    // Implementation für Session Anomaly Detection
    // Würde ungewöhnliche Session-Muster erkennen
  }
}

// Angular Guard für Route Protection
@Injectable({
  providedIn: 'root'
})
export class SecurityGuard {
  constructor(private securityManager: SecurityManager) {}

  canActivate(permission?: string, role?: string): boolean {
    if (!this.securityManager.isSessionValid()) {
      return false;
    }

    if (permission && !this.securityManager.hasPermission(permission)) {
      return false;
    }

    if (role && !this.securityManager.hasRole(role)) {
      return false;
    }

    return true;
  }

  canActivateChild(permissions?: string[], roles?: string[]): boolean {
    if (!this.securityManager.isSessionValid()) {
      return false;
    }

    if (permissions && !this.securityManager.hasAllPermissions(permissions)) {
      return false;
    }

    if (roles && !roles.some(role => this.securityManager.hasRole(role))) {
      return false;
    }

    return true;
  }
}

Diese umfassende Security-Architektur zeigt, wie Angular und NestJS zusammenarbeiten können, um eine sichere Full-Stack-Anwendung zu erstellen. Die Integration ermöglicht es, Sicherheitsrichtlinien zentral zu definieren und sowohl im Frontend als auch im Backend konsistent durchzusetzen.

Die Kombination aus client-seitiger Validierung (für bessere UX) und server-seitiger Durchsetzung (für echte Sicherheit) schafft ein robustes Sicherheitssystem. Features wie automatisches Token-Refresh, Session-Management und Security-Monitoring arbeiten nahtlos zusammen, um sowohl Sicherheit als auch Benutzerfreundlichkeit zu gewährleisten.


32.9 Fazit: Die Zukunft der Full-Stack-Entwicklung

Die Kombination von Angular und NestJS repräsentiert mehr als nur eine technische Entscheidung - sie verkörpert eine Vision der modernen Software-Entwicklung, in der Type Safety, Developer Experience und architektonische Exzellenz nicht mehr separate Ziele sind, sondern natürliche Eigenschaften einer gut gestalteten Technologie-Stack.

Diese Schulungsunterlagen haben Sie durch eine umfassende Reise durch die Welt von NestJS geführt, von den grundlegenden Konzepten bis hin zu hochkomplexen Enterprise-Architekturen. Aber das wahre Ziel war nicht nur das Erlernen eines Frameworks, sondern das Verstehen der Prinzipien und Patterns, die nachhaltige, skalierbare und wartbare Software-Systeme ausmachen.

Die Angular-NestJS-Kombination zeigt uns einen Ausblick auf die Zukunft der Web-Entwicklung: Eine Zukunft, in der Frontend und Backend nicht mehr separate Silos sind, sondern integrierte Teile eines kohärenten Systems. Eine Zukunft, in der TypeScript# Kombinierte Angular-NestJS-Architektur

Stellen Sie sich vor, Sie könnten ein Orchester dirigieren, in dem alle Musiker nicht nur dieselbe Sprache sprechen, sondern auch dieselben Noten lesen, dieselben Instrumente verstehen und perfekt aufeinander abgestimmt sind. Genau diese Harmonie erreichen Sie, wenn Sie Angular und NestJS in einer kombinierten Architektur verwenden. Beide Frameworks teilen nicht nur TypeScript als gemeinsame Sprache, sondern auch ähnliche Designprinzipien, Patterns und sogar Entwickler-Tools.

Diese Kombination ist mehr als nur die Summe ihrer Teile. Angular und NestJS wurden von denselben Prinzipien inspiriert - Dependency Injection, Decorator-basierte Konfiguration, modulare Architektur und starke Typisierung. Wenn Sie sie zusammen verwenden, entsteht eine synergetische Beziehung, die die Entwicklungseffizienz exponentiell steigert und gleichzeitig die Komplexität reduziert.

Denken Sie an traditionelle Full-Stack-Entwicklung wie an ein Gespräch zwischen zwei Menschen, die verschiedene Sprachen sprechen und einen Übersetzer benötigen. Jede Kommunikation zwischen Frontend und Backend erfordert Übersetzung, Validierung und oft Kompromisse bei der Datenintegrität. Mit Angular und NestJS hingegen sprechen Frontend und Backend dieselbe Sprache - nicht nur syntaktisch durch TypeScript, sondern auch konzeptuell durch gemeinsame Patterns und Strukturen.

Was macht diese Kombination so besonders? Es ist die Möglichkeit, Typen, Interfaces, Validierungsregeln und sogar Business Logic zwischen Frontend und Backend zu teilen. Wenn Sie eine neue API-Endpoint in NestJS erstellen, können Sie sofort die entsprechenden TypeScript-Interfaces in Angular verwenden. Wenn Sie Validierungsregeln ändern, werden diese automatisch in beiden Teilen Ihrer Anwendung reflektiert. Diese nahtlose Integration reduziert nicht nur Entwicklungszeit, sondern auch die Wahrscheinlichkeit von Fehlern und Inkonsistenzen.

32.10 Full-Stack TypeScript Setup

Das Setup einer kombinierten Angular-NestJS-Architektur ist wie das Entwerfen einer Smart-Home-Infrastruktur - alle Komponenten müssen von Anfang an darauf ausgelegt sein, miteinander zu kommunizieren und sich gegenseitig zu ergänzen. Der erste Schritt besteht darin, eine Projektstruktur zu schaffen, die sowohl die Unabhängigkeit der einzelnen Anwendungen als auch ihre Integration ermöglicht.

Die moderne Herangehensweise nutzt ein Monorepo-Setup, bei dem Angular und NestJS als separate Anwendungen in einem gemeinsamen Repository leben. Dies ermöglicht es, Code zu teilen, gemeinsame Entwicklungsworkflows zu verwenden und dennoch die Flexibilität für unabhängige Deployments zu behalten.

// tools/workspace-setup/src/generators/full-stack-app.generator.ts
// Generator für eine vollständige Angular-NestJS-Anwendung
import { Tree, formatFiles, installPackagesTask, generateFiles, joinPathFragments } from '@nrwl/devkit';
import { Schema as FullStackAppGeneratorSchema } from './schema';

interface NormalizedSchema extends FullStackAppGeneratorSchema {
  projectName: string;
  projectRoot: string;
  backendProjectName: string;
  frontendProjectName: string;
  sharedLibName: string;
}

export default async function (tree: Tree, options: FullStackAppGeneratorSchema) {
  const normalizedOptions = normalizeOptions(tree, options);
  
  // Erstelle die grundlegende Projektstruktur
  await createProjectStructure(tree, normalizedOptions);
  
  // Generiere Backend (NestJS)
  await generateBackend(tree, normalizedOptions);
  
  // Generiere Frontend (Angular)
  await generateFrontend(tree, normalizedOptions);
  
  // Erstelle Shared Libraries
  await generateSharedLibraries(tree, normalizedOptions);
  
  // Konfiguriere Development Tools
  await configureDevTools(tree, normalizedOptions);
  
  // Setup CI/CD Pipeline
  await setupCiCdPipeline(tree, normalizedOptions);
  
  await formatFiles(tree);
  return () => {
    installPackagesTask(tree);
  };
}

async function createProjectStructure(tree: Tree, options: NormalizedSchema) {
  // Erstelle Workspace-Konfiguration
  const workspaceConfig = {
    version: 2,
    projects: {},
    defaultProject: options.frontendProjectName,
  };
  
  tree.write('workspace.json', JSON.stringify(workspaceConfig, null, 2));
  
  // Erstelle Root-Level package.json
  const packageJson = {
    name: options.projectName,
    version: '0.0.1',
    private: true,
    scripts: {
      'build': 'nx run-many --target=build --projects=frontend,backend',
      'test': 'nx run-many --target=test --projects=frontend,backend,shared-models',
      'lint': 'nx run-many --target=lint --all',
      'e2e': 'nx e2e frontend-e2e',
      'start:dev': 'concurrently "nx serve backend" "nx serve frontend"',
      'start:prod': 'node apps/backend/dist/main.js',
    },
    devDependencies: {
      '@nrwl/nx': '^16.0.0',
      '@nrwl/angular': '^16.0.0',
      '@nrwl/nest': '^16.0.0',
      '@nrwl/workspace': '^16.0.0',
      'concurrently': '^8.0.0',
      'typescript': '^5.0.0',
    },
  };
  
  tree.write('package.json', JSON.stringify(packageJson, null, 2));
  
  // Erstelle TypeScript Root-Konfiguration
  const tsConfig = {
    compileOnSave: false,
    compilerOptions: {
      rootDir: '.',
      sourceMap: true,
      declaration: false,
      moduleResolution: 'node',
      emitDecoratorMetadata: true,
      experimentalDecorators: true,
      importHelpers: true,
      target: 'ES2022',
      module: 'ESNext',
      lib: ['ES2022', 'dom'],
      skipLibCheck: true,
      skipDefaultLibCheck: true,
      baseUrl: '.',
      paths: {
        '@workspace/shared-models': ['libs/shared/models/src/index.ts'],
        '@workspace/shared-utils': ['libs/shared/utils/src/index.ts'],
        '@workspace/shared-types': ['libs/shared/types/src/index.ts'],
      },
    },
    exclude: ['node_modules', 'tmp'],
  };
  
  tree.write('tsconfig.base.json', JSON.stringify(tsConfig, null, 2));
}

async function generateBackend(tree: Tree, options: NormalizedSchema) {
  // NestJS-Anwendung mit moderner Konfiguration erstellen
  const backendFiles = joinPathFragments(__dirname, 'files/backend');
  
  generateFiles(tree, backendFiles, `apps/${options.backendProjectName}`, {
    ...options,
    template: '',
  });
  
  // Backend-spezifische package.json
  const backendPackageJson = {
    name: options.backendProjectName,
    version: '0.0.1',
    scripts: {
      'prebuild': 'rimraf dist',
      'build': 'nest build',
      'format': 'prettier --write "src/**/*.ts" "test/**/*.ts"',
      'start': 'nest start',
      'start:dev': 'nest start --watch',
      'start:debug': 'nest start --debug --watch',
      'start:prod': 'node dist/main',
      'lint': 'eslint "{src,apps,libs,test}/**/*.ts" --fix',
      'test': 'jest',
      'test:watch': 'jest --watch',
      'test:cov': 'jest --coverage',
      'test:debug': 'node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand',
      'test:e2e': 'jest --config ./test/jest-e2e.json',
    },
    dependencies: {
      '@nestjs/common': '^10.0.0',
      '@nestjs/core': '^10.0.0',
      '@nestjs/platform-express': '^10.0.0',
      '@nestjs/swagger': '^7.0.0',
      '@nestjs/typeorm': '^10.0.0',
      '@nestjs/config': '^3.0.0',
      '@nestjs/jwt': '^10.0.0',
      'class-validator': '^0.14.0',
      'class-transformer': '^0.5.1',
      'typeorm': '^0.3.0',
      'pg': '^8.11.0',
      'reflect-metadata': '^0.1.13',
      'rxjs': '^7.8.0',
    },
    devDependencies: {
      '@nestjs/cli': '^10.0.0',
      '@nestjs/schematics': '^10.0.0',
      '@nestjs/testing': '^10.0.0',
      '@types/express': '^4.17.17',
      '@types/jest': '^29.5.2',
      '@types/node': '^20.3.1',
      '@types/supertest': '^2.0.12',
      '@typescript-eslint/eslint-plugin': '^5.59.11',
      '@typescript-eslint/parser': '^5.59.11',
      'eslint': '^8.42.0',
      'eslint-config-prettier': '^8.8.0',
      'eslint-plugin-prettier': '^4.2.1',
      'jest': '^29.5.0',
      'prettier': '^2.8.8',
      'source-map-support': '^0.5.21',
      'supertest': '^6.3.3',
      'ts-jest': '^29.1.0',
      'ts-loader': '^9.4.3',
      'ts-node': '^10.9.1',
      'tsconfig-paths': '^4.2.0',
      'typescript': '^5.1.3',
    },
  };
  
  tree.write(`apps/${options.backendProjectName}/package.json`, JSON.stringify(backendPackageJson, null, 2));
  
  // NestJS Main Application File
  const mainTs = `
import { NestFactory } from '@nestjs/core';
import { ValidationPipe, Logger } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app/app.module';

async function bootstrap() {
  const logger = new Logger('Bootstrap');
  
  const app = await NestFactory.create(AppModule, {
    logger: ['log', 'error', 'warn', 'debug', 'verbose'],
  });

  // Global Configuration
  app.setGlobalPrefix('api');
  
  // CORS Configuration for Angular Frontend
  app.enableCors({
    origin: process.env.FRONTEND_URL || 'http://localhost:4200',
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
    allowedHeaders: ['Content-Type', 'Authorization'],
  });

  // Global Validation Pipe
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      whitelist: true,
      forbidNonWhitelisted: true,
      disableErrorMessages: process.env.NODE_ENV === 'production',
    }),
  );

  // Swagger Documentation Setup
  if (process.env.NODE_ENV !== 'production') {
    const config = new DocumentBuilder()
      .setTitle('${options.projectName} API')
      .setDescription('Full-Stack TypeScript API with Angular Frontend')
      .setVersion('1.0')
      .addBearerAuth()
      .addTag('auth', 'Authentication endpoints')
      .addTag('users', 'User management')
      .addTag('health', 'Health check endpoints')
      .build();
      
    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('api/docs', app, document);
    
    logger.log('Swagger documentation available at /api/docs');
  }

  const port = process.env.PORT || 3333;
  await app.listen(port);
  
  logger.log(\`🚀 Backend is running on: http://localhost:\${port}/api\`);
  logger.log(\`📖 Swagger docs available at: http://localhost:\${port}/api/docs\`);
}

bootstrap().catch(err => {
  console.error('Error starting server:', err);
  process.exit(1);
});
`;
  
  tree.write(`apps/${options.backendProjectName}/src/main.ts`, mainTs);
}

async function generateFrontend(tree: Tree, options: NormalizedSchema) {
  // Angular-Anwendung mit moderner Konfiguration erstellen
  const frontendFiles = joinPathFragments(__dirname, 'files/frontend');
  
  generateFiles(tree, frontendFiles, `apps/${options.frontendProjectName}`, {
    ...options,
    template: '',
  });
  
  // Angular-spezifische package.json
  const frontendPackageJson = {
    name: options.frontendProjectName,
    version: '0.0.1',
    scripts: {
      'ng': 'ng',
      'start': 'ng serve',
      'build': 'ng build',
      'build:prod': 'ng build --configuration production',
      'watch': 'ng build --watch --configuration development',
      'test': 'ng test',
      'test:ci': 'ng test --watch=false --browsers=ChromeHeadless',
      'lint': 'ng lint',
      'e2e': 'ng e2e',
    },
    dependencies: {
      '@angular/animations': '^16.0.0',
      '@angular/common': '^16.0.0',
      '@angular/compiler': '^16.0.0',
      '@angular/core': '^16.0.0',
      '@angular/forms': '^16.0.0',
      '@angular/platform-browser': '^16.0.0',
      '@angular/platform-browser-dynamic': '^16.0.0',
      '@angular/router': '^16.0.0',
      '@angular/material': '^16.0.0',
      '@angular/cdk': '^16.0.0',
      'rxjs': '^7.8.0',
      'tslib': '^2.3.0',
      'zone.js': '^0.13.0',
    },
    devDependencies: {
      '@angular-devkit/build-angular': '^16.0.0',
      '@angular/cli': '^16.0.0',
      '@angular/compiler-cli': '^16.0.0',
      '@types/jasmine': '^4.3.0',
      '@types/node': '^20.3.1',
      'jasmine-core': '^4.6.0',
      'karma': '^6.4.0',
      'karma-chrome-launcher': '^3.2.0',
      'karma-coverage': '^2.2.0',
      'karma-jasmine': '^5.1.0',
      'karma-jasmine-html-reporter': '^2.1.0',
      'typescript': '^5.1.3',
    },
  };
  
  tree.write(`apps/${options.frontendProjectName}/package.json`, JSON.stringify(frontendPackageJson, null, 2));
  
  // Angular App Module mit API-Integration
  const appModuleTs = `
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ReactiveFormsModule } from '@angular/forms';

// Angular Material
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

// Application Components
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

// Interceptors
import { ApiInterceptor } from './core/interceptors/api.interceptor';
import { AuthInterceptor } from './core/interceptors/auth.interceptor';
import { ErrorInterceptor } from './core/interceptors/error.interceptor';

// Core Services
import { ApiService } from './core/services/api.service';
import { AuthService } from './core/services/auth.service';
import { NotificationService } from './core/services/notification.service';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    ReactiveFormsModule,
    AppRoutingModule,
    
    // Angular Material Modules
    MatToolbarModule,
    MatButtonModule,
    MatCardModule,
    MatInputModule,
    MatSnackBarModule,
    MatProgressSpinnerModule,
  ],
  providers: [
    // Core Services
    ApiService,
    AuthService,
    NotificationService,
    
    // HTTP Interceptors
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ApiInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ErrorInterceptor,
      multi: true,
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
`;
  
  tree.write(`apps/${options.frontendProjectName}/src/app/app.module.ts`, appModuleTs);
}

async function generateSharedLibraries(tree: Tree, options: NormalizedSchema) {
  // Shared Models Library
  await generateSharedModelsLibrary(tree, options);
  
  // Shared Utils Library
  await generateSharedUtilsLibrary(tree, options);
  
  // Shared Types Library
  await generateSharedTypesLibrary(tree, options);
}

async function generateSharedModelsLibrary(tree: Tree, options: NormalizedSchema) {
  const modelsPath = 'libs/shared/models/src/lib';
  
  // Base Entity für alle Models
  const baseEntityTs = `
import { ApiProperty } from '@nestjs/swagger';
import { IsUUID, IsDate } from 'class-validator';
import { Type, Exclude } from 'class-transformer';

export abstract class BaseEntity {
  @ApiProperty({ description: 'Unique identifier', example: '123e4567-e89b-12d3-a456-426614174000' })
  @IsUUID()
  id: string;

  @ApiProperty({ description: 'Creation timestamp' })
  @IsDate()
  @Type(() => Date)
  createdAt: Date;

  @ApiProperty({ description: 'Last update timestamp' })
  @IsDate()
  @Type(() => Date)
  updatedAt: Date;

  @Exclude()
  deletedAt?: Date;
}
`;
  
  tree.write(`${modelsPath}/base.entity.ts`, baseEntityTs);
  
  // User Model
  const userModelTs = `
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsString, IsOptional, IsEnum, IsBoolean, MinLength, MaxLength } from 'class-validator';
import { BaseEntity } from './base.entity';

export enum UserRole {
  ADMIN = 'admin',
  USER = 'user',
  MODERATOR = 'moderator',
}

export enum UserStatus {
  ACTIVE = 'active',
  INACTIVE = 'inactive',
  SUSPENDED = 'suspended',
}

export class User extends BaseEntity {
  @ApiProperty({ description: 'User email address', example: 'user@example.com' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'User first name', example: 'John' })
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  firstName: string;

  @ApiProperty({ description: 'User last name', example: 'Doe' })
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  lastName: string;

  @ApiProperty({ description: 'User role', enum: UserRole, example: UserRole.USER })
  @IsEnum(UserRole)
  role: UserRole;

  @ApiProperty({ description: 'User status', enum: UserStatus, example: UserStatus.ACTIVE })
  @IsEnum(UserStatus)
  status: UserStatus;

  @ApiProperty({ description: 'User profile picture URL', required: false })
  @IsOptional()
  @IsString()
  profilePictureUrl?: string;

  @ApiProperty({ description: 'Whether user email is verified' })
  @IsBoolean()
  emailVerified: boolean;

  @ApiProperty({ description: 'Last login timestamp', required: false })
  @IsOptional()
  lastLoginAt?: Date;

  // Virtual properties for computed values
  get fullName(): string {
    return \`\${this.firstName} \${this.lastName}\`;
  }

  get isAdmin(): boolean {
    return this.role === UserRole.ADMIN;
  }
}
`;
  
  tree.write(`${modelsPath}/user.model.ts`, userModelTs);
  
  // DTOs für API-Kommunikation
  const createUserDtoTs = `
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsString, IsOptional, IsEnum, MinLength, MaxLength, Matches } from 'class-validator';
import { UserRole } from './user.model';

export class CreateUserDto {
  @ApiProperty({ description: 'User email address', example: 'user@example.com' })
  @IsEmail({}, { message: 'Email must be a valid email address' })
  email: string;

  @ApiProperty({ description: 'User password', example: 'SecurePassword123!' })
  @IsString()
  @MinLength(8, { message: 'Password must be at least 8 characters long' })
  @MaxLength(128, { message: 'Password must not exceed 128 characters' })
  @Matches(
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
    { message: 'Password must contain uppercase, lowercase, number and special character' }
  )
  password: string;

  @ApiProperty({ description: 'User first name', example: 'John' })
  @IsString()
  @MinLength(2, { message: 'First name must be at least 2 characters long' })
  @MaxLength(50, { message: 'First name must not exceed 50 characters' })
  firstName: string;

  @ApiProperty({ description: 'User last name', example: 'Doe' })
  @IsString()
  @MinLength(2, { message: 'Last name must be at least 2 characters long' })
  @MaxLength(50, { message: 'Last name must not exceed 50 characters' })
  lastName: string;

  @ApiProperty({ description: 'User role', enum: UserRole, required: false, default: UserRole.USER })
  @IsOptional()
  @IsEnum(UserRole, { message: 'Role must be a valid user role' })
  role?: UserRole = UserRole.USER;

  @ApiProperty({ description: 'User profile picture URL', required: false })
  @IsOptional()
  @IsString()
  profilePictureUrl?: string;
}

export class UpdateUserDto {
  @ApiProperty({ description: 'User first name', example: 'John', required: false })
  @IsOptional()
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  firstName?: string;

  @ApiProperty({ description: 'User last name', example: 'Doe', required: false })
  @IsOptional()
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  lastName?: string;

  @ApiProperty({ description: 'User profile picture URL', required: false })
  @IsOptional()
  @IsString()
  profilePictureUrl?: string;
}

export class UserResponseDto {
  @ApiProperty()
  id: string;

  @ApiProperty()
  email: string;

  @ApiProperty()
  firstName: string;

  @ApiProperty()
  lastName: string;

  @ApiProperty({ enum: UserRole })
  role: UserRole;

  @ApiProperty()
  emailVerified: boolean;

  @ApiProperty({ required: false })
  profilePictureUrl?: string;

  @ApiProperty()
  createdAt: Date;

  @ApiProperty()
  updatedAt: Date;

  @ApiProperty({ required: false })
  lastLoginAt?: Date;

  // Computed properties
  @ApiProperty()
  fullName: string;

  @ApiProperty()
  isAdmin: boolean;
}
`;
  
  tree.write(`${modelsPath}/user.dto.ts`, createUserDtoTs);
  
  // Index file für einfache Imports
  const indexTs = `
export * from './base.entity';
export * from './user.model';
export * from './user.dto';
`;
  
  tree.write('libs/shared/models/src/index.ts', indexTs);
}

async function generateSharedUtilsLibrary(tree: Tree, options: NormalizedSchema) {
  const utilsPath = 'libs/shared/utils/src/lib';
  
  // API Response Utilities
  const apiResponseUtilsTs = `
export interface ApiResponse<T = any> {
  success: boolean;
  data?: T;
  message?: string;
  errors?: string[];
  meta?: {
    pagination?: PaginationMeta;
    timestamp: string;
    version: string;
  };
}

export interface PaginationMeta {
  page: number;
  limit: number;
  total: number;
  totalPages: number;
  hasNext: boolean;
  hasPrev: boolean;
}

export interface PaginationParams {
  page?: number;
  limit?: number;
  sortBy?: string;
  sortOrder?: 'ASC' | 'DESC';
  search?: string;
}

export class ApiResponseUtil {
  static success<T>(data: T, message?: string, meta?: any): ApiResponse<T> {
    return {
      success: true,
      data,
      message,
      meta: {
        ...meta,
        timestamp: new Date().toISOString(),
        version: '1.0',
      },
    };
  }

  static error(message: string, errors?: string[]): ApiResponse {
    return {
      success: false,
      message,
      errors,
      meta: {
        timestamp: new Date().toISOString(),
        version: '1.0',
      },
    };
  }

  static paginated<T>(
    data: T[],
    pagination: PaginationParams,
    total: number
  ): ApiResponse<T[]> {
    const page = pagination.page || 1;
    const limit = pagination.limit || 10;
    const totalPages = Math.ceil(total / limit);

    const paginationMeta: PaginationMeta = {
      page,
      limit,
      total,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };

    return this.success(data, 'Data retrieved successfully', { pagination: paginationMeta });
  }
}
`;
  
  tree.write(`${utilsPath}/api-response.util.ts`, apiResponseUtilsTs);
  
  // Validation Utilities
  const validationUtilsTs = `
import { ValidationError } from 'class-validator';

export class ValidationUtil {
  static formatValidationErrors(errors: ValidationError[]): string[] {
    const formattedErrors: string[] = [];

    const formatError = (error: ValidationError, parentPath = '') => {
      const propertyPath = parentPath ? \`\${parentPath}.\${error.property}\` : error.property;

      if (error.constraints) {
        Object.values(error.constraints).forEach(constraint => {
          formattedErrors.push(\`\${propertyPath}: \${constraint}\`);
        });
      }

      if (error.children && error.children.length > 0) {
        error.children.forEach(child => formatError(child, propertyPath));
      }
    };

    errors.forEach(error => formatError(error));
    return formattedErrors;
  }

  static isValidEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  static isValidPassword(password: string): {
    isValid: boolean;
    errors: string[];
  } {
    const errors: string[] = [];

    if (password.length < 8) {
      errors.push('Password must be at least 8 characters long');
    }

    if (!/[A-Z]/.test(password)) {
      errors.push('Password must contain at least one uppercase letter');
    }

    if (!/[a-z]/.test(password)) {
      errors.push('Password must contain at least one lowercase letter');
    }

    if (!/\d/.test(password)) {
      errors.push('Password must contain at least one number');
    }

    if (!/[@$!%*?&]/.test(password)) {
      errors.push('Password must contain at least one special character');
    }

    return {
      isValid: errors.length === 0,
      errors,
    };
  }
}
`;
  
  tree.write(`${utilsPath}/validation.util.ts`, validationUtilsTs);
  
  // Date Utilities
  const dateUtilsTs = `
export class DateUtil {
  static formatDate(date: Date | string, format: 'short' | 'long' | 'iso' = 'short'): string {
    const dateObj = typeof date === 'string' ? new Date(date) : date;

    switch (format) {
      case 'short':
        return dateObj.toLocaleDateString();
      case 'long':
        return dateObj.toLocaleDateString('en-US', {
          year: 'numeric',
          month: 'long',
          day: 'numeric',
          hour: '2-digit',
          minute: '2-digit',
        });
      case 'iso':
        return dateObj.toISOString();
      default:
        return dateObj.toLocaleDateString();
    }
  }

  static isToday(date: Date | string): boolean {
    const dateObj = typeof date === 'string' ? new Date(date) : date;
    const today = new Date();
    
    return dateObj.toDateString() === today.toDateString();
  }

  static daysSince(date: Date | string): number {
    const dateObj = typeof date === 'string' ? new Date(date) : date;
    const today = new Date();
    const diffTime = Math.abs(today.getTime() - dateObj.getTime());
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  }

  static addDays(date: Date | string, days: number): Date {
    const dateObj = typeof date === 'string' ? new Date(date) : new Date(date);
    dateObj.setDate(dateObj.getDate() + days);
    return dateObj;
  }
}
`;
  
  tree.write(`${utilsPath}/date.util.ts`, dateUtilsTs);
  
  // Index file
  const indexTs = `
export * from './api-response.util';
export * from './validation.util';
export * from './date.util';
`;
  
  tree.write('libs/shared/utils/src/index.ts', indexTs);
}

async function configureDevTools(tree: Tree, options: NormalizedSchema) {
  // ESLint Konfiguration für das gesamte Workspace
  const eslintConfig = {
    root: true,
    ignorePatterns: ['**/*'],
    plugins: ['@nrwl/nx'],
    overrides: [
      {
        files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
        rules: {
          '@nrwl/nx/enforce-module-boundaries': [
            'error',
            {
              enforceBuildableLibDependency: true,
              allow: [],
              depConstraints: [
                {
                  sourceTag: '*',
                  onlyDependOnLibsWithTags: ['*'],
                },
              ],
            },
          ],
        },
      },
      {
        files: ['*.ts', '*.tsx'],
        extends: ['@nrwl/eslint-plugin-nx/typescript'],
        rules: {
          '@typescript-eslint/no-unused-vars': 'error',
          '@typescript-eslint/explicit-function-return-type': 'warn',
          '@typescript-eslint/no-explicit-any': 'warn',
        },
      },
      {
        files: ['*.js', '*.jsx'],
        extends: ['@nrwl/eslint-plugin-nx/javascript'],
        rules: {},
      },
    ],
  };
  
  tree.write('.eslintrc.json', JSON.stringify(eslintConfig, null, 2));
  
  // Prettier Konfiguration
  const prettierConfig = {
    semi: true,
    trailingComma: 'es5',
    singleQuote: true,
    printWidth: 100,
    tabWidth: 2,
    useTabs: false,
  };
  
  tree.write('.prettierrc', JSON.stringify(prettierConfig, null, 2));
  
  // Husky Pre-commit Hooks
  const huskyPreCommit = `#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
`;
  
  tree.write('.husky/pre-commit', huskyPreCommit);
  
  // Lint-staged Konfiguration
  const lintStagedConfig = {
    '{apps,libs}/**/*.{ts,tsx,js,jsx}': [
      'nx affected:lint --fix --uncommitted',
      'nx format:write --uncommitted',
    ],
    '{apps,libs}/**/*.{json,md,html,css,scss}': ['nx format:write --uncommitted'],
  };
  
  tree.write('.lintstagedrc.json', JSON.stringify(lintStagedConfig, null, 2));
}

function normalizeOptions(tree: Tree, options: FullStackAppGeneratorSchema): NormalizedSchema {
  const projectName = options.name;
  const projectRoot = `apps/${projectName}`;
  
  return {
    ...options,
    projectName,
    projectRoot,
    backendProjectName: `${projectName}-backend`,
    frontendProjectName: `${projectName}-frontend`,
    sharedLibName: `${projectName}-shared`,
  };
}

Diese umfassende Setup-Strategie schafft die Grundlage für eine hochintegrierte Angular-NestJS-Anwendung. Der Generator erstellt nicht nur die grundlegende Projektstruktur, sondern konfiguriert auch alle notwendigen Tools für moderne Full-Stack-Entwicklung: TypeScript-Konfiguration, ESLint, Prettier, Husky-Hooks und eine konsistente Package-Management-Struktur.

Der Schlüssel liegt in der gemeinsamen TypeScript-Konfiguration und den geteilten Bibliotheken. Durch die Verwendung von Path Mapping in der tsconfig.base.json können sowohl Angular als auch NestJS auf dieselben Typen, Models und Utilities zugreifen. Dies eliminiert Code-Duplizierung und stellt sicher, dass Änderungen an geteilten Ressourcen automatisch in beiden Anwendungen reflektiert werden.

32.11 Shared Types und Interfaces

Shared Types und Interfaces sind das Rückgrat einer typesicheren Full-Stack-Anwendung. Stellen Sie sich vor, Sie hätten einen universellen Übersetzer, der nicht nur zwischen verschiedenen Sprachen übersetzt, sondern auch sicherstellt, dass die Bedeutung niemals verloren geht. Genau das leisten geteilte Types in einer Angular-NestJS-Architektur - sie schaffen eine gemeinsame Sprache zwischen Frontend und Backend, die Kompilierzeit-Sicherheit bietet und Laufzeitfehler verhindert.

Der traditionelle Ansatz der Web-Entwicklung führt oft zu einem frustrierenden Muster: Sie definieren ein User-Interface im Backend, erstellen ein separates Interface im Frontend und hoffen, dass beide synchron bleiben. Wenn sich die Backend-API ändert, müssen Sie manuell das entsprechende Frontend-Interface aktualisieren. Dieser Prozess ist fehleranfällig und wird mit wachsender Anwendungskomplexität exponentiell schwieriger.

Mit geteilten Types und Interfaces wird diese Herausforderung elegant gelöst. Sie definieren Ihre Datenstrukturen einmal und verwenden sie überall. Wenn Sie eine neue Eigenschaft zu einem User-Model hinzufügen, wird diese Änderung automatisch sowohl im Backend als auch im Frontend verfügbar. TypeScript’s Compiler wird Sie warnen, wenn Sie versuchen, auf nicht existierende Eigenschaften zuzugreifen oder inkompatible Typen zu verwenden.

// libs/shared/types/src/lib/api-contracts.ts
// Zentrale Definition aller API-Contracts zwischen Frontend und Backend
import { User, UserRole, UserStatus } from '@workspace/shared-models';
import { PaginationParams, PaginationMeta } from '@workspace/shared-utils';

// Authentication Contracts
export namespace AuthContracts {
  export interface LoginRequest {
    email: string;
    password: string;
    rememberMe?: boolean;
  }

  export interface LoginResponse {
    accessToken: string;
    refreshToken: string;
    user: User;
    expiresIn: number;
  }

  export interface RefreshTokenRequest {
    refreshToken: string;
  }

  export interface RefreshTokenResponse {
    accessToken: string;
    expiresIn: number;
  }

  export interface PasswordResetRequest {
    email: string;
  }

  export interface PasswordResetConfirmRequest {
    token: string;
    newPassword: string;
  }

  export interface ChangePasswordRequest {
    currentPassword: string;
    newPassword: string;
  }
}

// User Management Contracts
export namespace UserContracts {
  export interface GetUsersRequest extends PaginationParams {
    role?: UserRole;
    status?: UserStatus;
    search?: string;
    sortBy?: 'createdAt' | 'email' | 'firstName' | 'lastName' | 'lastLoginAt';
    sortOrder?: 'ASC' | 'DESC';
  }

  export interface GetUsersResponse {
    users: User[];
    pagination: PaginationMeta;
  }

  export interface GetUserRequest {
    id: string;
  }

  export interface GetUserResponse {
    user: User;
  }

  export interface CreateUserRequest {
    email: string;
    password: string;
    firstName: string;
    lastName: string;
    role?: UserRole;
    profilePictureUrl?: string;
    sendWelcomeEmail?: boolean;
  }

  export interface CreateUserResponse {
    user: User;
    temporaryPassword?: string; // Nur wenn kein Passwort gesetzt wurde
  }

  export interface UpdateUserRequest {
    id: string;
    firstName?: string;
    lastName?: string;
    profilePictureUrl?: string;
    role?: UserRole;
    status?: UserStatus;
  }

  export interface UpdateUserResponse {
    user: User;
  }

  export interface DeleteUserRequest {
    id: string;
    transferDataToUserId?: string; // Für Datenübertragung vor Löschung
  }

  export interface BulkUserAction {
    userIds: string[];
    action: 'activate' | 'deactivate' | 'suspend' | 'delete';
    reason?: string;
  }

  export interface BulkUserActionResponse {
    successCount: number;
    failureCount: number;
    errors: Array<{
      userId: string;
      error: string;
    }>;
  }

  export interface UserActivityRequest {
    userId: string;
    limit?: number;
    offset?: number;
  }

  export interface UserActivity {
    id: string;
    action: string;
    details: Record<string, any>;
    ipAddress: string;
    userAgent: string;
    timestamp: Date;
  }

  export interface UserActivityResponse {
    activities: UserActivity[];
    totalCount: number;
  }
}

// File Upload Contracts
export namespace FileContracts {
  export interface UploadFileRequest {
    file: File; // Frontend only - wird zu FormData konvertiert
    category?: 'profile' | 'document' | 'image';
    isPublic?: boolean;
  }

  export interface UploadFileResponse {
    fileId: string;
    fileName: string;
    originalName: string;
    mimeType: string;
    size: number;
    url: string;
    thumbnailUrl?: string;
  }

  export interface GetFileRequest {
    fileId: string;
  }

  export interface GetFileResponse {
    fileId: string;
    fileName: string;
    originalName: string;
    mimeType: string;
    size: number;
    url: string;
    downloadUrl: string;
    thumbnailUrl?: string;
    uploadedBy: string;
    uploadedAt: Date;
    isPublic: boolean;
  }

  export interface DeleteFileRequest {
    fileId: string;
  }
}

// Notification Contracts
export namespace NotificationContracts {
  export interface GetNotificationsRequest extends PaginationParams {
    isRead?: boolean;
    type?: NotificationType;
  }

  export enum NotificationType {
    INFO = 'info',
    WARNING = 'warning',
    ERROR = 'error',
    SUCCESS = 'success',
    SYSTEM = 'system',
  }

  export interface Notification {
    id: string;
    title: string;
    message: string;
    type: NotificationType;
    isRead: boolean;
    actionUrl?: string;
    actionText?: string;
    createdAt: Date;
    readAt?: Date;
  }

  export interface GetNotificationsResponse {
    notifications: Notification[];
    pagination: PaginationMeta;
    unreadCount: number;
  }

  export interface MarkNotificationReadRequest {
    notificationId: string;
  }

  export interface MarkAllNotificationsReadRequest {
    // Keine Parameter - markiert alle als gelesen
  }

  export interface CreateNotificationRequest {
    userId: string;
    title: string;
    message: string;
    type: NotificationType;
    actionUrl?: string;
    actionText?: string;
  }
}

// WebSocket Event Contracts
export namespace WebSocketContracts {
  export interface ServerToClientEvents {
    'user-updated': (data: { user: User }) => void;
    'notification-received': (data: { notification: NotificationContracts.Notification }) => void;
    'user-online': (data: { userId: string; timestamp: Date }) => void;
    'user-offline': (data: { userId: string; timestamp: Date }) => void;
    'system-maintenance': (data: { message: string; maintenanceAt: Date }) => void;
  }

  export interface ClientToServerEvents {
    'join-room': (data: { roomId: string }) => void;
    'leave-room': (data: { roomId: string }) => void;
    'mark-notification-read': (data: { notificationId: string }) => void;
    'user-typing': (data: { roomId: string; isTyping: boolean }) => void;
  }

  export interface SocketData {
    userId: string;
    sessionId: string;
  }
}

// Error Contracts
export namespace ErrorContracts {
  export enum ErrorCode {
    // Authentication Errors
    INVALID_CREDENTIALS = 'INVALID_CREDENTIALS',
    TOKEN_EXPIRED = 'TOKEN_EXPIRED',
    TOKEN_INVALID = 'TOKEN_INVALID',
    UNAUTHORIZED = 'UNAUTHORIZED',
    FORBIDDEN = 'FORBIDDEN',
    
    // Validation Errors
    VALIDATION_FAILED = 'VALIDATION_FAILED',
    INVALID_EMAIL = 'INVALID_EMAIL',
    WEAK_PASSWORD = 'WEAK_PASSWORD',
    
    // Resource Errors
    USER_NOT_FOUND = 'USER_NOT_FOUND',
    EMAIL_ALREADY_EXISTS = 'EMAIL_ALREADY_EXISTS',
    FILE_NOT_FOUND = 'FILE_NOT_FOUND',
    RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
    
    // System Errors
    INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
    DATABASE_ERROR = 'DATABASE_ERROR',
    FILE_UPLOAD_ERROR = 'FILE_UPLOAD_ERROR',
    RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
  }

  export interface ApiError {
    code: ErrorCode;
    message: string;
    details?: Record<string, any>;
    timestamp: string;
    path: string;
    method: string;
    userMessage?: string; // Benutzerfreundliche Nachricht
  }

  export interface ValidationError {
    field: string;
    message: string;
    code: string;
    value?: any;
  }

  export interface ApiErrorResponse {
    success: false;
    error: ApiError;
    validationErrors?: ValidationError[];
    meta: {
      timestamp: string;
      version: string;
      requestId: string;
    };
  }
}

// Health Check Contracts
export namespace HealthContracts {
  export interface HealthCheckResponse {
    status: 'ok' | 'error' | 'shutting_down';
    info?: Record<string, any>;
    error?: Record<string, any>;
    details: Record<string, any>;
    timestamp: string;
  }

  export interface DatabaseHealthInfo {
    status: 'up' | 'down';
    responseTime: number;
    connections: {
      active: number;
      idle: number;
      total: number;
    };
  }

  export interface RedisHealthInfo {
    status: 'up' | 'down';
    responseTime: number;
    memory: {
      used: number;
      total: number;
      percentage: number;
    };
  }

  export interface SystemHealthInfo {
    uptime: number;
    memory: {
      used: number;
      total: number;
      percentage: number;
    };
    cpu: {
      usage: number;
      loadAverage: number[];
    };
  }
}

Diese umfassende Sammlung von API-Contracts dient als Single Source of Truth für die Kommunikation zwischen Angular und NestJS. Jeder Contract definiert nicht nur die Datenstruktur, sondern auch die erwarteten Request- und Response-Formate. Dies ermöglicht es beiden Seiten der Anwendung, sich auf konsistente Interfaces zu verlassen.

Die Verwendung von Namespaces organisiert die Contracts logisch und verhindert Namenskonflikte. Jeder Namespace gruppiert verwandte Operationen und macht es einfach, die richtigen Types für spezifische API-Aufrufe zu finden. Die Kombination aus Enums für konstante Werte und Interfaces für Datenstrukturen schafft ein robustes Typsystem, das sowohl zur Compile-Zeit als auch zur Laufzeit Sicherheit bietet.

// libs/shared/types/src/lib/type-guards.ts
// Type Guards für Runtime-Type-Checking
import { User, UserRole, UserStatus } from '@workspace/shared-models';
import { AuthContracts, UserContracts, ErrorContracts } from './api-contracts';

/**
 * Type Guards ermöglichen Runtime-Type-Checking für TypeScript
 * Sie sind essentiell für die sichere Verarbeitung von API-Responses
 */

// User Type Guards
export function isUser(obj: any): obj is User {
  return obj &&
    typeof obj.id === 'string' &&
    typeof obj.email === 'string' &&
    typeof obj.firstName === 'string' &&
    typeof obj.lastName === 'string' &&
    Object.values(UserRole).includes(obj.role) &&
    Object.values(UserStatus).includes(obj.status) &&
    typeof obj.emailVerified === 'boolean';
}

export function isUserArray(obj: any): obj is User[] {
  return Array.isArray(obj) && obj.every(isUser);
}

// Authentication Type Guards
export function isLoginResponse(obj: any): obj is AuthContracts.LoginResponse {
  return obj &&
    typeof obj.accessToken === 'string' &&
    typeof obj.refreshToken === 'string' &&
    isUser(obj.user) &&
    typeof obj.expiresIn === 'number';
}

export function isRefreshTokenResponse(obj: any): obj is AuthContracts.RefreshTokenResponse {
  return obj &&
    typeof obj.accessToken === 'string' &&
    typeof obj.expiresIn === 'number';
}

// Error Type Guards
export function isApiError(obj: any): obj is ErrorContracts.ApiError {
  return obj &&
    Object.values(ErrorContracts.ErrorCode).includes(obj.code) &&
    typeof obj.message === 'string' &&
    typeof obj.timestamp === 'string' &&
    typeof obj.path === 'string' &&
    typeof obj.method === 'string';
}

export function isApiErrorResponse(obj: any): obj is ErrorContracts.ApiErrorResponse {
  return obj &&
    obj.success === false &&
    isApiError(obj.error);
}

// Pagination Type Guards
export function isPaginationMeta(obj: any): obj is import('@workspace/shared-utils').PaginationMeta {
  return obj &&
    typeof obj.page === 'number' &&
    typeof obj.limit === 'number' &&
    typeof obj.total === 'number' &&
    typeof obj.totalPages === 'number' &&
    typeof obj.hasNext === 'boolean' &&
    typeof obj.hasPrev === 'boolean';
}

// Generic API Response Type Guard
export function isSuccessApiResponse<T>(
  obj: any,
  dataValidator: (data: any) => data is T
): obj is import('@workspace/shared-utils').ApiResponse<T> {
  return obj &&
    obj.success === true &&
    obj.data !== undefined &&
    dataValidator(obj.data);
}

// Utility Type Guards
export function isStringArray(obj: any): obj is string[] {
  return Array.isArray(obj) && obj.every(item => typeof item === 'string');
}

export function isNumberArray(obj: any): obj is number[] {
  return Array.isArray(obj) && obj.every(item => typeof item === 'number');
}

export function isValidDate(obj: any): obj is Date {
  return obj instanceof Date && !isNaN(obj.getTime());
}

export function isValidDateString(obj: any): boolean {
  return typeof obj === 'string' && !isNaN(Date.parse(obj));
}

// Complex Type Guards for Nested Objects
export function isGetUsersResponse(obj: any): obj is UserContracts.GetUsersResponse {
  return obj &&
    isUserArray(obj.users) &&
    isPaginationMeta(obj.pagination);
}

export function isCreateUserResponse(obj: any): obj is UserContracts.CreateUserResponse {
  return obj &&
    isUser(obj.user) &&
    (obj.temporaryPassword === undefined || typeof obj.temporaryPassword === 'string');
}

// Generic Validator Creator
export function createArrayValidator<T>(itemValidator: (item: any) => item is T) {
  return (obj: any): obj is T[] => {
    return Array.isArray(obj) && obj.every(itemValidator);
  };
}

// Generic Object Validator Creator
export function createObjectValidator<T>(validators: Record<keyof T, (value: any) => boolean>) {
  return (obj: any): obj is T => {
    if (!obj || typeof obj !== 'object') return false;
    
    return Object.entries(validators).every(([key, validator]) => {
      return validator(obj[key]);
    });
  };
}

// Runtime Type Assertion Utilities
export class TypeAssertions {
  static assertUser(obj: any): User {
    if (!isUser(obj)) {
      throw new Error('Invalid User object');
    }
    return obj;
  }

  static assertUserArray(obj: any): User[] {
    if (!isUserArray(obj)) {
      throw new Error('Invalid User array');
    }
    return obj;
  }

  static assertApiResponse<T>(
    obj: any,
    dataValidator: (data: any) => data is T
  ): import('@workspace/shared-utils').ApiResponse<T> {
    if (!isSuccessApiResponse(obj, dataValidator)) {
      throw new Error('Invalid API response');
    }
    return obj;
  }
}

Type Guards sind ein kritischer Bestandteil einer typesicheren Full-Stack-Anwendung. Während TypeScript Compile-Time-Type-Safety bietet, können Type Guards Runtime-Type-Safety gewährleisten. Dies ist besonders wichtig bei der Verarbeitung von API-Responses, wo die Daten von externen Quellen stammen und nicht zur Compile-Zeit validiert werden können.

Die Kombination aus statischen Types und Runtime-Validierung schafft eine robuste Basis für die Kommunikation zwischen Angular und NestJS. Wenn eine API-Response nicht den erwarteten Typen entspricht, können Type Guards dies erkennen und entsprechende Fehlerbehandlung auslösen, anstatt dass die Anwendung mit unerwarteten Daten fehlschlägt.

32.12 API Contract Management

API Contract Management ist wie das Aufstellen von Verkehrsregeln für eine Stadt - es definiert, wie verschiedene Teile der Anwendung miteinander kommunizieren sollen, und stellt sicher, dass alle Beteiligten dieselben Regeln befolgen. In einer Angular-NestJS-Architektur wird API Contract Management zum Rückgrat einer wartbaren und skalierbaren Anwendung.

Der traditionelle Ansatz zur API-Entwicklung führt oft zu einem Henne-und-Ei-Problem: Soll das Frontend oder das Backend zuerst entwickelt werden? Wie stellen Sie sicher, dass beide Seiten dieselben Erwartungen an die API haben? Wie verwalten Sie Änderungen an der API, ohne dass Frontend und Backend auseinander driften?

API Contract Management löst diese Probleme durch die Definition von Contracts als First-Class Citizens in Ihrer Anwendungsarchitektur. Diese Contracts dienen als Schnittstelle zwischen Frontend und Backend und können unabhängig von beiden entwickelt, getestet und versioniert werden.

// libs/shared/api-contracts/src/lib/contract-manager.ts
// Zentrales Management aller API-Contracts mit Versionierung und Validierung
import { Type } from 'class-transformer';
import { IsEnum, IsOptional, IsString, IsNumber, ValidateNested } from 'class-validator';

export enum ApiVersion {
  V1 = 'v1',
  V2 = 'v2',
}

export enum HttpMethod {
  GET = 'GET',
  POST = 'POST',
  PUT = 'PUT',
  PATCH = 'PATCH',
  DELETE = 'DELETE',
}

export interface ApiEndpoint {
  path: string;
  method: HttpMethod;
  version: ApiVersion;
  requestType?: new () => any;
  responseType?: new () => any;
  description: string;
  tags: string[];
  deprecated?: boolean;
  authentication?: 'required' | 'optional' | 'none';
  rateLimit?: {
    requests: number;
    window: number; // seconds
  };
}

export interface ApiContract {
  name: string;
  version: ApiVersion;
  endpoints: ApiEndpoint[];
  models: Array<new () => any>;
  baseUrl: string;
  description: string;
}

export class ApiContractManager {
  private static contracts = new Map<string, ApiContract>();
  private static validators = new Map<string, (data: any) => boolean>();

  /**
   * Registriert einen API-Contract
   */
  static registerContract(contract: ApiContract): void {
    const contractKey = `${contract.name}-${contract.version}`;
    this.contracts.set(contractKey, contract);
    
    // Registriere Validators für alle Endpoints
    contract.endpoints.forEach(endpoint => {
      if (endpoint.requestType) {
        this.registerValidator(endpoint.requestType, endpoint.path, endpoint.method, 'request');
      }
      if (endpoint.responseType) {
        this.registerValidator(endpoint.responseType, endpoint.path, endpoint.method, 'response');
      }
    });
  }

  /**
   * Holt einen registrierten Contract
   */
  static getContract(name: string, version: ApiVersion): ApiContract | undefined {
    return this.contracts.get(`${name}-${version}`);
  }

  /**
   * Listet alle verfügbaren Contracts auf
   */
  static getAllContracts(): ApiContract[] {
    return Array.from(this.contracts.values());
  }

  /**
   * Validiert Request/Response-Daten gegen Contract
   */
  static validateData(
    path: string, 
    method: HttpMethod, 
    type: 'request' | 'response', 
    data: any
  ): { isValid: boolean; errors: string[] } {
    const validatorKey = `${path}-${method}-${type}`;
    const validator = this.validators.get(validatorKey);
    
    if (!validator) {
      return { isValid: true, errors: [] };
    }

    try {
      const isValid = validator(data);
      return { isValid, errors: isValid ? [] : ['Validation failed'] };
    } catch (error) {
      return { isValid: false, errors: [error.message] };
    }
  }

  /**
   * Generiert OpenAPI/Swagger-Dokumentation aus Contracts
   */
  static generateOpenApiSpec(contractName: string, version: ApiVersion): any {
    const contract = this.getContract(contractName, version);
    if (!contract) {
      throw new Error(`Contract ${contractName}-${version} not found`);
    }

    const openApiSpec = {
      openapi: '3.0.0',
      info: {
        title: contract.name,
        version: contract.version,
        description: contract.description,
      },
      servers: [
        {
          url: contract.baseUrl,
          description: 'API Server',
        },
      ],
      paths: {},
      components: {
        schemas: {},
        securitySchemes: {
          BearerAuth: {
            type: 'http',
            scheme: 'bearer',
            bearerFormat: 'JWT',
          },
        },
      },
    };

    // Generiere Paths aus Endpoints
    contract.endpoints.forEach(endpoint => {
      const pathKey = endpoint.path.replace(/:([^/]+)/g, '{$1}');
      
      if (!openApiSpec.paths[pathKey]) {
        openApiSpec.paths[pathKey] = {};
      }

      openApiSpec.paths[pathKey][endpoint.method.toLowerCase()] = {
        tags: endpoint.tags,
        summary: endpoint.description,
        deprecated: endpoint.deprecated || false,
        security: endpoint.authentication === 'required' ? [{ BearerAuth: [] }] : [],
        requestBody: endpoint.requestType ? {
          required: true,
          content: {
            'application/json': {
              schema: { $ref: `#/components/schemas/${endpoint.requestType.name}` },
            },
          },
        } : undefined,
        responses: {
          200: {
            description: 'Success',
            content: endpoint.responseType ? {
              'application/json': {
                schema: { $ref: `#/components/schemas/${endpoint.responseType.name}` },
              },
            } : undefined,
          },
          400: {
            description: 'Bad Request',
            content: {
              'application/json': {
                schema: { $ref: '#/components/schemas/ErrorResponse' },
              },
            },
          },
          401: {
            description: 'Unauthorized',
          },
          500: {
            description: 'Internal Server Error',
          },
        },
      };
    });

    return openApiSpec;
  }

  /**
   * Generiert TypeScript-Client-Code für Angular
   */
  static generateAngularClient(contractName: string, version: ApiVersion): string {
    const contract = this.getContract(contractName, version);
    if (!contract) {
      throw new Error(`Contract ${contractName}-${version} not found`);
    }

    let clientCode = `
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class ${this.toPascalCase(contractName)}Client {
  private readonly baseUrl = '${contract.baseUrl}';

  constructor(private http: HttpClient) {}

`;

    contract.endpoints.forEach(endpoint => {
      const methodName = this.generateMethodName(endpoint.path, endpoint.method);
      const parameters = this.extractPathParameters(endpoint.path);
      const hasBody = ['POST', 'PUT', 'PATCH'].includes(endpoint.method);
      
      clientCode += this.generateClientMethod(endpoint, methodName, parameters, hasBody);
    });

    clientCode += `
  private handleError(error: any): Observable<never> {
    console.error('API Error:', error);
    throw error;
  }

  private buildUrl(path: string, params: Record<string, any> = {}): string {
    let url = this.baseUrl + path;
    Object.keys(params).forEach(key => {
      url = url.replace(`:${key}`, params[key]);
    });
    return url;
  }
}
`;

    return clientCode;
  }

  /**
   * Generiert NestJS-Controller-Stubs aus Contracts
   */
  static generateNestJSController(contractName: string, version: ApiVersion): string {
    const contract = this.getContract(contractName, version);
    if (!contract) {
      throw new Error(`Contract ${contractName}-${version} not found`);
    }

    let controllerCode = `
import { Controller, Get, Post, Put, Patch, Delete, Body, Param, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';

@ApiTags('${contract.name}')
@Controller('${version}/${contract.name.toLowerCase()}')
export class ${this.toPascalCase(contractName)}Controller {

`;

    contract.endpoints.forEach(endpoint => {
      controllerCode += this.generateControllerMethod(endpoint);
    });

    controllerCode += '\n}';

    return controllerCode;
  }

  private static registerValidator(
    type: new () => any,
    path: string,
    method: HttpMethod,
    requestType: 'request' | 'response'
  ): void {
    const validatorKey = `${path}-${method}-${requestType}`;
    
    // Hier würden Sie eine echte Validierungslogik implementieren
    // Für diese Demo verwenden wir eine einfache Placeholder-Implementierung
    this.validators.set(validatorKey, (data: any) => {
      return data !== null && data !== undefined;
    });
  }

  private static toPascalCase(str: string): string {
    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
  }

  private static generateMethodName(path: string, method: HttpMethod): string {
    const cleanPath = path.replace(/[{}:]/g, '').replace(/\//g, '_');
    const methodPrefix = method.toLowerCase();
    return `${methodPrefix}${this.toPascalCase(cleanPath)}`;
  }

  private static extractPathParameters(path: string): string[] {
    const matches = path.match(/:([^/]+)/g);
    return matches ? matches.map(match => match.substring(1)) : [];
  }

  private static generateClientMethod(
    endpoint: ApiEndpoint,
    methodName: string,
    parameters: string[],
    hasBody: boolean
  ): string {
    const parameterList = parameters.length > 0 ? `${parameters.join(': string, ')}: string${hasBody ? ', body: any' : ''}` : hasBody ? 'body: any' : '';
    const pathParams = parameters.length > 0 ? `{ ${parameters.join(', ')} }` : '{}';
    
    return `
  ${methodName}(${parameterList}): Observable<any> {
    const url = this.buildUrl('${endpoint.path}', ${pathParams});
    ${hasBody 
      ? `return this.http.${endpoint.method.toLowerCase()}(url, body).pipe(catchError(this.handleError));`
      : `return this.http.${endpoint.method.toLowerCase()}(url).pipe(catchError(this.handleError));`
    }
  }
`;
  }

  private static generateControllerMethod(endpoint: ApiEndpoint): string {
    const decoratorMap = {
      GET: '@Get',
      POST: '@Post',
      PUT: '@Put',
      PATCH: '@Patch',
      DELETE: '@Delete',
    };

    const methodDecorator = decoratorMap[endpoint.method];
    const pathParams = this.extractPathParameters(endpoint.path);
    const methodName = this.generateMethodName(endpoint.path, endpoint.method);
    const hasBody = ['POST', 'PUT', 'PATCH'].includes(endpoint.method);

    let methodSignature = methodName + '(';
    const parameters: string[] = [];

    if (pathParams.length > 0) {
      pathParams.forEach(param => {
        parameters.push(`@Param('${param}') ${param}: string`);
      });
    }

    if (hasBody && endpoint.requestType) {
      parameters.push(`@Body() body: ${endpoint.requestType.name}`);
    }

    methodSignature += parameters.join(', ') + ')';

    const responseType = endpoint.responseType ? endpoint.responseType.name : 'any';

    return `
  @ApiOperation({ summary: '${endpoint.description}' })
  @ApiResponse({ status: 200, description: 'Success', type: ${responseType} })
  ${endpoint.authentication === 'required' ? '@ApiBearerAuth()' : ''}
  ${methodDecorator}('${endpoint.path.replace(/:/g, '')}')
  async ${methodSignature}: Promise<${responseType}> {
    // TODO: Implement ${methodName}
    throw new Error('Method not implemented');
  }
`;
  }
}

// Beispiel für Contract-Definition
export class UserApiContract {
  static register(): void {
    const contract: ApiContract = {
      name: 'users',
      version: ApiVersion.V1,
      baseUrl: '/api/v1',
      description: 'User management API',
      endpoints: [
        {
          path: '/users',
          method: HttpMethod.GET,
          version: ApiVersion.V1,
          responseType: class GetUsersResponse {},
          description: 'Get all users',
          tags: ['users'],
          authentication: 'required',
          rateLimit: { requests: 100, window: 60 },
        },
        {
          path: '/users/:id',
          method: HttpMethod.GET,
          version: ApiVersion.V1,
          responseType: class GetUserResponse {},
          description: 'Get user by ID',
          tags: ['users'],
          authentication: 'required',
        },
        {
          path: '/users',
          method: HttpMethod.POST,
          version: ApiVersion.V1,
          requestType: class CreateUserRequest {},
          responseType: class CreateUserResponse {},
          description: 'Create new user',
          tags: ['users'],
          authentication: 'required',
        },
        {
          path: '/users/:id',
          method: HttpMethod.PUT,
          version: ApiVersion.V1,
          requestType: class UpdateUserRequest {},
          responseType: class UpdateUserResponse {},
          description: 'Update user',
          tags: ['users'],
          authentication: 'required',
        },
        {
          path: '/users/:id',
          method: HttpMethod.DELETE,
          version: ApiVersion.V1,
          description: 'Delete user',
          tags: ['users'],
          authentication: 'required',
        },
      ],
      models: [],
    };

    ApiContractManager.registerContract(contract);
  }
}

API Contract Management ermöglicht es, die API als eigenständiges Artefakt zu behandeln, das unabhängig von Frontend und Backend entwickelt und versioniert werden kann. Dies schafft eine klare Trennung der Verantwortlichkeiten und ermöglicht es Teams, parallel zu arbeiten, ohne sich gegenseitig zu blockieren.

Die Fähigkeit, automatisch Client-Code für Angular und Controller-Stubs für NestJS zu generieren, reduziert nicht nur den Entwicklungsaufwand, sondern stellt auch sicher, dass beide Seiten der Anwendung konsistent implementiert sind. Wenn sich ein Contract ändert, können die entsprechenden Code-Generatoren ausgeführt werden, um sicherzustellen, dass Frontend und Backend synchron bleiben.

Das System unterstützt auch API-Versionierung, was entscheidend für die Wartung und Weiterentwicklung von Produktions-APIs ist. Verschiedene Versionen können parallel existieren, was es ermöglicht, neue Features schrittweise einzuführen, ohne bestehende Clients zu beeinträchtigen.

32.13 End-to-End Type Safety

End-to-End Type Safety ist wie ein vollständiges Qualitätssicherungssystem in einer Fabrik - es stellt sicher, dass von der ersten Komponente bis zum finalen Produkt alles den definierten Standards entspricht. In einer Angular-NestJS-Anwendung bedeutet das, dass Typen vom Browser bis zur Datenbank und wieder zurück konsistent und korrekt sind.

Der wahre Wert von TypeScript zeigt sich erst, wenn die gesamte Anwendung typisiert ist. Es reicht nicht aus, nur Frontend oder Backend zu typisieren - die echten Vorteile entstehen, wenn beide Seiten und alle Zwischenebenen derselben Typ-Kontrakte folgen. Dies schafft ein Sicherheitsnetz, das Fehler zur Compile-Zeit abfängt, bevor sie in die Produktion gelangen können.

// libs/shared/type-safety/src/lib/end-to-end-types.ts
// Umfassendes Type Safety System für die gesamte Anwendung
import { Observable } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';

// Basis-Typen für Type Safety
export type SafeApiCall<TRequest, TResponse> = (request: TRequest) => Observable<TResponse>;
export type SafeAsyncApiCall<TRequest, TResponse> = (request: TRequest) => Promise<TResponse>;

// Error Handling Types
export interface TypeSafeError {
  code: string;
  message: string;
  details?: Record<string, any>;
  timestamp: Date;
  source: 'frontend' | 'backend' | 'network';
}

export type ApiResult<T> = {
  success: true;
  data: T;
} | {
  success: false;
  error: TypeSafeError;
};

// Generic Type-Safe API Client Interface
export interface TypeSafeApiClient {
  get<TResponse>(url: string, options?: RequestOptions): Observable<ApiResult<TResponse>>;
  post<TRequest, TResponse>(url: string, body: TRequest, options?: RequestOptions): Observable<ApiResult<TResponse>>;
  put<TRequest, TResponse>(url: string, body: TRequest, options?: RequestOptions): Observable<ApiResult<TResponse>>;
  patch<TRequest, TResponse>(url: string, body: TRequest, options?: RequestOptions): Observable<ApiResult<TResponse>>;
  delete<TResponse>(url: string, options?: RequestOptions): Observable<ApiResult<TResponse>>;
}

export interface RequestOptions {
  headers?: Record<string, string>;
  params?: Record<string, string | number | boolean>;
  timeout?: number;
}

// Type-Safe Form Handling
export type FormFieldType<T> = T extends string 
  ? 'text' | 'email' | 'password' | 'url'
  : T extends number 
    ? 'number'
    : T extends boolean 
      ? 'checkbox'
      : T extends Date 
        ? 'date' | 'datetime-local'
        : 'text';

export type FormConfig<T> = {
  [K in keyof T]: {
    type: FormFieldType<T[K]>;
    label: string;
    required: boolean;
    validators?: Array<(value: T[K]) => string | null>;
    placeholder?: string;
    helpText?: string;
  };
};

export interface TypeSafeForm<T> {
  config: FormConfig<T>;
  initialValue: Partial<T>;
  onSubmit: (value: T) => Observable<ApiResult<any>>;
  onValidate?: (value: Partial<T>) => Record<keyof T, string | null>;
}

// Database Entity Type Safety
export interface TypeSafeEntity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
  deletedAt?: Date;
}

export type EntityRelation<T extends TypeSafeEntity> = T | string; // ID oder vollständige Entity
export type EntityArray<T extends TypeSafeEntity> = T[] | string[]; // Array von Entities oder IDs

// Repository Pattern mit Type Safety
export interface TypeSafeRepository<TEntity extends TypeSafeEntity, TCreateDto, TUpdateDto> {
  findAll(options?: FindAllOptions<TEntity>): Promise<TEntity[]>;
  findById(id: string): Promise<TEntity | null>;
  findOne(criteria: Partial<TEntity>): Promise<TEntity | null>;
  create(dto: TCreateDto): Promise<TEntity>;
  update(id: string, dto: TUpdateDto): Promise<TEntity>;
  delete(id: string): Promise<void>;
  count(criteria?: Partial<TEntity>): Promise<number>;
}

export interface FindAllOptions<TEntity> {
  where?: Partial<TEntity>;
  orderBy?: Partial<Record<keyof TEntity, 'ASC' | 'DESC'>>;
  limit?: number;
  offset?: number;
  relations?: Array<keyof TEntity>;
}

// Service Layer Type Safety
export interface TypeSafeService<TEntity extends TypeSafeEntity, TCreateDto, TUpdateDto> {
  getAll(options?: FindAllOptions<TEntity>): Promise<ApiResult<TEntity[]>>;
  getById(id: string): Promise<ApiResult<TEntity>>;
  create(dto: TCreateDto): Promise<ApiResult<TEntity>>;
  update(id: string, dto: TUpdateDto): Promise<ApiResult<TEntity>>;
  delete(id: string): Promise<ApiResult<void>>;
}

// Controller Type Safety
export interface TypeSafeController<TEntity extends TypeSafeEntity, TCreateDto, TUpdateDto> {
  findAll(query: FindAllOptions<TEntity>): Promise<ApiResult<TEntity[]>>;
  findById(id: string): Promise<ApiResult<TEntity>>;
  create(body: TCreateDto): Promise<ApiResult<TEntity>>;
  update(id: string, body: TUpdateDto): Promise<ApiResult<TEntity>>;
  delete(id: string): Promise<ApiResult<void>>;
}

// WebSocket Type Safety
export interface TypeSafeWebSocketEvent<TPayload = any> {
  event: string;
  payload: TPayload;
  timestamp: Date;
  userId?: string;
  sessionId?: string;
}

export interface TypeSafeWebSocketClient {
  emit<TPayload>(event: string, payload: TPayload): void;
  on<TPayload>(event: string, handler: (data: TypeSafeWebSocketEvent<TPayload>) => void): void;
  off(event: string, handler?: Function): void;
  disconnect(): void;
}

// State Management Type Safety (für NgRx oder ähnliches)
export interface TypeSafeAction<TType extends string = string, TPayload = any> {
  type: TType;
  payload?: TPayload;
  meta?: Record<string, any>;
}

export interface TypeSafeState<T = any> {
  data: T;
  loading: boolean;
  error: TypeSafeError | null;
  lastUpdated: Date;
}

export type TypeSafeReducer<TState, TAction extends TypeSafeAction> = (
  state: TState,
  action: TAction
) => TState;

// Testing Type Safety
export interface TypeSafeTestCase<TInput, TExpectedOutput> {
  name: string;
  input: TInput;
  expectedOutput: TExpectedOutput;
  setup?: () => void;
  teardown?: () => void;
}

export interface TypeSafeApiTestCase<TRequest, TResponse> extends TypeSafeTestCase<TRequest, TResponse> {
  endpoint: string;
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
  mockResponse?: TResponse;
  expectedStatusCode?: number;
}

// Configuration Type Safety
export interface TypeSafeEnvironmentConfig {
  production: boolean;
  apiUrl: string;
  websocketUrl: string;
  authTokenKey: string;
  logLevel: 'debug' | 'info' | 'warn' | 'error';
  featureFlags: Record<string, boolean>;
}

// Validation Type Safety
export type ValidationRule<T> = (value: T) => string | null;
export type ValidationSchema<T> = {
  [K in keyof T]?: ValidationRule<T[K]>[];
};

export interface TypeSafeValidator<T> {
  schema: ValidationSchema<T>;
  validate(data: Partial<T>): ValidationResult<T>;
  validateField<K extends keyof T>(field: K, value: T[K]): string | null;
}

export interface ValidationResult<T> {
  isValid: boolean;
  errors: Partial<Record<keyof T, string>>;
  data: T;
}

// Utility Types für erweiterte Type Safety
export type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

export type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

export type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;

export type OptionalFields<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

export type NonNullableFields<T> = {
  [P in keyof T]: NonNullable<T[P]>;
};

// Event-driven Architecture Type Safety
export interface TypeSafeDomainEvent<TPayload = any> {
  eventType: string;
  aggregateId: string;
  payload: TPayload;
  version: number;
  timestamp: Date;
  userId?: string;
}

export interface TypeSafeEventHandler<TEvent extends TypeSafeDomainEvent> {
  handle(event: TEvent): Promise<void>;
  eventType: string;
}

export interface TypeSafeEventBus {
  publish<TEvent extends TypeSafeDomainEvent>(event: TEvent): Promise<void>;
  subscribe<TEvent extends TypeSafeDomainEvent>(
    eventType: string,
    handler: TypeSafeEventHandler<TEvent>
  ): void;
  unsubscribe(eventType: string, handler: TypeSafeEventHandler<any>): void;
}

// Query Type Safety (für komplexe Datenabrufe)
export interface TypeSafeQuery<TResult> {
  select?: Array<keyof TResult>;
  where?: QueryCondition<TResult>;
  orderBy?: QuerySort<TResult>;
  limit?: number;
  offset?: number;
  relations?: string[];
}

export type QueryCondition<T> = {
  [K in keyof T]?: T[K] | QueryOperator<T[K]>;
};

export interface QueryOperator<T> {
  $eq?: T;
  $ne?: T;
  $in?: T[];
  $nin?: T[];
  $gt?: T;
  $gte?: T;
  $lt?: T;
  $lte?: T;
  $like?: string;
  $ilike?: string;
  $between?: [T, T];
}

export type QuerySort<T> = {
  [K in keyof T]?: 'ASC' | 'DESC';
};

// Performance Type Safety
export interface TypeSafePerformanceMetric {
  name: string;
  value: number;
  unit: 'ms' | 'bytes' | 'count' | 'percent';
  timestamp: Date;
  tags?: Record<string, string>;
}

export interface TypeSafePerformanceMonitor {
  startTimer(name: string): () => TypeSafePerformanceMetric;
  recordMetric(metric: TypeSafePerformanceMetric): void;
  getMetrics(filter?: Partial<TypeSafePerformanceMetric>): TypeSafePerformanceMetric[];
}

Diese umfassende Type Safety-Infrastruktur stellt sicher, dass jede Schicht der Anwendung typisiert ist und dass diese Types konsistent durch die gesamte Anwendung fließen. Von der UI-Komponente über den Service-Layer bis zur Datenbank und wieder zurück - jeder Schritt ist typisiert und validiert.

// apps/frontend/src/app/core/services/type-safe-api.service.ts
// Implementierung des Type-Safe API Clients für Angular
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { 
  TypeSafeApiClient, 
  ApiResult, 
  TypeSafeError, 
  RequestOptions 
} from '@workspace/shared-type-safety';

@Injectable({
  providedIn: 'root'
})
export class TypeSafeApiService implements TypeSafeApiClient {
  private readonly baseUrl = '/api';

  constructor(private http: HttpClient) {}

  get<TResponse>(url: string, options?: RequestOptions): Observable<ApiResult<TResponse>> {
    return this.http.get<TResponse>(this.buildUrl(url), this.buildHttpOptions(options))
      .pipe(
        map(data => ({ success: true as const, data })),
        catchError(error => this.handleError(error))
      );
  }

  post<TRequest, TResponse>(
    url: string, 
    body: TRequest, 
    options?: RequestOptions
  ): Observable<ApiResult<TResponse>> {
    return this.http.post<TResponse>(this.buildUrl(url), body, this.buildHttpOptions(options))
      .pipe(
        map(data => ({ success: true as const, data })),
        catchError(error => this.handleError(error))
      );
  }

  put<TRequest, TResponse>(
    url: string, 
    body: TRequest, 
    options?: RequestOptions
  ): Observable<ApiResult<TResponse>> {
    return this.http.put<TResponse>(this.buildUrl(url), body, this.buildHttpOptions(options))
      .pipe(
        map(data => ({ success: true as const, data })),
        catchError(error => this.handleError(error))
      );
  }

  patch<TRequest, TResponse>(
    url: string, 
    body: TRequest, 
    options?: RequestOptions
  ): Observable<ApiResult<TResponse>> {
    return this.http.patch<TResponse>(this.buildUrl(url), body, this.buildHttpOptions(options))
      .pipe(
        map(data => ({ success: true as const, data })),
        catchError(error => this.handleError(error))
      );
  }

  delete<TResponse>(url: string, options?: RequestOptions): Observable<ApiResult<TResponse>> {
    return this.http.delete<TResponse>(this.buildUrl(url), this.buildHttpOptions(options))
      .pipe(
        map(data => ({ success: true as const, data })),
        catchError(error => this.handleError(error))
      );
  }

  private buildUrl(url: string): string {
    return url.startsWith('http') ? url : `${this.baseUrl}${url}`;
  }

  private buildHttpOptions(options?: RequestOptions): any {
    let headers = new HttpHeaders();
    
    if (options?.headers) {
      Object.entries(options.headers).forEach(([key, value]) => {
        headers = headers.set(key, value);
      });
    }

    return {
      headers,
      params: options?.params,
      timeout: options?.timeout,
    };
  }

  private handleError<T>(error: HttpErrorResponse): Observable<ApiResult<T>> {
    const typeSafeError: TypeSafeError = {
      code: error.status?.toString() || 'UNKNOWN_ERROR',
      message: error.message || 'An unexpected error occurred',
      details: {
        status: error.status,
        statusText: error.statusText,
        url: error.url,
      },
      timestamp: new Date(),
      source: 'network',
    };

    return of({ success: false as const, error: typeSafeError });
  }
}
// apps/backend/src/common/decorators/type-safe-controller.decorator.ts
// Type-Safe Controller Decorator für NestJS
import { applyDecorators, Type } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger';
import { 
  TypeSafeEntity, 
  ApiResult 
} from '@workspace/shared-type-safety';

export interface TypeSafeControllerOptions<TEntity, TCreateDto, TUpdateDto> {
  entity: Type<TEntity>;
  createDto: Type<TCreateDto>;
  updateDto: Type<TUpdateDto>;
  tag: string;
}

export function TypeSafeController<TEntity extends TypeSafeEntity, TCreateDto, TUpdateDto>(
  options: TypeSafeControllerOptions<TEntity, TCreateDto, TUpdateDto>
) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    // Hier würden Sie die Controller-Methoden automatisch mit den entsprechenden
    // Swagger-Decorators und Type-Validierung versehen
    return constructor;
  };
}

// Type-Safe Method Decorators
export function TypeSafeGet<TResponse>(responseType: Type<TResponse>, description: string) {
  return applyDecorators(
    ApiOperation({ summary: description }),
    ApiResponse({ 
      status: 200, 
      description: 'Success',
      type: responseType,
    }),
    ApiResponse({ 
      status: 400, 
      description: 'Bad Request' 
    }),
    ApiResponse({ 
      status: 500, 
      description: 'Internal Server Error' 
    })
  );
}

export function TypeSafePost<TRequest, TResponse>(
  requestType: Type<TRequest>,
  responseType: Type<TResponse>,
  description: string
) {
  return applyDecorators(
    ApiOperation({ summary: description }),
    ApiBody({ type: requestType }),
    ApiResponse({ 
      status: 201, 
      description: 'Created',
      type: responseType,
    }),
    ApiResponse({ 
      status: 400, 
      description: 'Bad Request' 
    }),
    ApiResponse({ 
      status: 500, 
      description: 'Internal Server Error' 
    })
  );
}

export function TypeSafePut<TRequest, TResponse>(
  requestType: Type<TRequest>,
  responseType: Type<TResponse>,
  description: string
) {
  return applyDecorators(
    ApiOperation({ summary: description }),
    ApiBody({ type: requestType }),
    ApiResponse({ 
      status: 200, 
      description: 'Updated',
      type: responseType,
    }),
    ApiResponse({ 
      status: 400, 
      description: 'Bad Request' 
    }),
    ApiResponse({ 
      status: 404, 
      description: 'Not Found' 
    }),
    ApiResponse({ 
      status: 500, 
      description: 'Internal Server Error' 
    })
  );
}

End-to-End Type Safety transformiert die Entwicklungserfahrung fundamental. Anstatt zur Laufzeit herauszufinden, dass ein API-Call fehlschlägt oder dass Daten nicht das erwartete Format haben, erhalten Entwickler sofortiges Feedback vom TypeScript-Compiler. Dies reduziert nicht nur Bugs, sondern auch die Zeit, die für Debugging und Tests benötigt wird.

Die wirkliche Stärke zeigt sich bei Refactoring-Operationen. Wenn Sie eine Eigenschaft zu einem User-Model hinzufügen, wird TypeScript Sie an allen Stellen warnen, wo diese neue Eigenschaft verwendet werden könnte. Dies macht großangelegte Änderungen sicherer und schneller, da Sie Vertrauen haben, dass der Compiler alle betroffenen Stellen identifiziert hat.