23 Microservices mit NestJS

NestJS bietet hervorragende Unterstützung für die Entwicklung von Microservices-Architekturen durch ein umfassendes Set von Tools und Abstraktionen, die die Komplexität verteilter Systeme reduzieren.

23.1 Microservices Architecture

In einer Microservices-Architektur wird eine Anwendung in kleine, unabhängige Services aufgeteilt, die über Netzwerkschnittstellen kommunizieren. NestJS unterstützt dieses Paradigma durch:

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USER_SERVICE',
        transport: Transport.TCP,
        options: { host: 'localhost', port: 8877 }
      }
    ])
  ]
})
export class AppModule {}

23.2 Message Patterns

NestJS verwendet Message Patterns für die Kommunikation zwischen Services:

// Service (Provider)
@Controller()
export class UserController {
  @MessagePattern({ cmd: 'get_user' })
  getUser(data: { id: number }) {
    return this.userService.findById(data.id);
  }

  @EventPattern('user_created')
  handleUserCreated(data: Record<string, unknown>) {
    console.log('User created:', data);
  }
}

// Client
@Injectable()
export class ApiService {
  constructor(
    @Inject('USER_SERVICE') private client: ClientProxy
  ) {}

  async getUser(id: number) {
    return this.client.send({ cmd: 'get_user' }, { id });
  }

  emitUserCreated(userData: any) {
    this.client.emit('user_created', userData);
  }
}

23.3 Transport Layer

NestJS unterstützt verschiedene Transport-Mechanismen:

23.3.1 TCP Transport

const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.TCP,
  options: { host: 'localhost', port: 8877 }
});

23.3.2 Redis Transport

const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.REDIS,
  options: {
    host: 'localhost',
    port: 6379,
  }
});

23.3.3 NATS Transport

const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.NATS,
  options: {
    servers: ['nats://localhost:4222']
  }
});

23.4 Service Discovery

Für dynamische Service-Erkennung können Sie Service Discovery Pattern implementieren:

@Injectable()
export class ServiceRegistry {
  private services = new Map<string, string>();

  registerService(name: string, url: string) {
    this.services.set(name, url);
  }

  getService(name: string): string | undefined {
    return this.services.get(name);
  }
}

23.5 Circuit Breaker Pattern

Das Circuit Breaker Pattern verhindert Kaskadenausfälle:

@Injectable()
export class CircuitBreakerService {
  private failures = 0;
  private readonly threshold = 5;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';

  async callService<T>(serviceCall: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      throw new Error('Circuit breaker is OPEN');
    }

    try {
      const result = await serviceCall();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }

  private onFailure() {
    this.failures++;
    if (this.failures >= this.threshold) {
      this.state = 'OPEN';
    }
  }
}

23.6 Distributed Logging

Für verteiltes Logging verwenden Sie Correlation IDs:

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const correlationId = uuidv4();
    const request = context.switchToHttp().getRequest();
    request.correlationId = correlationId;

    return next.handle().pipe(
      tap(() => {
        this.logger.log(`Request processed: ${correlationId}`);
      })
    );
  }
}

23.7 API Gateway Pattern

Ein API Gateway fungiert als zentrale Schnittstelle:

@Controller('api')
export class GatewayController {
  constructor(
    @Inject('USER_SERVICE') private userService: ClientProxy,
    @Inject('ORDER_SERVICE') private orderService: ClientProxy
  ) {}

  @Get('users/:id')
  async getUser(@Param('id') id: string) {
    return this.userService.send({ cmd: 'get_user' }, { id });
  }

  @Get('orders/:userId')
  async getUserOrders(@Param('userId') userId: string) {
    return this.orderService.send({ cmd: 'get_orders' }, { userId });
  }

  @Get('users/:id/profile')
  async getUserProfile(@Param('id') id: string) {
    const user = await firstValueFrom(
      this.userService.send({ cmd: 'get_user' }, { id })
    );
    const orders = await firstValueFrom(
      this.orderService.send({ cmd: 'get_orders' }, { userId: id })
    );
    
    return { user, orders };
  }
}

Die Microservices-Architektur mit NestJS ermöglicht es, skalierbare und wartbare verteilte Systeme zu entwickeln, wobei jeder Service klar definierte Verantwortlichkeiten hat und unabhängig entwickelt werden kann.