NestJSでinterfaceに対してDIする
NestJSはmoduleクラス周りでのDIの実装がとても優れているなと感じており、moduleクラスによって依存関係がまとまるので、可読性も高いなと感じています。
そんな中で1点、特定のクラスに対してinterfaceを噛ませて実装したいと思った時に、少し詰まったので、それの解決策です。
やりたいこと
DemoService
に対して、DemoRepositoryInterface
を実装したDemoRepository
をDIしたい。
import { Injectable } from '@nestjs/common';
import { DemoRepositoryInterface } from './demo.repository.interface';
@Injectable()
export class DemoService {
constructor(
private readonly demoRepository: DemoRepositoryInterface,
) {}
// その他メソッドなど...
}
interfaceを噛ませないで書いてみる
interfaceを噛ませないのであれば簡単です。
import { Injectable } from '@nestjs/common';
import { DemoRepository } from './demo.repository';
@Injectable()
export class DemoService {
constructor(
private readonly demoRepository: DemoRepository,
) {}
// その他メソッドなど...
}
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
という方法を使えば、実装が可能です。
しかし、これはDemoRepository
クラスを、クラス名と同じ名前のDemoRepository
トークンとして呼び出すことができるという設定です。
なので、サービス側でDemoRepository
で呼び出すことができます。
上記のドキュメントにも書いてありますが、以下の記載の省略形になります。
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を噛ませて書く
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デコレータ
を利用してサービス側から呼び出すことができます。
公式ドキュメントのコードをそのまま持ってくると、以下の通りです。
import { connection } from './connection';
@Module({
providers: [
{
provide: 'CONNECTION',
useValue: connection,
},
],
})
export class AppModule {}
@Injectable()
export class CatsRepository {
constructor(@Inject('CONNECTION') connection: Connection) {}
}
これを利用することで、interfaceを噛ませた状態でDIができます。
また、公式ドキュメントには、以下のように記載されているので、定数ファイルの中でトークンを指定しています。
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 {}
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,
) {}
// その他メソッドなど...
}
export enum ConstantTokens {
REPOSITORY = 'repository',
}
こうすることで、interfaceを噛ませていても、DIができます。
めでたしめでたし
Discussion