Dev Study
← 解説「カスタムプロバイダ — useValue / useFactory / useClass」に戻る

サンプルコードで身につける: カスタムプロバイダ — useValue / useFactory / useClass

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1クラス省略形の正体は useClass

providers にクラスを並べるだけの書き方が、provide と useClass を組にした省略形であることを示します。両者はまったく同じ意味で、フルの形を知っておくと次の useValue / useFactory への発展が理解しやすくなります。

import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";

@Module({
  // この2つはまったく同じ意味
  providers: [
    UsersService, // 省略形
    // { provide: UsersService, useClass: UsersService }, // フルの形
  ],
})
export class UsersModule {}

2useValue で固定値を注入する

設定オブジェクトや定数のような「生成不要の固定値」は useValue で注入します。トークンに文字列を使った場合、受け取る側は @Inject("トークン名") を明示する必要がある点が、クラスをトークンにする場合との違いです。

import { Module, Injectable, Inject } from "@nestjs/common";

@Injectable()
export class MailService {
  // 文字列トークンは @Inject で明示して受け取る
  constructor(@Inject("MAIL_CONFIG") private cfg: { from: string }) {}
  sender() {
    return this.cfg.from;
  }
}

@Module({
  providers: [
    { provide: "MAIL_CONFIG", useValue: { from: "noreply@example.com" } },
    MailService,
  ],
})
export class MailModule {}

3useFactory の本質をプレーンTSで再現

useFactory の「トークンごとにファクトリ関数で値を生成し、inject で指定した依存も渡す」という核心を、デコレータなしの簡易コンテナで再現した実行例です。useValue と useFactory の両方を1つのコンテナで解決する流れが追えます。

TypeScript

4useFactory の解決順序を再現

useFactory が inject の依存を先に解決してから自分を生成する、という順序を生成ログで可視化した実行例です。登録順ではなく依存関係に沿って組み立てられることが、出力されるログの並びから確認できます。

TypeScript

5実務: 環境ごとに useClass で実装を差し替える

本番では本物の決済クライアント、開発ではログを出すだけの偽実装、というように環境変数で実装を丸ごと差し替える実務例です。注入側は共通のインターフェースしか知らないため、差し替えても呼び出しコードは一切変わりません。

import { Module } from "@nestjs/common";

// 注入側はこのインターフェースだけに依存する
export interface PaymentClient {
  charge(amount: number): Promise<string>;
}

export class StripePaymentClient implements PaymentClient {
  async charge(amount: number) {
    return "stripe:" + amount; // 本物の決済 API を叩く
  }
}
export class FakePaymentClient implements PaymentClient {
  async charge(amount: number) {
    return "fake:" + amount; // 開発用。実際には課金しない
  }
}

@Module({
  providers: [
    {
      provide: "PAYMENT_CLIENT",
      // 環境変数で実装を丸ごと差し替える(呼び出し側は無変更)
      useClass:
        process.env.NODE_ENV === "production"
          ? StripePaymentClient
          : FakePaymentClient,
    },
  ],
  exports: ["PAYMENT_CLIENT"],
})
export class PaymentModule {}