30 NestJS im Enterprise-Bereich

Stellen Sie sich vor, Sie müssten eine Brücke bauen - nicht irgendeine Brücke, sondern eine, die täglich von Millionen von Menschen benutzt wird, bei jedem Wetter standhalten muss und gleichzeitig verschiedene Arten von Verkehr bewältigen soll. Genau vor dieser Herausforderung stehen Unternehmen, wenn sie Softwaresysteme für den Enterprise-Bereich entwickeln. Diese Systeme müssen nicht nur funktionieren, sondern auch skalieren, sicher sein, regulatorischen Anforderungen entsprechen und sich nahtlos in bestehende Infrastrukturen einfügen.

NestJS hat sich in den letzten Jahren als eine der führenden Technologien für solche Enterprise-Anwendungen etabliert. Aber was macht es so besonders geeignet für große Unternehmen? Die Antwort liegt nicht nur in der technischen Exzellenz des Frameworks, sondern auch in seiner Philosophie: NestJS wurde von Anfang an mit dem Gedanken entwickelt, dass Software wartbar, testbar und erweiterbar sein muss. Diese Prinzipien sind in Enterprise-Umgebungen nicht nur wünschenswert, sondern absolut kritisch.

Wenn wir über Enterprise-Software sprechen, bewegen wir uns in einer Welt, in der ein Systemausfall Millionen kosten kann, Sicherheitslücken zu regulatorischen Strafen führen und schlechte Performance ganze Geschäftsprozesse lahmlegen kann. In diesem Kontext ist die Wahl der richtigen Technologie nicht nur eine technische Entscheidung, sondern eine strategische Geschäftsentscheidung.

30.1 Warum NestJS für Enterprise?

Die Frage, warum NestJS für Enterprise-Anwendungen geeignet ist, lässt sich am besten durch eine Analogie verstehen. Denken Sie an den Unterschied zwischen dem Bau eines Familienhauses und dem Bau eines Wolkenkratzers. Beide sind Gebäude, aber die Anforderungen, Standards und Komplexitäten sind völlig unterschiedlich. Ein Wolkenkratzer benötigt eine ausgeklügelte Architektur, strenge Sicherheitsstandards, redundante Systeme und die Fähigkeit, verschiedene Arten von Mietern zu beherbergen. Genau diese Eigenschaften bringt NestJS für die Softwareentwicklung mit.

Der erste und vielleicht wichtigste Grund ist die Architektur selbst. NestJS folgt den bewährten Prinzipien der objektorientierten Programmierung und kombiniert sie mit modernen Konzepten wie Dependency Injection und Decorator-basierter Konfiguration. Diese Herangehensweise ist nicht zufällig gewählt - sie stammt aus der Enterprise-Welt von Java und .NET, Technologien, die seit Jahrzehnten in kritischen Geschäftsanwendungen eingesetzt werden.

Betrachten wir ein praktisches Beispiel: Stellen Sie sich vor, Sie entwickeln ein Customer Relationship Management (CRM) System für ein multinationalen Konzern. Dieses System muss Millionen von Kundendatensätzen verwalten, mit verschiedenen Datenbanken kommunizieren, APIs für mobile Anwendungen bereitstellen und gleichzeitig strenge Sicherheits- und Compliance-Anforderungen erfüllen.

// enterprise/crm/src/customer/customer.module.ts
// Ein typisches Enterprise-Modul zeigt die organisatorische Klarheit von NestJS
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CustomerController } from './customer.controller';
import { CustomerService } from './customer.service';
import { Customer } from './entities/customer.entity';
import { CustomerRepository } from './repositories/customer.repository';
import { AuditModule } from '../audit/audit.module';
import { SecurityModule } from '../security/security.module';
import { NotificationModule } from '../notification/notification.module';

@Module({
  imports: [
    // Datenbankentitäten registrieren
    TypeOrmModule.forFeature([Customer]),
    
    // Abhängige Module für Enterprise-Funktionalitäten
    AuditModule,           // Für Compliance und Nachverfolgung
    SecurityModule,        // Für Autorisierung und Verschlüsselung
    NotificationModule,    // Für Geschäftsprozess-Benachrichtigungen
  ],
  controllers: [CustomerController],
  providers: [
    CustomerService,
    CustomerRepository,
    
    // Enterprise-spezifische Provider
    {
      provide: 'CUSTOMER_CACHE_CONFIG',
      useValue: {
        ttl: parseInt(process.env.CUSTOMER_CACHE_TTL, 10) || 3600,
        maxItems: parseInt(process.env.CUSTOMER_CACHE_MAX_ITEMS, 10) || 10000,
      },
    },
    
    // Datenschutz-konforme Konfiguration
    {
      provide: 'GDPR_COMPLIANCE_CONFIG',
      useValue: {
        dataRetentionDays: 2555, // 7 Jahre
        anonymizationFields: ['email', 'phone', 'address'],
        requiredConsents: ['marketing', 'analytics'],
      },
    },
  ],
  exports: [CustomerService], // Für andere Module verfügbar machen
})
export class CustomerModule {}

Die modulare Struktur von NestJS löst ein fundamentales Problem in Enterprise-Anwendungen: die Verwaltung von Komplexität. Anstatt eine monolithische Anwendung zu haben, in der alles miteinander verwoben ist, können Sie Ihre Geschäftslogik in klar abgegrenzte Module aufteilen. Jedes Modul kann eigenständig entwickelt, getestet und gewartet werden, was besonders in großen Entwicklungsteams von unschätzbarem Wert ist.

Ein weiterer entscheidender Vorteil ist die TypeScript-Integration. In Enterprise-Umgebungen, wo ein kleiner Fehler katastrophale Auswirkungen haben kann, ist die Compile-Time-Typsicherheit von TypeScript wie ein Sicherheitsnetz. Sie fängt potentielle Probleme ab, bevor sie in die Produktion gelangen. Stellen Sie sich vor, Sie hätten ein Frühwarnsystem, das Sie warnt, wenn Sie dabei sind, einen kritischen Fehler zu machen - genau das leistet TypeScript.

// enterprise/crm/src/customer/dto/update-customer.dto.ts
// Typsichere DTOs verhindern Laufzeitfehler in kritischen Systemen
import { IsEmail, IsOptional, IsString, IsDateString, ValidateNested, IsArray } from 'class-validator';
import { Type } from 'class-transformer';

export class AddressDto {
  @IsString()
  @IsOptional()
  street?: string;

  @IsString()
  @IsOptional()
  city?: string;

  @IsString()
  @IsOptional()
  postalCode?: string;

  @IsString()
  @IsOptional()
  country?: string;
}

export class UpdateCustomerDto {
  @IsString()
  @IsOptional()
  readonly firstName?: string;

  @IsString()
  @IsOptional()
  readonly lastName?: string;

  @IsEmail()
  @IsOptional()
  readonly email?: string;

  @ValidateNested()
  @Type(() => AddressDto)
  @IsOptional()
  readonly address?: AddressDto;

  @IsArray()
  @IsString({ each: true })
  @IsOptional()
  readonly tags?: string[];

  @IsDateString()
  @IsOptional()
  readonly lastContactDate?: string;

  // Enterprise-spezifische Felder für Compliance
  @IsArray()
  @IsString({ each: true })
  @IsOptional()
  readonly consentTypes?: string[];

  @IsDateString()
  @IsOptional()
  readonly gdprConsentDate?: string;
}

Diese Typsicherheit erstreckt sich durch die gesamte Anwendung und schafft ein Vertrauen, das in kritischen Geschäftsanwendungen unerlässlich ist. Wenn Sie wissen, dass Ihre Datenstrukturen zur Compile-Zeit validiert werden, können Sie sich auf die Geschäftslogik konzentrieren, anstatt sich Gedanken über potentielle Typfehler zu machen.

30.2 Skalierbarkeit und Performance

In der Enterprise-Welt ist Skalierbarkeit nicht nur ein Nice-to-have, sondern eine Überlebensfrage. Stellen Sie sich vor, Sie betreiben eine E-Commerce-Plattform, die normalerweise 10.000 Benutzer gleichzeitig bedient, aber am Black Friday plötzlich 100.000 Benutzer gleichzeitig bewältigen muss. Ihre Anwendung muss nicht nur diese Last bewältigen, sondern auch dabei zuverlässig und performant bleiben.

NestJS bietet mehrere Mechanismen für Skalierbarkeit, die von horizontaler Skalierung über Microservices bis hin zu intelligenter Ressourcenoptimierung reichen. Der Schlüssel liegt darin, diese Mechanismen strategisch einzusetzen, basierend auf den spezifischen Anforderungen Ihrer Anwendung.

Beginnen wir mit dem Performance-Monitoring, denn Sie können nur optimieren, was Sie messen können. Denken Sie an Performance-Monitoring wie an das Armaturenbrett in Ihrem Auto - es zeigt Ihnen alle wichtigen Kennzahlen an, die Sie benötigen, um sicher und effizient zu fahren.

// enterprise/monitoring/src/performance/performance.interceptor.ts
// Ein umfassendes Performance-Monitoring-System für Enterprise-Anwendungen
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { MetricsService } from './metrics.service';

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

  constructor(private readonly metricsService: MetricsService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const startTime = Date.now();
    const request = context.switchToHttp().getRequest();
    const { method, url, ip, headers } = request;
    
    // Eindeutige Request-ID für Tracing
    const requestId = headers['x-request-id'] || `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    // Request-Kontext für Logging setzen
    request.requestId = requestId;
    request.startTime = startTime;

    this.logger.log(`🚀 [${requestId}] ${method} ${url} - Start`, {
      method,
      url,
      ip,
      userAgent: headers['user-agent'],
    });

    return next.handle().pipe(
      tap({
        next: (data) => {
          const duration = Date.now() - startTime;
          const dataSize = JSON.stringify(data).length;

          // Metriken sammeln
          this.metricsService.recordRequestDuration(method, url, duration);
          this.metricsService.recordResponseSize(method, url, dataSize);
          this.metricsService.incrementRequestCounter(method, url, 200);

          // Performance-Warnung bei langsamen Requests
          if (duration > 1000) {
            this.logger.warn(`⚠️ [${requestId}] Slow request: ${duration}ms`, {
              method,
              url,
              duration,
              dataSize,
            });
          }

          this.logger.log(`✅ [${requestId}] ${method} ${url} - Success (${duration}ms)`, {
            duration,
            dataSize,
            statusCode: 200,
          });
        },
        error: (error) => {
          const duration = Date.now() - startTime;
          const statusCode = error.status || 500;

          // Fehler-Metriken sammeln
          this.metricsService.incrementRequestCounter(method, url, statusCode);
          this.metricsService.incrementErrorCounter(method, url, error.name);

          this.logger.error(`❌ [${requestId}] ${method} ${url} - Error (${duration}ms)`, {
            duration,
            statusCode,
            errorType: error.name,
            errorMessage: error.message,
            stack: error.stack,
          });
        },
      }),
    );
  }
}

Für echte Enterprise-Skalierung ist Caching unerlässlich. Denken Sie an Caching wie an ein gut organisiertes Lager: Die am häufigsten benötigten Artikel werden vorne gelagert, damit Sie sie schnell erreichen können, während seltener benötigte Artikel weiter hinten stehen.

// enterprise/caching/src/cache/enterprise-cache.service.ts
// Ein mehrschichtiges Caching-System für Enterprise-Anwendungen
import { Injectable, Logger } from '@nestjs/common';
import { Redis } from 'ioredis';
import { LRUCache } from 'lru-cache';

@Injectable()
export class EnterpriseCacheService {
  private readonly logger = new Logger(EnterpriseCacheService.name);
  
  // Level 1: In-Memory Cache für sehr häufig abgerufene Daten
  private readonly l1Cache = new LRUCache<string, any>({
    max: 10000,           // Maximal 10.000 Einträge
    ttl: 5 * 60 * 1000,   // 5 Minuten TTL
    updateAgeOnGet: true, // TTL bei Zugriff erneuern
  });

  // Level 2: Redis für geteilte Caches zwischen Instanzen
  private readonly l2Cache: Redis;

  constructor() {
    // Redis-Verbindung mit Enterprise-Konfiguration
    this.l2Cache = new Redis({
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT, 10),
      password: process.env.REDIS_PASSWORD,
      db: parseInt(process.env.REDIS_DB, 10) || 0,
      
      // Enterprise-spezifische Konfiguration
      retryDelayOnFailover: 100,
      maxRetriesPerRequest: 3,
      
      // Connection Pool für bessere Performance
      lazyConnect: true,
      keepAlive: 30000,
      
      // Cluster-Konfiguration für High Availability
      enableOfflineQueue: false,
    });
  }

  async get<T>(key: string, options?: { skipL1?: boolean }): Promise<T | null> {
    const cacheKey = this.buildCacheKey(key);

    try {
      // Schritt 1: Versuche L1 Cache (in-memory)
      if (!options?.skipL1) {
        const l1Result = this.l1Cache.get(cacheKey);
        if (l1Result !== undefined) {
          this.logger.debug(`Cache HIT (L1): ${cacheKey}`);
          return l1Result as T;
        }
      }

      // Schritt 2: Versuche L2 Cache (Redis)
      const l2Result = await this.l2Cache.get(cacheKey);
      if (l2Result !== null) {
        const parsedResult = JSON.parse(l2Result);
        
        // Speichere Ergebnis auch in L1 Cache für zukünftige Zugriffe
        this.l1Cache.set(cacheKey, parsedResult);
        
        this.logger.debug(`Cache HIT (L2): ${cacheKey}`);
        return parsedResult as T;
      }

      this.logger.debug(`Cache MISS: ${cacheKey}`);
      return null;

    } catch (error) {
      this.logger.error(`Cache GET error for key ${cacheKey}:`, error);
      return null;
    }
  }

  async set<T>(key: string, value: T, ttlSeconds?: number): Promise<void> {
    const cacheKey = this.buildCacheKey(key);
    const defaultTTL = 3600; // 1 Stunde Standard-TTL

    try {
      // In beiden Cache-Ebenen speichern
      this.l1Cache.set(cacheKey, value);
      
      if (ttlSeconds || defaultTTL) {
        await this.l2Cache.setex(cacheKey, ttlSeconds || defaultTTL, JSON.stringify(value));
      } else {
        await this.l2Cache.set(cacheKey, JSON.stringify(value));
      }

      this.logger.debug(`Cache SET: ${cacheKey} (TTL: ${ttlSeconds || defaultTTL}s)`);

    } catch (error) {
      this.logger.error(`Cache SET error for key ${cacheKey}:`, error);
    }
  }

  async invalidate(pattern: string): Promise<void> {
    try {
      // L1 Cache: Alle Keys durchgehen und matching löschen
      for (const key of this.l1Cache.keys()) {
        if (key.includes(pattern)) {
          this.l1Cache.delete(key);
        }
      }

      // L2 Cache: Pattern-basierte Löschung
      const keys = await this.l2Cache.keys(`*${pattern}*`);
      if (keys.length > 0) {
        await this.l2Cache.del(...keys);
      }

      this.logger.log(`Cache invalidated for pattern: ${pattern} (${keys.length} keys)`);

    } catch (error) {
      this.logger.error(`Cache invalidation error for pattern ${pattern}:`, error);
    }
  }

  private buildCacheKey(key: string): string {
    const environment = process.env.NODE_ENV || 'development';
    const version = process.env.APP_VERSION || 'v1';
    return `${environment}:${version}:${key}`;
  }

  // Enterprise-spezifische Methoden für Cache-Management
  async getCacheStats(): Promise<any> {
    return {
      l1: {
        size: this.l1Cache.size,
        max: this.l1Cache.max,
        hitRate: this.calculateL1HitRate(),
      },
      l2: {
        info: await this.l2Cache.info('memory'),
        keyspace: await this.l2Cache.info('keyspace'),
      },
    };
  }

  private calculateL1HitRate(): number {
    // Vereinfachte Hit-Rate-Berechnung
    // In einer echten Implementierung würden Sie detailliertere Metriken sammeln
    return this.l1Cache.size > 0 ? 0.85 : 0; // Platzhalter-Wert
  }
}

Database Connection Pooling ist ein weiterer kritischer Aspekt für Enterprise-Performance. Stellen Sie sich vor, Sie betreiben ein Restaurant: Anstatt für jeden Gast einen neuen Koch einzustellen, haben Sie einen Pool von Köchen, die mehrere Bestellungen abarbeiten können. Genau so funktioniert Connection Pooling für Datenbanken.

// enterprise/database/src/config/database.config.ts
// Enterprise-konforme Datenbankkonfiguration mit Connection Pooling
import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const getDatabaseConfig = (): TypeOrmModuleOptions => {
  const isProduction = process.env.NODE_ENV === 'production';
  
  return {
    type: 'postgres',
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT, 10) || 5432,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    
    // Enterprise Connection Pooling
    extra: {
      // Maximale Anzahl simultaner Verbindungen
      max: parseInt(process.env.DB_POOL_MAX, 10) || 20,
      
      // Minimale Anzahl von Verbindungen, die im Pool gehalten werden
      min: parseInt(process.env.DB_POOL_MIN, 10) || 5,
      
      // Zeit in Millisekunden, die eine Verbindung idle sein kann, bevor sie geschlossen wird
      idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT, 10) || 30000,
      
      // Maximale Zeit, die auf eine Verbindung gewartet wird
      acquireTimeoutMillis: parseInt(process.env.DB_ACQUIRE_TIMEOUT, 10) || 60000,
      
      // Aktiviert Connection-Validierung
      testOnBorrow: true,
      
      // SSL-Konfiguration für Enterprise-Sicherheit
      ssl: isProduction ? {
        rejectUnauthorized: false, // Für selbstsignierte Zertifikate
        ca: process.env.DB_SSL_CA,
        cert: process.env.DB_SSL_CERT,
        key: process.env.DB_SSL_KEY,
      } : false,
      
      // Query-Timeout zur Vermeidung von hängenden Verbindungen
      statement_timeout: parseInt(process.env.DB_STATEMENT_TIMEOUT, 10) || 30000,
      
      // Logging für Performance-Monitoring
      log: isProduction ? ['error', 'warn'] : ['query', 'error', 'warn'],
    },
    
    // Automatische Schema-Synchronisation nur in Development
    synchronize: !isProduction,
    
    // Migrations-Konfiguration für Production
    migrationsRun: isProduction,
    migrations: ['dist/migrations/*.js'],
    migrationsTableName: 'typeorm_migrations',
    
    // Logging-Konfiguration
    logging: isProduction ? ['error', 'warn', 'migration'] : 'all',
    logger: 'advanced-console',
    
    // Entity-Konfiguration
    entities: ['dist/**/*.entity.js'],
    autoLoadEntities: true,
    
    // Cache-Konfiguration für Query-Results
    cache: {
      type: 'redis',
      options: {
        host: process.env.REDIS_HOST,
        port: parseInt(process.env.REDIS_PORT, 10),
        password: process.env.REDIS_PASSWORD,
        db: parseInt(process.env.REDIS_CACHE_DB, 10) || 1,
      },
      duration: 300000, // 5 Minuten Standard-Cache-Zeit
    },
  };
};

30.3 Security Best Practices

Sicherheit in Enterprise-Anwendungen ist wie der Bau eines Tresors - Sie benötigen mehrere Sicherheitsebenen, die alle zusammenarbeiten, um Ihre wertvollsten Daten zu schützen. In der Welt der Enterprise-Software kann eine einzige Sicherheitslücke nicht nur zu Datenverlust führen, sondern auch zu rechtlichen Konsequenzen, Vertrauensverlust und enormen finanziellen Schäden.

Der erste Grundsatz der Enterprise-Sicherheit ist das Prinzip der “Defense in Depth” - verteidigungstiefe Sicherheit. Das bedeutet, dass Sie nicht auf eine einzige Sicherheitsmaßnahme setzen, sondern mehrere Schutzschichten implementieren. Wenn eine Schicht versagt, fangen die anderen den Angriff ab.

// enterprise/security/src/guards/enterprise-auth.guard.ts
// Ein mehrschichtiges Authentifizierungs- und Autorisierungssystem
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Logger } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { SecurityService } from '../services/security.service';
import { AuditService } from '../services/audit.service';

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

  constructor(
    private readonly jwtService: JwtService,
    private readonly reflector: Reflector,
    private readonly securityService: SecurityService,
    private readonly auditService: AuditService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const handler = context.getHandler();
    const controllerClass = context.getClass();

    // Schritt 1: Rate Limiting prüfen
    await this.checkRateLimit(request);

    // Schritt 2: Token-Validierung
    const user = await this.validateToken(request);

    // Schritt 3: Benutzer-Status prüfen (aktiv, nicht gesperrt, etc.)
    await this.validateUserStatus(user);

    // Schritt 4: Rollen- und Permissions-Prüfung
    await this.validatePermissions(user, handler, controllerClass);

    // Schritt 5: IP-Whitelist prüfen (für sensitive Operationen)
    await this.validateIPAccess(request, handler);

    // Schritt 6: Session-Validierung
    await this.validateSession(user, request);

    // Schritt 7: Audit-Log erstellen
    await this.logAccess(user, request, handler);

    // User-Informationen an Request anhängen
    request.user = user;
    
    return true;
  }

  private async checkRateLimit(request: any): Promise<void> {
    const clientIP = this.getClientIP(request);
    const isRateLimited = await this.securityService.checkRateLimit(clientIP);
    
    if (isRateLimited) {
      this.logger.warn(`Rate limit exceeded for IP: ${clientIP}`);
      throw new UnauthorizedException('Rate limit exceeded');
    }
  }

  private async validateToken(request: any): Promise<any> {
    const token = this.extractTokenFromHeader(request);
    
    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    try {
      // JWT-Token dekodieren und validieren
      const payload = await this.jwtService.verifyAsync(token);
      
      // Token-Blacklist prüfen (für Logout-Tokens)
      const isBlacklisted = await this.securityService.isTokenBlacklisted(token);
      if (isBlacklisted) {
        throw new UnauthorizedException('Token has been revoked');
      }

      // Token-Metadaten validieren
      if (!payload.sub || !payload.iat || !payload.exp) {
        throw new UnauthorizedException('Invalid token structure');
      }

      // User-Informationen laden
      const user = await this.securityService.getUserById(payload.sub);
      if (!user) {
        throw new UnauthorizedException('User not found');
      }

      return user;

    } catch (error) {
      if (error.name === 'JsonWebTokenError') {
        throw new UnauthorizedException('Invalid token');
      }
      if (error.name === 'TokenExpiredError') {
        throw new UnauthorizedException('Token has expired');
      }
      throw error;
    }
  }

  private async validateUserStatus(user: any): Promise<void> {
    // Benutzer-Status prüfen
    if (!user.isActive) {
      throw new UnauthorizedException('User account is deactivated');
    }

    if (user.isLocked) {
      throw new UnauthorizedException('User account is locked');
    }

    // Passwort-Ablauf prüfen (Enterprise-Anforderung)
    const passwordAge = Date.now() - new Date(user.passwordChangedAt).getTime();
    const maxPasswordAge = 90 * 24 * 60 * 60 * 1000; // 90 Tage
    
    if (passwordAge > maxPasswordAge) {
      throw new UnauthorizedException('Password has expired, please change it');
    }

    // Multi-Factor Authentication prüfen
    if (user.mfaEnabled && !user.mfaVerified) {
      throw new UnauthorizedException('MFA verification required');
    }
  }

  private async validatePermissions(user: any, handler: Function, controllerClass: Function): Promise<void> {
    // Erforderliche Rollen aus Metadaten lesen
    const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [handler, controllerClass]);
    const requiredPermissions = this.reflector.getAllAndOverride<string[]>('permissions', [handler, controllerClass]);

    if (requiredRoles && requiredRoles.length > 0) {
      const hasRole = requiredRoles.some(role => user.roles.includes(role));
      if (!hasRole) {
        this.logger.warn(`Access denied for user ${user.id}: insufficient role (required: ${requiredRoles.join(', ')})`);
        throw new ForbiddenException('Insufficient privileges');
      }
    }

    if (requiredPermissions && requiredPermissions.length > 0) {
      const hasPermission = await this.securityService.userHasPermissions(user.id, requiredPermissions);
      if (!hasPermission) {
        this.logger.warn(`Access denied for user ${user.id}: insufficient permissions (required: ${requiredPermissions.join(', ')})`);
        throw new ForbiddenException('Insufficient permissions');
      }
    }
  }

  private async validateIPAccess(request: any, handler: Function): Promise<void> {
    const isRestrictedEndpoint = this.reflector.get<boolean>('restrictedIP', handler);
    
    if (isRestrictedEndpoint) {
      const clientIP = this.getClientIP(request);
      const isAllowed = await this.securityService.isIPAllowed(clientIP);
      
      if (!isAllowed) {
        this.logger.warn(`Access denied from IP: ${clientIP} (restricted endpoint)`);
        throw new ForbiddenException('Access from this IP is not allowed');
      }
    }
  }

  private async validateSession(user: any, request: any): Promise<void> {
    const sessionId = request.headers['x-session-id'];
    
    if (sessionId) {
      const isValidSession = await this.securityService.validateSession(user.id, sessionId);
      if (!isValidSession) {
        throw new UnauthorizedException('Invalid session');
      }
    }
  }

  private async logAccess(user: any, request: any, handler: Function): Promise<void> {
    const auditData = {
      userId: user.id,
      userEmail: user.email,
      action: `${request.method} ${request.url}`,
      ip: this.getClientIP(request),
      userAgent: request.headers['user-agent'],
      timestamp: new Date(),
      success: true,
    };

    await this.auditService.logAccess(auditData);
  }

  private extractTokenFromHeader(request: any): string | undefined {
    const authHeader = request.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      return authHeader.substring(7);
    }
    return undefined;
  }

  private getClientIP(request: any): string {
    // Verschiedene Möglichkeiten, die echte Client-IP zu erhalten
    return request.headers['x-forwarded-for']?.split(',')[0] ||
           request.headers['x-real-ip'] ||
           request.connection.remoteAddress ||
           request.socket.remoteAddress ||
           'unknown';
  }
}

Datenverschlüsselung ist ein weiterer kritischer Aspekt der Enterprise-Sicherheit. Denken Sie daran wie an einen Safe: Selbst wenn jemand Zugang zu Ihren Daten erhält, sind diese ohne den richtigen Schlüssel unbrauchbar.

// enterprise/security/src/services/encryption.service.ts
// Enterprise-grade Verschlüsselungsservice
import { Injectable, Logger } from '@nestjs/common';
import * as crypto from 'crypto';
import * as bcrypt from 'bcrypt';

@Injectable()
export class EncryptionService {
  private readonly logger = new Logger(EncryptionService.name);
  
  // AES-256-GCM für symmetrische Verschlüsselung
  private readonly algorithm = 'aes-256-gcm';
  private readonly keyLength = 32; // 256 bits
  private readonly ivLength = 16;  // 128 bits
  private readonly tagLength = 16; // 128 bits

  constructor() {
    // Validiere, dass erforderliche Umgebungsvariablen gesetzt sind
    if (!process.env.ENCRYPTION_KEY) {
      throw new Error('ENCRYPTION_KEY environment variable is required');
    }
  }

  /**
   * Verschlüsselt sensitive Daten für die Speicherung
   * Verwendet AES-256-GCM für authentifizierte Verschlüsselung
   */
  encryptSensitiveData(plaintext: string, additionalData?: string): string {
    try {
      const key = this.getDerivedKey();
      const iv = crypto.randomBytes(this.ivLength);
      
      const cipher = crypto.createCipher(this.algorithm, key);
      
      // Zusätzliche authentifizierte Daten setzen (falls vorhanden)
      if (additionalData) {
        cipher.setAAD(Buffer.from(additionalData, 'utf8'));
      }

      let encrypted = cipher.update(plaintext, 'utf8', 'hex');
      encrypted += cipher.final('hex');
      
      const tag = cipher.getAuthTag();

      // Format: iv:tag:encrypted (alle hex-encoded)
      return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`;

    } catch (error) {
      this.logger.error('Encryption failed:', error);
      throw new Error('Data encryption failed');
    }
  }

  /**
   * Entschlüsselt previously verschlüsselte Daten
   */
  decryptSensitiveData(encryptedData: string, additionalData?: string): string {
    try {
      const parts = encryptedData.split(':');
      if (parts.length !== 3) {
        throw new Error('Invalid encrypted data format');
      }

      const [ivHex, tagHex, encryptedHex] = parts;
      const key = this.getDerivedKey();
      const iv = Buffer.from(ivHex, 'hex');
      const tag = Buffer.from(tagHex, 'hex');

      const decipher = crypto.createDecipher(this.algorithm, key);
      decipher.setAuthTag(tag);
      
      if (additionalData) {
        decipher.setAAD(Buffer.from(additionalData, 'utf8'));
      }

      let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
      decrypted += decipher.final('utf8');

      return decrypted;

    } catch (error) {
      this.logger.error('Decryption failed:', error);
      throw new Error('Data decryption failed');
    }
  }

  /**
   * Hash-Passwörter mit bcrypt (Enterprise-Standard)
   */
  async hashPassword(password: string): Promise<string> {
    const saltRounds = parseInt(process.env.BCRYPT_ROUNDS, 10) || 12;
    return bcrypt.hash(password, saltRounds);
  }

  /**
   * Verifiziert Passwörter gegen Hashes
   */
  async verifyPassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
  }

  /**
   * Generiert kryptographisch sichere Zufallstoken
   */
  generateSecureToken(length: number = 32): string {
    return crypto.randomBytes(length).toString('hex');
  }

  /**
   * Erstellt HMAC-Signaturen für Datenintegrität
   */
  createHMAC(data: string, secret?: string): string {
    const key = secret || process.env.HMAC_SECRET || this.getDerivedKey().toString('hex');
    return crypto.createHmac('sha256', key).update(data).digest('hex');
  }

  /**
   * Verifiziert HMAC-Signaturen
   */
  verifyHMAC(data: string, signature: string, secret?: string): boolean {
    const expectedSignature = this.createHMAC(data, secret);
    
    // Timing-sichere Vergleich zur Vermeidung von Timing-Angriffen
    return crypto.timingSafeEqual(
      Buffer.from(signature, 'hex'),
      Buffer.from(expectedSignature, 'hex')
    );
  }

  private getDerivedKey(): Buffer {
    const masterKey = process.env.ENCRYPTION_KEY;
    const salt = process.env.ENCRYPTION_SALT || 'enterprise-app-salt';
    
    // PBKDF2 für Key-Derivation (OWASP-konform)
    return crypto.pbkdf2Sync(masterKey, salt, 100000, this.keyLength, 'sha256');
  }
}

30.4 Compliance und Auditing

Compliance in Enterprise-Umgebungen ist wie das Führen eines detaillierten Tagebuchs über alle wichtigen Ereignisse in Ihrem Leben. Regulatorische Behörden, interne Auditoren und Sicherheitsexperten müssen nachvollziehen können, wer wann was gemacht hat und warum. Dies ist nicht nur eine rechtliche Anforderung, sondern auch ein wichtiges Werkzeug für die Sicherheit und Qualitätssicherung.

Stellen Sie sich vor, Sie müssten beweisen, dass Ihre Anwendung alle Datenschutzbestimmungen der DSGVO einhält. Dazu benötigen Sie lückenlose Aufzeichnungen über alle Datenverarbeitungsvorgänge, Benutzeraktivitäten und Systemereignisse. Ein gut implementiertes Audit-System ist wie ein digitaler Detektiv, der alle wichtigen Spuren sammelt und sicher aufbewahrt.

// enterprise/compliance/src/audit/audit.service.ts
// Umfassendes Audit-System für Enterprise-Compliance
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuditLog } from './entities/audit-log.entity';
import { EncryptionService } from '../security/encryption.service';

export enum AuditEventType {
  USER_LOGIN = 'USER_LOGIN',
  USER_LOGOUT = 'USER_LOGOUT',
  DATA_ACCESS = 'DATA_ACCESS',
  DATA_MODIFICATION = 'DATA_MODIFICATION',
  DATA_DELETION = 'DATA_DELETION',
  PERMISSION_CHANGE = 'PERMISSION_CHANGE',
  SECURITY_EVENT = 'SECURITY_EVENT',
  SYSTEM_ERROR = 'SYSTEM_ERROR',
  GDPR_REQUEST = 'GDPR_REQUEST',
  COMPLIANCE_CHECK = 'COMPLIANCE_CHECK',
}

export interface AuditEventData {
  eventType: AuditEventType;
  userId?: string;
  userEmail?: string;
  entityType?: string;
  entityId?: string;
  oldValues?: Record<string, any>;
  newValues?: Record<string, any>;
  ipAddress?: string;
  userAgent?: string;
  sessionId?: string;
  details?: Record<string, any>;
  riskLevel?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
  complianceRelevant?: boolean;
}

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

  constructor(
    @InjectRepository(AuditLog)
    private readonly auditRepository: Repository<AuditLog>,
    private readonly encryptionService: EncryptionService,
  ) {}

  /**
   * Erstellt einen Audit-Log-Eintrag für Compliance-Zwecke
   */
  async logEvent(eventData: AuditEventData): Promise<void> {
    try {
      const auditLog = new AuditLog();
      
      // Basis-Informationen
      auditLog.eventType = eventData.eventType;
      auditLog.timestamp = new Date();
      auditLog.userId = eventData.userId;
      auditLog.userEmail = eventData.userEmail;
      auditLog.ipAddress = eventData.ipAddress;
      auditLog.userAgent = eventData.userAgent;
      auditLog.sessionId = eventData.sessionId;
      auditLog.riskLevel = eventData.riskLevel || 'LOW';
      auditLog.complianceRelevant = eventData.complianceRelevant || false;

      // Sensitive Daten verschlüsseln (GDPR-Konformität)
      if (eventData.oldValues) {
        auditLog.oldValuesEncrypted = this.encryptionService.encryptSensitiveData(
          JSON.stringify(eventData.oldValues),
          `audit:${auditLog.eventType}:old`
        );
      }

      if (eventData.newValues) {
        auditLog.newValuesEncrypted = this.encryptionService.encryptSensitiveData(
          JSON.stringify(eventData.newValues),
          `audit:${auditLog.eventType}:new`
        );
      }

      if (eventData.details) {
        auditLog.detailsEncrypted = this.encryptionService.encryptSensitiveData(
          JSON.stringify(eventData.details),
          `audit:${auditLog.eventType}:details`
        );
      }

      // Entity-Informationen
      auditLog.entityType = eventData.entityType;
      auditLog.entityId = eventData.entityId;

      // Integrity Hash für Tamper-Detection
      auditLog.integrityHash = this.calculateIntegrityHash(auditLog);

      await this.auditRepository.save(auditLog);

      // Bei kritischen Ereignissen zusätzliche Maßnahmen
      if (eventData.riskLevel === 'CRITICAL') {
        await this.handleCriticalEvent(auditLog);
      }

      this.logger.debug(`Audit event logged: ${eventData.eventType}`, {
        userId: eventData.userId,
        eventType: eventData.eventType,
        riskLevel: eventData.riskLevel,
      });

    } catch (error) {
      this.logger.error('Failed to log audit event:', error);
      // Audit-Fehler dürfen die Hauptfunktionalität nicht beeinträchtigen
      // aber sollten überwacht werden
      await this.handleAuditFailure(eventData, error);
    }
  }

  /**
   * Spezielle Audit-Methode für GDPR-relevante Ereignisse
   */
  async logGDPREvent(eventData: Omit<AuditEventData, 'eventType' | 'complianceRelevant'> & {
    gdprArticle?: string;
    legalBasis?: string;
    dataSubjectId?: string;
  }): Promise<void> {
    await this.logEvent({
      ...eventData,
      eventType: AuditEventType.GDPR_REQUEST,
      complianceRelevant: true,
      riskLevel: 'HIGH',
      details: {
        ...eventData.details,
        gdprArticle: eventData.gdprArticle,
        legalBasis: eventData.legalBasis,
        dataSubjectId: eventData.dataSubjectId,
      },
    });
  }

  /**
   * Erstellt Compliance-Reports für Auditoren
   */
  async generateComplianceReport(
    startDate: Date,
    endDate: Date,
    eventTypes?: AuditEventType[]
  ): Promise<any> {
    const query = this.auditRepository
      .createQueryBuilder('audit')
      .where('audit.timestamp BETWEEN :startDate AND :endDate', {
        startDate,
        endDate,
      })
      .andWhere('audit.complianceRelevant = :complianceRelevant', {
        complianceRelevant: true,
      });

    if (eventTypes && eventTypes.length > 0) {
      query.andWhere('audit.eventType IN (:...eventTypes)', { eventTypes });
    }

    const auditLogs = await query
      .orderBy('audit.timestamp', 'DESC')
      .getMany();

    // Statistiken berechnen
    const statistics = {
      totalEvents: auditLogs.length,
      eventsByType: this.groupEventsByType(auditLogs),
      eventsByRiskLevel: this.groupEventsByRiskLevel(auditLogs),
      uniqueUsers: new Set(auditLogs.map(log => log.userId)).size,
      dateRange: { startDate, endDate },
    };

    return {
      statistics,
      events: auditLogs.map(log => this.sanitizeAuditLogForReport(log)),
      generatedAt: new Date(),
      generatedBy: 'system', // In echten Anwendungen: aktueller Benutzer
    };
  }

  /**
   * Überprüft die Integrität der Audit-Logs
   */
  async verifyAuditIntegrity(startDate?: Date, endDate?: Date): Promise<{
    valid: boolean;
    corruptedLogs: string[];
    totalChecked: number;
  }> {
    const query = this.auditRepository.createQueryBuilder('audit');
    
    if (startDate && endDate) {
      query.where('audit.timestamp BETWEEN :startDate AND :endDate', {
        startDate,
        endDate,
      });
    }

    const logs = await query.getMany();
    const corruptedLogs: string[] = [];

    for (const log of logs) {
      const calculatedHash = this.calculateIntegrityHash(log);
      if (calculatedHash !== log.integrityHash) {
        corruptedLogs.push(log.id);
      }
    }

    return {
      valid: corruptedLogs.length === 0,
      corruptedLogs,
      totalChecked: logs.length,
    };
  }

  private calculateIntegrityHash(auditLog: Partial<AuditLog>): string {
    const dataToHash = [
      auditLog.eventType,
      auditLog.timestamp?.toISOString(),
      auditLog.userId,
      auditLog.entityType,
      auditLog.entityId,
      auditLog.oldValuesEncrypted,
      auditLog.newValuesEncrypted,
      auditLog.detailsEncrypted,
    ].join('|');

    return this.encryptionService.createHMAC(dataToHash);
  }

  private async handleCriticalEvent(auditLog: AuditLog): Promise<void> {
    // Bei kritischen Ereignissen zusätzliche Sicherheitsmaßnahmen
    this.logger.warn(`Critical security event detected: ${auditLog.eventType}`, {
      userId: auditLog.userId,
      ipAddress: auditLog.ipAddress,
      timestamp: auditLog.timestamp,
    });

    // TODO: Implementieren Sie hier zusätzliche Maßnahmen wie:
    // - Benachrichtigung des Sicherheitsteams
    // - Automatische Sperrung verdächtiger Benutzer
    // - Erhöhte Überwachung
  }

  private async handleAuditFailure(eventData: AuditEventData, error: Error): Promise<void> {
    // Fallback-Logging bei Audit-Fehlern
    this.logger.error('Audit system failure - using fallback logging', {
      eventType: eventData.eventType,
      userId: eventData.userId,
      error: error.message,
      timestamp: new Date(),
    });

    // TODO: Implementieren Sie hier Fallback-Mechanismen wie:
    // - Logging in Datei-System
    // - Benachrichtigung der Administratoren
    // - Temporäre Speicherung in alternativer Datenbank
  }

  private groupEventsByType(logs: AuditLog[]): Record<string, number> {
    return logs.reduce((acc, log) => {
      acc[log.eventType] = (acc[log.eventType] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
  }

  private groupEventsByRiskLevel(logs: AuditLog[]): Record<string, number> {
    return logs.reduce((acc, log) => {
      acc[log.riskLevel] = (acc[log.riskLevel] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
  }

  private sanitizeAuditLogForReport(log: AuditLog): any {
    // Entfernt verschlüsselte Daten aus Reports (nur für interne Auditoren)
    return {
      id: log.id,
      eventType: log.eventType,
      timestamp: log.timestamp,
      userId: log.userId,
      userEmail: log.userEmail,
      entityType: log.entityType,
      entityId: log.entityId,
      ipAddress: log.ipAddress,
      riskLevel: log.riskLevel,
      complianceRelevant: log.complianceRelevant,
      // Verschlüsselte Felder werden für Reports ausgelassen
    };
  }
}

30.5 Multi-Tenant Architectures

Multi-Tenancy ist wie das Betreiben eines Apartmentgebäudes, in dem jeder Mieter seine eigene private Wohnung hat, aber alle dieselbe Infrastruktur (Wasser, Strom, Internet) teilen. In der Software-Welt bedeutet das, dass eine einzige Anwendungsinstanz mehrere Kunden (Tenants) bedient, wobei jeder Kunde seine eigenen Daten und Konfigurationen hat, aber die zugrunde liegende Infrastruktur geteilt wird.

Diese Architektur ist besonders in Enterprise-Umgebungen wichtig, da sie es ermöglicht, Kosten zu reduzieren, Wartung zu vereinfachen und trotzdem Datenisolation und kundenspezifische Anpassungen zu bieten. Denken Sie an Software-as-a-Service-Plattformen wie Salesforce oder Slack - diese bedienen Millionen von Kunden mit derselben Anwendung, aber jeder Kunde sieht nur seine eigenen Daten.

// enterprise/multi-tenant/src/tenant/tenant.service.ts
// Multi-Tenant-Management für Enterprise-Anwendungen
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { Tenant } from './entities/tenant.entity';
import { TenantConfig } from './entities/tenant-config.entity';

export interface TenantContext {
  tenantId: string;
  tenantName: string;
  subdomain: string;
  config: Record<string, any>;
  features: string[];
  plan: 'basic' | 'professional' | 'enterprise';
  isActive: boolean;
}

@Injectable()
export class TenantService {
  private readonly logger = new Logger(TenantService.name);
  
  // Cache für Tenant-Informationen (Performance-Optimierung)
  private readonly tenantCache = new Map<string, TenantContext>();
  private readonly cacheTimeout = 5 * 60 * 1000; // 5 Minuten

  constructor(
    @InjectRepository(Tenant)
    private readonly tenantRepository: Repository<Tenant>,
    @InjectRepository(TenantConfig)
    private readonly tenantConfigRepository: Repository<TenantConfig>,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Identifiziert den Tenant basierend auf der Anfrage
   * Unterstützt verschiedene Identifikationsmethoden
   */
  async identifyTenant(request: any): Promise<TenantContext> {
    let tenantId: string | null = null;

    // Methode 1: Subdomain-basierte Identifikation
    const host = request.headers.host;
    if (host) {
      const subdomain = host.split('.')[0];
      if (subdomain && subdomain !== 'www' && subdomain !== 'api') {
        const tenant = await this.getTenantBySubdomain(subdomain);
        if (tenant) {
          tenantId = tenant.tenantId;
        }
      }
    }

    // Methode 2: Header-basierte Identifikation
    if (!tenantId) {
      tenantId = request.headers['x-tenant-id'];
    }

    // Methode 3: Query-Parameter (für APIs)
    if (!tenantId) {
      tenantId = request.query.tenantId;
    }

    // Methode 4: JWT-Token (falls Benutzer eingeloggt)
    if (!tenantId && request.user) {
      tenantId = request.user.tenantId;
    }

    if (!tenantId) {
      throw new NotFoundException('Tenant not identified');
    }

    return this.getTenantContext(tenantId);
  }

  /**
   * Lädt Tenant-Kontext mit Caching für Performance
   */
  async getTenantContext(tenantId: string): Promise<TenantContext> {
    // Cache prüfen
    const cached = this.tenantCache.get(tenantId);
    if (cached && this.isCacheValid(tenantId)) {
      return cached;
    }

    // Tenant-Daten aus Datenbank laden
    const tenant = await this.tenantRepository
      .createQueryBuilder('tenant')
      .leftJoinAndSelect('tenant.config', 'config')
      .where('tenant.id = :tenantId', { tenantId })
      .getOne();

    if (!tenant) {
      throw new NotFoundException(`Tenant ${tenantId} not found`);
    }

    if (!tenant.isActive) {
      throw new NotFoundException(`Tenant ${tenantId} is not active`);
    }

    // Tenant-Kontext zusammenbauen
    const context: TenantContext = {
      tenantId: tenant.id,
      tenantName: tenant.name,
      subdomain: tenant.subdomain,
      config: this.mergeConfigs(tenant.config),
      features: tenant.features || [],
      plan: tenant.plan,
      isActive: tenant.isActive,
    };

    // In Cache speichern
    this.tenantCache.set(tenantId, context);
    
    return context;
  }

  /**
   * Erstellt datenbankverbindung für spezifischen Tenant
   * Unterstützt sowohl Schema-Separation als auch Database-Separation
   */
  async getTenantDataSource(tenantId: string): Promise<DataSource> {
    const tenant = await this.getTenantContext(tenantId);
    
    // Für Enterprise-Kunden: Separate Datenbank
    if (tenant.plan === 'enterprise' && tenant.config.dedicatedDatabase) {
      return this.createTenantSpecificDataSource(tenant);
    }
    
    // Für andere Pläne: Schema-Separation
    return this.dataSource;
  }

  /**
   * Wendet Tenant-spezifische Datenbankfilter an
   */
  applyTenantFilter(queryBuilder: any, tenantId: string, alias?: string): void {
    const columnName = alias ? `${alias}.tenantId` : 'tenantId';
    queryBuilder.andWhere(`${columnName} = :tenantId`, { tenantId });
  }

  /**
   * Validates ob Benutzer Zugriff auf bestimmte Features hat
   */
  async hasFeature(tenantId: string, feature: string): Promise<boolean> {
    const context = await this.getTenantContext(tenantId);
    return context.features.includes(feature);
  }

  /**
   * Wendet Tenant-spezifische Konfiguration an
   */
  async applyTenantConfig(tenantId: string, defaultConfig: Record<string, any>): Promise<Record<string, any>> {
    const context = await this.getTenantContext(tenantId);
    
    return {
      ...defaultConfig,
      ...context.config,
      // Tenant-spezifische Overrides
      tenantId,
      tenantName: context.tenantName,
    };
  }

  private async getTenantBySubdomain(subdomain: string): Promise<Tenant | null> {
    return this.tenantRepository.findOne({
      where: { subdomain, isActive: true },
    });
  }

  private createTenantSpecificDataSource(tenant: TenantContext): DataSource {
    // Diese Methode würde eine neue DataSource für den Tenant erstellen
    // mit seinen spezifischen Datenbankverbindungsdetails
    return new DataSource({
      type: 'postgres',
      host: tenant.config.databaseHost || process.env.DB_HOST,
      port: tenant.config.databasePort || parseInt(process.env.DB_PORT, 10),
      username: tenant.config.databaseUsername || process.env.DB_USERNAME,
      password: tenant.config.databasePassword || process.env.DB_PASSWORD,
      database: tenant.config.databaseName || `tenant_${tenant.tenantId}`,
      entities: ['dist/**/*.entity.js'],
      synchronize: false, // Niemals in Production
      logging: ['error', 'warn'],
    });
  }

  private mergeConfigs(configs: TenantConfig[]): Record<string, any> {
    const merged: Record<string, any> = {};
    
    for (const config of configs || []) {
      merged[config.key] = this.parseConfigValue(config.value, config.type);
    }
    
    return merged;
  }

  private parseConfigValue(value: string, type: string): any {
    switch (type) {
      case 'boolean':
        return value === 'true';
      case 'number':
        return parseInt(value, 10);
      case 'json':
        return JSON.parse(value);
      default:
        return value;
    }
  }

  private isCacheValid(tenantId: string): boolean {
    // Vereinfachte Cache-Validierung
    // In einer echten Implementierung würden Sie Timestamps verfolgen
    return true;
  }
}

Ein wichtiger Aspekt der Multi-Tenancy ist das Routing und die Middleware, die sicherstellt, dass jede Anfrage im richtigen Tenant-Kontext verarbeitet wird:

// enterprise/multi-tenant/src/middleware/tenant.middleware.ts
// Middleware für automatische Tenant-Erkennung und -Isolation
import { Injectable, NestMiddleware, Logger, BadRequestException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { TenantService } from '../tenant/tenant.service';

// Erweitere Express Request um Tenant-Informationen
declare global {
  namespace Express {
    interface Request {
      tenant?: {
        id: string;
        name: string;
        config: Record<string, any>;
        features: string[];
        plan: string;
      };
    }
  }
}

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

  constructor(private readonly tenantService: TenantService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    try {
      // Tenant identifizieren
      const tenantContext = await this.tenantService.identifyTenant(req);
      
      // Tenant-Informationen an Request anhängen
      req.tenant = {
        id: tenantContext.tenantId,
        name: tenantContext.tenantName,
        config: tenantContext.config,
        features: tenantContext.features,
        plan: tenantContext.plan,
      };

      // Tenant-spezifische HTTP-Headers setzen
      res.setHeader('X-Tenant-ID', tenantContext.tenantId);
      res.setHeader('X-Tenant-Name', tenantContext.tenantName);
      
      // Feature-Flags als Header (für Frontend-Entwicklung)
      res.setHeader('X-Tenant-Features', tenantContext.features.join(','));

      this.logger.debug(`Request processed for tenant: ${tenantContext.tenantId}`, {
        tenantId: tenantContext.tenantId,
        path: req.path,
        method: req.method,
      });

      next();

    } catch (error) {
      this.logger.error('Tenant identification failed:', error);
      
      if (error instanceof NotFoundException) {
        throw new BadRequestException('Invalid or missing tenant information');
      }
      
      throw error;
    }
  }
}

30.6 Legacy System Integration

Legacy-System-Integration ist wie das Renovieren eines historischen Gebäudes, während Menschen darin wohnen. Sie müssen moderne Annehmlichkeiten hinzufügen, ohne die bestehende Struktur zu zerstören oder den Betrieb zu unterbrechen. In Enterprise-Umgebungen ist dies eine der häufigsten und herausforderndsten Aufgaben.

Viele Unternehmen haben über Jahrzehnte gewachsene IT-Landschaften mit einer Mischung aus mainframe-basierten Systemen, älteren Web-Anwendungen und modernen Cloud-Services. NestJS muss sich nahtlos in diese heterogene Umgebung einfügen und als Brücke zwischen alter und neuer Technologie fungieren.

// enterprise/integration/src/legacy/legacy-adapter.service.ts
// Adapter für die Integration mit Legacy-Systemen
import { Injectable, Logger } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import * as xml2js from 'xml2js';

export interface LegacySystemConfig {
  name: string;
  baseUrl: string;
  authMethod: 'basic' | 'token' | 'certificate' | 'soap-header';
  timeout: number;
  retryAttempts: number;
  format: 'json' | 'xml' | 'soap' | 'fixed-width';
  encoding?: string;
}

@Injectable()
export class LegacyAdapterService {
  private readonly logger = new Logger(LegacyAdapterService.name);
  
  // XML-Parser für SOAP und XML-APIs
  private readonly xmlParser = new xml2js.Parser({
    explicitArray: false,
    normalizeTags: true,
    mergeAttrs: true,
  });

  constructor(private readonly httpService: HttpService) {}

  /**
   * Universeller Adapter für verschiedene Legacy-System-Formate
   */
  async callLegacySystem<T>(
    systemConfig: LegacySystemConfig,
    endpoint: string,
    data?: any,
    method: 'GET' | 'POST' | 'PUT' = 'GET'
  ): Promise<T> {
    const startTime = Date.now();
    
    try {
      this.logger.debug(`Calling legacy system: ${systemConfig.name}/${endpoint}`, {
        system: systemConfig.name,
        endpoint,
        method,
      });

      let response;
      
      switch (systemConfig.format) {
        case 'soap':
          response = await this.callSOAPService(systemConfig, endpoint, data);
          break;
        case 'xml':
          response = await this.callXMLService(systemConfig, endpoint, data, method);
          break;
        case 'fixed-width':
          response = await this.callFixedWidthService(systemConfig, endpoint, data, method);
          break;
        default:
          response = await this.callJSONService(systemConfig, endpoint, data, method);
      }

      const duration = Date.now() - startTime;
      this.logger.debug(`Legacy system call completed: ${systemConfig.name}/${endpoint} (${duration}ms)`);

      return response;

    } catch (error) {
      const duration = Date.now() - startTime;
      this.logger.error(`Legacy system call failed: ${systemConfig.name}/${endpoint} (${duration}ms)`, error);
      
      // Fehler-Transformation für einheitliche Behandlung
      throw this.transformLegacyError(error, systemConfig);
    }
  }

  /**
   * SOAP-Service-Aufrufe (häufig in Enterprise-Umgebungen)
   */
  private async callSOAPService(config: LegacySystemConfig, action: string, data: any): Promise<any> {
    const soapEnvelope = this.buildSOAPEnvelope(action, data);
    
    const headers = {
      'Content-Type': 'text/xml; charset=utf-8',
      'SOAPAction': action,
      ...this.buildAuthHeaders(config),
    };

    const response = await firstValueFrom(
      this.httpService.post(config.baseUrl, soapEnvelope, {
        headers,
        timeout: config.timeout,
        responseType: 'text',
      })
    );

    // SOAP-Response parsen
    const parsed = await this.xmlParser.parseStringPromise(response.data);
    return this.extractSOAPBody(parsed);
  }

  /**
   * XML-API-Aufrufe
   */
  private async callXMLService(
    config: LegacySystemConfig,
    endpoint: string,
    data: any,
    method: string
  ): Promise<any> {
    const url = `${config.baseUrl}/${endpoint}`;
    const headers = {
      'Content-Type': 'application/xml',
      'Accept': 'application/xml',
      ...this.buildAuthHeaders(config),
    };

    let requestData: string | undefined;
    if (data && (method === 'POST' || method === 'PUT')) {
      requestData = this.objectToXML(data);
    }

    const response = await firstValueFrom(
      this.httpService.request({
        method,
        url,
        headers,
        data: requestData,
        timeout: config.timeout,
        responseType: 'text',
      })
    );

    // XML-Response zu JSON konvertieren
    const parsed = await this.xmlParser.parseStringPromise(response.data);
    return parsed;
  }

  /**
   * Fixed-Width-Format (oft bei Mainframe-Systemen)
   */
  private async callFixedWidthService(
    config: LegacySystemConfig,
    endpoint: string,
    data: any,
    method: string
  ): Promise<any> {
    const url = `${config.baseUrl}/${endpoint}`;
    const headers = {
      'Content-Type': 'text/plain',
      ...this.buildAuthHeaders(config),
    };

    let requestData: string | undefined;
    if (data && (method === 'POST' || method === 'PUT')) {
      requestData = this.objectToFixedWidth(data);
    }

    const response = await firstValueFrom(
      this.httpService.request({
        method,
        url,
        headers,
        data: requestData,
        timeout: config.timeout,
        responseType: 'text',
        responseEncoding: config.encoding || 'utf8',
      })
    );

    // Fixed-Width-Response parsen
    return this.parseFixedWidthResponse(response.data);
  }

  /**
   * Standard JSON-API-Aufrufe
   */
  private async callJSONService(
    config: LegacySystemConfig,
    endpoint: string,
    data: any,
    method: string
  ): Promise<any> {
    const url = `${config.baseUrl}/${endpoint}`;
    const headers = {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      ...this.buildAuthHeaders(config),
    };

    const response = await firstValueFrom(
      this.httpService.request({
        method,
        url,
        headers,
        data,
        timeout: config.timeout,
      })
    );

    return response.data;
  }

  private buildAuthHeaders(config: LegacySystemConfig): Record<string, string> {
    switch (config.authMethod) {
      case 'basic':
        const credentials = Buffer.from(
          `${process.env[`${config.name.toUpperCase()}_USERNAME`]}:${process.env[`${config.name.toUpperCase()}_PASSWORD`]}`
        ).toString('base64');
        return { 'Authorization': `Basic ${credentials}` };
        
      case 'token':
        const token = process.env[`${config.name.toUpperCase()}_TOKEN`];
        return { 'Authorization': `Bearer ${token}` };
        
      case 'soap-header':
        // SOAP-spezifische Auth-Header werden im SOAP-Envelope behandelt
        return {};
        
      default:
        return {};
    }
  }

  private buildSOAPEnvelope(action: string, data: any): string {
    const bodyContent = this.objectToXML(data, false);
    
    return `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <${action} xmlns="http://tempuri.org/">
      ${bodyContent}
    </${action}>
  </soap:Body>
</soap:Envelope>`;
  }

  private extractSOAPBody(parsed: any): any {
    // Navigiere durch SOAP-Struktur zur eigentlichen Antwort
    const envelope = parsed['soap:envelope'] || parsed.envelope;
    const body = envelope['soap:body'] || envelope.body;
    
    // Extrahiere erste Eigenschaft aus Body (die eigentliche Response)
    const responseKey = Object.keys(body)[0];
    return body[responseKey];
  }

  private objectToXML(obj: any, includeRoot: boolean = true): string {
    const builder = new xml2js.Builder({
      rootName: includeRoot ? 'data' : undefined,
      headless: !includeRoot,
    });
    return builder.buildObject(obj);
  }

  private objectToFixedWidth(obj: any): string {
    // Vereinfachte Fixed-Width-Formatierung
    // In einer echten Implementierung würden Sie Schema-Definitionen verwenden
    const lines: string[] = [];
    
    for (const [key, value] of Object.entries(obj)) {
      const line = `${key.padEnd(20)}${String(value).padEnd(30)}`;
      lines.push(line);
    }
    
    return lines.join('\n');
  }

  private parseFixedWidthResponse(response: string): any {
    // Vereinfachte Fixed-Width-Parsing
    // In einer echten Implementierung würden Sie Schema-Definitionen verwenden
    const lines = response.split('\n');
    const result: any = {};
    
    for (const line of lines) {
      if (line.trim()) {
        const key = line.substring(0, 20).trim();
        const value = line.substring(20).trim();
        result[key] = value;
      }
    }
    
    return result;
  }

  private transformLegacyError(error: any, config: LegacySystemConfig): Error {
    // Transformiere verschiedene Fehlerformate in einheitliche Struktur
    let message = `Legacy system error (${config.name}): `;
    
    if (error.response) {
      message += `HTTP ${error.response.status} - ${error.response.statusText}`;
    } else if (error.code) {
      message += `${error.code} - ${error.message}`;
    } else {
      message += error.message || 'Unknown error';
    }
    
    const transformedError = new Error(message);
    transformedError.name = 'LegacySystemError';
    
    return transformedError;
  }
}

Diese umfassende Betrachtung von NestJS im Enterprise-Bereich zeigt, dass erfolgreiche Enterprise-Software mehr ist als nur funktionaler Code. Sie erfordert durchdachte Architektur, rigorose Sicherheitsmaßnahmen, lückenlose Compliance und die Fähigkeit, mit bestehenden Systemen zu integrieren. NestJS bietet die Werkzeuge und die Flexibilität, all diese Anforderungen zu erfüllen, aber der Schlüssel liegt in der intelligenten Anwendung dieser Möglichkeiten.

Denken Sie daran: Enterprise-Software ist ein Marathon, kein Sprint. Die Entscheidungen, die Sie heute treffen, werden die Wartbarkeit, Skalierbarkeit und Sicherheit Ihrer Anwendung für Jahre bestimmen. Mit NestJS als solider Grundlage und den in diesem Kapitel vorgestellten Patterns und Practices sind Sie gut gerüstet für diese Herausforderung.