Chuyển đến nội dung chính

レッスン 5: プロバイダーと依存関係の注入

プロバイダー、サービス、依存性注入コンテナーについての深い理解。カスタムプロバイダー、useClass、useValue、useFactory、useExisting。インジェクションスコープ。

💻 プログラミング — レッスン 5 レッスン 5: プロバイダーと依存関係の注入

NestJS: 基本から高度まで

パート 2: プロバイダー、依存関係の注入、およびデータ層

xdev.asia

1. プロバイダーとは何ですか?

プロバイダーは NestJS の中核となる概念です。任意のクラスがマークされます @Injectable() サービス、リポジトリ、ファクトリー、ヘルパーなど、すべてがプロバイダーになることができます。主なアイデア: プロバイダーは次のことができます。 注射する。注入する コンストラクターを通じて他のクラスに接続し、関係を作成します 疎結合。

import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat): Cat {
    this.cats.push(cat);
    return cat;
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

2. NestJS での依存性注入 (DI)

NestJS には、依存関係の作成と注入を自動的に管理する IoC (Inversion of Control) コンテナが組み込まれています。

@Controller('cats')
export class CatsController {
  // NestJS IoC container tự động:
  // 1. Tìm provider CatsService trong module
  // 2. Tạo instance (hoặc lấy singleton có sẵn)
  // 3. Inject vào constructor parameter
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }
}

DIフロー

1. @Module({ providers: [CatsService] })
   → NestJS đăng ký CatsService vào IoC container

2. constructor(private readonly catsService: CatsService)
   → NestJS nhận biết CatsController cần CatsService

3. NestJS resolve dependency:
   → Kiểm tra CatsService đã tồn tại trong container?
     → Có: inject instance đã có (singleton)
     → Không: tạo instance mới, lưu vào container, inject

3. カスタムプロバイダー

useClass — スワップ実装

// Interface
interface Logger {
  log(message: string): void;
}

// Implementations
@Injectable()
class ConsoleLogger implements Logger {
  log(message: string) { console.log(message); }
}

@Injectable()
class FileLogger implements Logger {
  log(message: string) { /* write to file */ }
}

// Module — dễ dàng swap
@Module({
  providers: [
    {
      provide: 'LOGGER',
      useClass: process.env.NODE_ENV === 'production' 
        ? FileLogger 
        : ConsoleLogger,
    },
  ],
})
export class AppModule {}

// Inject bằng @Inject() token
@Injectable()
export class AppService {
  constructor(@Inject('LOGGER') private logger: Logger) {}
}

useValue — 定数またはモックを挿入する

// Inject config values
const DATABASE_CONFIG = {
  host: 'localhost',
  port: 5432,
  database: 'mydb',
};

@Module({
  providers: [
    { provide: 'DATABASE_CONFIG', useValue: DATABASE_CONFIG },
    { provide: 'API_KEY', useValue: 'sk-xxx-12345' },
  ],
})
export class AppModule {}

// Inject mock cho testing
const mockCatsService = {
  findAll: () => [{ name: 'Test Cat' }],
  create: (cat) => cat,
};

// Trong test
Test.createTestingModule({
  providers: [
    { provide: CatsService, useValue: mockCatsService },
  ],
});

useFactory — 複雑なロジック、非同期

@Module({
  providers: [
    ConfigService,
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async (configService: ConfigService) => {
        const options = configService.get('database');
        const connection = await createConnection(options);
        return connection;
      },
      inject: [ConfigService],  // Dependencies cho factory
    },
  ],
})
export class DatabaseModule {}

// Factory với multiple dependencies
{
  provide: 'MAILER',
  useFactory: (config: ConfigService, logger: LoggerService) => {
    return new MailerService(
      config.get('SMTP_HOST'),
      config.get('SMTP_PORT'),
      logger,
    );
  },
  inject: [ConfigService, LoggerService],
}

useExisting — エイリアスプロバイダー

@Module({
  providers: [
    CatsService,
    // 'AliasedCatsService' trỏ tới cùng instance CatsService
    { provide: 'AliasedCatsService', useExisting: CatsService },
  ],
})
export class CatsModule {}

4. インジェクションスコープ

import { Injectable, Scope } from '@nestjs/common';

// DEFAULT — Singleton (một instance cho toàn app)
@Injectable({ scope: Scope.DEFAULT })
export class SingletonService {}

// REQUEST — Mỗi request tạo instance mới
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
  constructor(@Inject(REQUEST) private request: Request) {
    // Truy cập request object
  }
}

// TRANSIENT — Mỗi lần inject tạo instance mới
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {}

重要な注意事項: スコープは 泡立つ。コントローラーが REQUEST スコープのサービスを挿入すると、コントローラーも REQUEST スコープになります。

範囲インスタンスユースケース
デフォルトアプリ全体の場合は 1ほとんどのサービス (90% 以上)
リクエストリクエストごとに 1 つマルチテナント、リクエストコンテキスト
過渡現象注入するたびに新しいステートフルヘルパー、ロガー

5. オプションの依存関係

import { Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService {
  constructor(
    @Optional() @Inject('HTTP_OPTIONS') private options?: HttpOptions,
  ) {
    // Nếu HTTP_OPTIONS không được provide, options = undefined
    this.options = options ?? { timeout: 5000 };
  }
}

6. 実践例:オーダーサービス

// order.service.ts
@Injectable()
export class OrderService {
  constructor(
    private readonly productService: ProductService,
    private readonly paymentService: PaymentService,
    private readonly notificationService: NotificationService,
    @Inject('LOGGER') private readonly logger: Logger,
  ) {}

  async createOrder(dto: CreateOrderDto): Promise<Order> {
    // 1. Kiểm tra sản phẩm
    const product = await this.productService.findOne(dto.productId);
    if (!product) throw new NotFoundException('Product not found');

    // 2. Xử lý thanh toán
    const payment = await this.paymentService.charge(dto.amount);

    // 3. Tạo order
    const order = { id: Date.now(), ...dto, paymentId: payment.id };

    // 4. Gửi notification
    await this.notificationService.sendOrderConfirmation(order);

    this.logger.log(`Order ${order.id} created`);
    return order;
  }
}

7. まとめ

  • プロバイダー: 任意のクラス @Injectable() 誰もがプロバイダーです
  • DIコンテナ: NestJS は依存関係を自動的に解決して挿入します。
  • カスタムプロバイダー: useClass、useValue、useFactory、useExisting
  • スコープ: デフォルト (シングルトン)、リクエスト、トランジェント
  • 常に優先順位を付ける コンストラクターインジェクション テスト容易性のために

次の記事で詳しく説明します モジュール — NestJS の機能ごとにコードを整理する方法。