핵심은 **“직접 생성하여 의존관계를 직접 생성하지 않는다는 것”**이다. NestJS에서는 프로바이더들을 위한 컨테이너인 모듈를 기반하여 개체를 런타임에 생성하여 DI를 처리한다.

아래 코드를 보면 세 가지 단계로 나눌 수 있다.

  1. 추상화된 Cat인터페이스를 구현한 서비스CatService를 선언한다.
  2. 모듈CatModule에 프로바이더로서 CatService를 등록한다.
  3. 컨트롤러에서 특정 요청에 따른 CatService의 비즈니스 로직을 실행한다.
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './cat.interface';
import { CreateCatDto } from './create-cat.dto';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  findAll(): Cat[] {
    return this.catsService.findAll();
  }
}

// cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

// cat.interface.ts
export interface Cat {
  name: string;
  age: number;
  breed: string;
}

// create-cat.dto.ts
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

모듈에 등록한 프로바이더를 직접 생성하지 않아 눈에 보이지 않기 때문에 IoC가 되었는지 안 되었는지 판별하기 어렵다.

그렇다면 이런 상황이라면 어떨까. CatController가 Tiger에 대한 비즈니스 로직을 사용하고 싶다면??

  1. 우선 Tiger에 대한 모듈과 서비스를 구현한다.
  2. Tiger모듈의 providers 배열에 Tiger서비스를 등록한다.
  3. Tiger모듈을 export한다. (노출시킨다는 표현을 봤는데 적절한 것 같다. 다른 모듈이 자신을 쓸 수 있게끔 자신의 publicity를 노출시킨다는 것이니까 말이다.
  4. CatModule의 imports 배열에 TigerModule을 사용한다.
  5. CatController에서 Tiger에 대한 서비스를 사용한다. 이는 CatModule이 TigerModule을 import하였기 때문이다. TigerModule에서 provider로 TigerService를 지정했기 때문에 CatModule의 Provider로서 또한 TigerService를 지정된 효과를 볼 수 있다.

코드는 아래와 같다.

TigerService

@Injectable()
export class TigerService {
  private readonly tigers: Tiger[] = [];

  create(tiger: Tiger) {
    this.tigers.push(tiger);
  }

  findAll(): Tiger[] {
    return this.tigers;
  }
}

TigerModule

@Module({
  providers: [TigerService],
})
export class TigersModule {}

CatModule

@Module({
  imports: [TigersModule],
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}