Dev Study
← 解説「インジェクションスコープ — Singleton / Request / Transient」に戻る

サンプルコードで身につける: インジェクションスコープ — Singleton / Request / Transient

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

1既定はシングルトン

@Injectable() にスコープを指定しないと既定のシングルトンになり、アプリ起動時に一度だけ生成したインスタンスを全リクエストで共有します。まずはこの「何も書かなければ使い回し」という前提を押さえます。

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

// scope を書かない = Scope.DEFAULT(シングルトン)
@Injectable()
export class CounterService {
  private count = 0;

  // 同じインスタンスを共有するので、値は全リクエストで増え続ける
  increment(): number {
    this.count += 1;
    return this.count;
  }
}

2Scope.REQUEST と Scope.TRANSIENT の宣言

@Injectable({ scope: ... }) にスコープを渡すと生成のタイミングを変えられます。REQUEST はリクエストごと、TRANSIENT は注入されるたびに新しいインスタンスを作るという、3種類の宣言の書き分けを示します。

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

// リクエストごとに新しいインスタンス
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
  requestId = Math.random().toString(16).slice(2);
}

// 注入されるたびに新しいインスタンス
@Injectable({ scope: Scope.TRANSIENT })
export class TransientLogger {
  prefix = "";
}

3Singleton と Transient を生成カウンタで比較

シングルトン(初回だけ生成して共有)とトランジェント(毎回新規)の違いを、生成カウンタ付きの実行例で対比します。同一インスタンスかどうかの真偽値が、2つのスコープの本質的な差を端的に表します。

TypeScript

4Request スコープを生成カウンタで再現

リクエストスコープが「同じリクエスト内では同一、リクエストが変われば新規」になる挙動を、リクエストIDをキーにしたキャッシュで再現した実行例です。シングルトンとリクエストスコープの中間的なふるまいが生成回数から分かります。

TypeScript

5実務: リクエストスコープのロガー

リクエストごとに発番した相関ID(correlation id)をログに付けたいケースの実務例です。リクエスト固有の状態を持たせるためにあえて Scope.REQUEST を選び、同一リクエストのログを後から追跡できるようにします。

import { Injectable, Scope, Inject } from "@nestjs/common";
import { REQUEST } from "@nestjs/core";
import { Request } from "express";

// リクエスト固有の相関IDを持たせたいので REQUEST スコープにする
@Injectable({ scope: Scope.REQUEST })
export class RequestLogger {
  private readonly correlationId: string;

  constructor(@Inject(REQUEST) req: Request) {
    // ヘッダにあれば引き継ぎ、なければ新規発番
    this.correlationId =
      (req.headers["x-correlation-id"] as string) ??
      Math.random().toString(16).slice(2);
  }

  log(message: string) {
    // 同一リクエストのログを後から追跡できる
    console.log("[" + this.correlationId + "] " + message);
  }
}