Chapter 17

fundamentals-modulereference

kisihara.c
kisihara.c
2021.02.17に更新

モジュール参照

NestはModuleRefクラスを用意している。内部のプロバイダのリストをナビゲートし、そのインジェクショントークンを検索キーとして任意のプロバイダへの参照を取得する為のものだ。ModuleRefクラスはまた、静的プロバイダ及びスコープ化プロバイダを動的にインスタンス化する方法も提供する。ModuleRefは通常の方法でクラスにインジェクションする事ができる。

cats.service.ts
@Injectable()
export class CatsService {
  constructor(private moduleRef: ModuleRef) {}
}

HINT
ModuleRefクラスは@nestjs/coreパッケージからインポートする事。

インスタンスの取得

ModuleRefインスタンス(以下、モジュールリファレンスと呼称)にはget()メソッドがある。現在のモジュールに存在する(かつ、インスタンス化されている)もの(プロバイダ、コントローラ、インジェクション可能なオブジェクト(ガード、インターセプター等))を、そのインジェクショントークン/クラス名を使って取得できる。

cats.service.ts
@Injectable()
export class CatsService implements OnModuleInit {
  private service: Service;
  constructor(private moduleRef: ModuleRef) {}

  onModuleInit() {
    this.service = this.moduleRef.get(Service);
  }
}

WARNING
スコープ化されたプロバイダ(遷移的、もしくはリクエストスコープ)はget()メソッドで取得不能。代わりに次節のテクニックを使用の事。スコープの制御の方法についてはこちら

グローバルコンテキストからプロバイダを取得するには(たとえばプロバイダが別のモジュールにインジェクションされている場合等)、get()の第二引数に{ strict: false }オプションを渡してほしい。

this.moduleRef.get(Service, { strict: false });

スコープ化されたプロバイダの解決

スコープ化されたプロバイダ(遷移的もしくはリクエストスコープ)を動的に解決(resolve)するにはresolve()メソッドを使い、プロバイダのインジェクショントークンを引数として渡す。

cats.service.ts
@Injectable()
export class CatsService implements OnModuleInit {
  private transientService: TransientService;
  constructor(private moduleRef: ModuleRef) {}

  async onModuleInit() {
    this.transientService = await this.moduleRef.resolve(TransientService);
  }
}

resolve()メソッドは、自分自身のDIコンテナのサブツリーから、プロバイダの一意のインスタンスを返す。各サブツリーは一意のコンテキスト識別子を持っている。したがって、このメソッドを複数回呼び出してインスタンスリファレンスを比較すると、等しくない。

cats.service.ts
@Injectable()
export class CatsService implements OnModuleInit {
  constructor(private moduleRef: ModuleRef) {}

  async onModuleInit() {
    const transientServices = await Promise.all([
      this.moduleRef.resolve(TransientService),
      this.moduleRef.resolve(TransientService),
    ]);
    console.log(transientServices[0] === transientServices[1]); // false
  }
}

複数のresolve()コールを超えて単一のインスタンスを生成し、生成されたDIコンテナのサブツリーを共有するようにする為には、resolve()メソッドにコンテキスト識別子を渡す事ができる。コンテキスト識別子を生成するには、ContextIdFactoryクラスを使用する。このクラスは適切な一意の識別子を返すcreate()メソッドを提供する。

cats.service.ts
@Injectable()
export class CatsService implements OnModuleInit {
  constructor(private moduleRef: ModuleRef) {}

  async onModuleInit() {
    const contextId = ContextIdFactory.create();
    const transientServices = await Promise.all([
      this.moduleRef.resolve(TransientService, contextId),
      this.moduleRef.resolve(TransientService, contextId),
    ]);
    console.log(transientServices[0] === transientServices[1]); // true
  }
}

HINT
ContextIdFactoryクラスは@nestjs/coreパッケージからインポートの事。

REQUESTプロバイダの登録

手動で生成されたコンテキスト識別子(ContextIdFactory.create()を使用)は、Nestの依存性インジェクションシステムによってインスタンス化・管理されていない為、REQUESTundefinedのままであるDIサブツリーを表す。

手動で作成したDIサブツリーにカスタムREQUESTオブジェクトを登録するには、以下のようにModuleRef#registerRequestByContextId()メソッドを使う。

const contextId = ContextIdFactory.create();
this.moduleRef.registerRequestByContextId(/* YOUR_REQUEST_OBJECT */, contextId);

現在のサブツリーの取得

リクエストのコンテキスト内でリクエストスコープ化されたプロバイダのインスタンスを解決(resolve)したい場合がある。例えば、CatsServiceがリクエストスコープ化されており、リクエストスコープ化されたプロバイダとして記録されているCatsRepositoryのインスタンスを解決したいとする。同じDIコンテナサブツリーを共有する為には、新しいコンテキストの識別ではなく、現在のコンテキスト識別子の取得が必要(例えば、上記のようにContextIdFactory.create()メソッドを使用する)。現在のコンテキスト識別子を取得するには、@Inject()デコレータを使用してリクエストオブジェクトをインジェクションする事から始める。

cats.service.ts
@Injectable()
export class CatsService {
  constructor(
    @Inject(REQUEST) private request: Record<string, unknown>,
  ) {}
}

HINT
requestプロバイダについて更に詳しくはこちら

では、ContextIdFactoryクラスのgetByRequest()メソッドを使用する。リクエストオブジェクトを元にコンテキストIDを作成し、それをresolve()の呼び出しに渡す。

const contextId = ContextIdFactory.getByRequest(this.request);
const catsRepository = await this.moduleRef.resolve(CatsRepository, contextId);

カスタムクラスのインスタンスを動的に作成する

以前はプロバイダとして登録されていなかったクラスを動的にインスタンス化するには、モジュール参照のcreate()メソッドを使用。

@Injectable()
export class CatsService implements OnModuleInit {
  private catsFactory: CatsFactory;
  constructor(private moduleRef: ModuleRef) {}

  async onModuleInit() {
    this.catsFactory = await this.moduleRef.create(CatsFactory);
  }
}

このテクニックによって、フレームワークコンテナの外で、異なるクラスを条件付きでインスタンス化できる。