🐈

NestJSでinterfaceに対してDIする

2022/05/04に公開

NestJSはmoduleクラス周りでのDIの実装がとても優れているなと感じており、moduleクラスによって依存関係がまとまるので、可読性も高いなと感じています。

そんな中で1点、特定のクラスに対してinterfaceを噛ませて実装したいと思った時に、少し詰まったので、それの解決策です。

やりたいこと

DemoServiceに対して、DemoRepositoryInterfaceを実装したDemoRepositoryをDIしたい。

demo.service.ts
import { Injectable } from '@nestjs/common';
import { DemoRepositoryInterface } from './demo.repository.interface';

@Injectable()
export class DemoService {
  constructor(
    private readonly demoRepository: DemoRepositoryInterface,
  ) {}

  // その他メソッドなど...
}

interfaceを噛ませないで書いてみる

interfaceを噛ませないのであれば簡単です。

demo.service.ts
import { Injectable } from '@nestjs/common';
import { DemoRepository } from './demo.repository';

@Injectable()
export class DemoService {
  constructor(
    private readonly demoRepository: DemoRepository,
  ) {}

  // その他メソッドなど...
}
demo.service.ts
import { Module } from '@nestjs/common';
import { DemoController } from './demo.controller';
import { DemoService } from './demo.service';
import { DemoRepository } from './demo.repository';

@Module({
  controllers: [DemoController],
  providers: [DemoService,DemoRepository],
})
export class DemoModule {}

NestJSのドキュメントに記載されているstandard providers という方法を使えば、実装が可能です。
https://docs.nestjs.com/fundamentals/custom-providers#standard-providers

しかし、これはDemoRepositoryクラスを、クラス名と同じ名前のDemoRepositoryトークンとして呼び出すことができるという設定です。

なので、サービス側でDemoRepositoryで呼び出すことができます。

上記のドキュメントにも書いてありますが、以下の記載の省略形になります。

demo.service.ts
import { Module } from "@nestjs/common";
import { DemoController } from "./demo.controller";
import { DemoService } from "./demo.service";
import { DemoRepository } from "./demo.repository";

@Module({
  controllers: [DemoController],
  providers: [
    {
      provide: DemoService,
      useClass: DemoService,
    },
    {
      provide: DemoRepository,
      useClass: DemoRepository,
    },
  ],
})
export class DemoModule {}

useClassで宣言されているのが実際のクラス名、provideで宣言されているのがそれをなんという名前で扱うかという設定です。(ドキュメント内ではtokenと呼ばれています。)

この、useClassの文字列をprovideでも同じように扱いたいときにだけ、省略して記載することができます。

なので、サービス側は、コンストラクタにて、DemoRepositoryでリポジトリを呼び出すことができます。

interfaceを噛ませて書く

demo.service.ts
import { Injectable } from '@nestjs/common';
import { DemoRepositoryInterface } from './demo.repository.interface';

@Injectable()
export class DemoService {
  constructor(
    private readonly demoRepository: DemoRepositoryInterface,
  ) {}

  // その他メソッドなど...
}

interfaceを噛ませると、サービス側からDemoRepositoryでリポジトリが呼び出せなくなります。

そこで利用したのが、ドキュメントに記載されているNon-class-based provider tokensです。これを利用すると、特定のクラスに対して、特定の文字列をトークンとして紐づける事ができます。

また、Non-class-basedと記載されている通り、このトークンはクラスを基にしておらず、@injectデコレータ を利用してサービス側から呼び出すことができます。

公式ドキュメントのコードをそのまま持ってくると、以下の通りです。

module
import { connection } from './connection';

@Module({
  providers: [
    {
      provide: 'CONNECTION',
      useValue: connection,
    },
  ],
})
export class AppModule {}
service側のコンストラクタ
@Injectable()
export class CatsRepository {
  constructor(@Inject('CONNECTION') connection: Connection) {}
}

これを利用することで、interfaceを噛ませた状態でDIができます。

また、公式ドキュメントには、以下のように記載されているので、定数ファイルの中でトークンを指定しています。

demo.module.ts
import { Module } from "@nestjs/common";
import { DemoController } from "./demo.controller";
import { DemoService } from "./demo.service";
import { DemoRepository } from "./demo.repository";
import { ConstantTokens } from './demo.constants';

@Module({
  controllers: [DemoController],
  providers: [
    {
      provide: DemoService,
      useClass: DemoService,
    },
    {
      provide: ConstantTokens.REPOSITORY,
      useClass: DemoRepository,
    },
  ],
})
export class DemoModule {}
demo.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { DemoRepositoryInterface } from './demo.repository.interface';
import { ConstantTokens } from './book.constants';

@Injectable()
export class DemoService {
  constructor(
    @Inject(ConstantTokens.REPOSITORY)
    private readonly demoRepository: DemoRepositoryInterface,
  ) {}

  // その他メソッドなど...
}
demo.constants.ts
export enum ConstantTokens {
  REPOSITORY = 'repository',
}

こうすることで、interfaceを噛ませていても、DIができます。

めでたしめでたし

参考資料

Discussion