🐾
NestJSの抽象を使ったDIを型安全にする
モチベーション
抽象に対して具象で依存関係を登録したい
しかし、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 {}
注意点
ClassProvider
の provide
に 型
は当然渡せないので、抽象は type
や interface
ではなく abstract class
で定義する必要があります
抽象の定義
export abstract class AbstractCatsService {
abstract findAll(): Cat[];
}
Discussion