🐾

NestJSの抽象を使ったDIを型安全にする

2022/09/21に公開

モチベーション

抽象に対して具象で依存関係を登録したい
しかし、NestJSの依存関係の登録は、 何かしらのトークンに何かしらの値を登録する くらいのざっくりしたもの
TypeScript使っているので多少は型安全にしたい

NestJSのクラスの依存解決ざっくり説明

クラスデコレータ @Injectable() @inject() @Module() を使う

注入したいクラス
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable() // ← @Injectable() をつける
export class CatsService {
  private readonly cats: Cat[] = [];

  findAll(): Cat[] {
    return this.cats;
  }
}
注入されたいクラス
import { Controller, Get, Inject } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(
    @Inject('CatsService') // ← @Inject() をつけて、目印となる値を渡しておく
    private catsService: CatsService,
  ) {}

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}
module
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [
    {
      provide: 'CatsService', // ← 目印に対して
      useClass: CatsService, // ← 依存関係を登録する
    }
  ],
})
export class AppModule {}

※目印となる値は 割りといろんな値 を受け取れる

  • string
  • symbol
  • Type<any>
  • Abstract<any>
  • Function

問題点

目印となる値と解決先のクラスの関係性がない

抽象クラスとクラス
{
  provide: AbstractDogsService,
  useClass: CatsService,
}
  • 全く関係ない抽象と具象でも登録できてしまう

型安全にする

抽象と具象の関係性をチェックする関数を通してあげる

抽象と具象の関係性をチェックしてClassProviderを返す関数
import { ClassProvider } from '@nestjs/common'

export const provideClass = <
  T extends abstract new(...args: any) => any,
  U extends new(...args: any) => InstanceType<T>,
>(
  abstractClass: T,
  useClass: U
): ClassProvider => ({
  provide: abstractClass,
  useClass,
})
  • abstract new(...args: any) => any は抽象クラスのコンストラクタのこと
  • new(...args: any) => InstanceType<T>T のインスタンスを返すコンストラクタのこと
@Module({
  controllers: [CatsController],
  providers: [
    // 抽象と具象の関係性の型チェックをしつつ登録
    provideClass(AbstractCatsService, CatsService),
  ],
})
export class AppModule {}

注意点
ClassProviderprovide は当然渡せないので、抽象は typeinterface ではなく abstract class で定義する必要があります

抽象の定義
export abstract class AbstractCatsService {
  abstract findAll(): Cat[];
}
株式会社ゆめみ

Discussion