NestJS 공식문서 번역 겸 공부하는 글 입니다.
의역 및 오역이 있을 수 있습니다.
https://docs.nestjs.com/providers

Providers

Providers

provider는 Nest에서 가장 근본적인 개념이다. 대부분의 기본 Nest class는 provider로 취급된다(예를 들어, service, repository, factory, helper 등등). provider는 의존성을 주입할 수 있다. 이것은 오브젝트간의 서로 다양한 관계들을 만들 수 있음을 뜻한다.

image of component

앞선 챕터에서, 우리는 CatsController를 만들었고, controller는 HTTP request를 핸들링하고 복잡한 일을 provider에게 맡긴다.

Services

간단한 CatsService를 만들어보자. 이 service는 데이터를 저장하고 불러오는 역할을 담당할 것이며, CatsController가 이를 시용할 것이다.

// cats.service.ts

import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

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

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

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

@Injectable() 데코레이터는 CatService가 Nest IoC container에 의해 관리될 수 있게 메타데이터를 붙여준다. 또한 예제에서는 Cat interface를 사용중이다.

// interfaces/cat.interface.ts

export interface Cat {
  name: string;
  age: number;
  breed: string;
}

위에서 만든 service를 CatController에서 사용해보자.

// cat.controller.ts

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

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

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

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

CatService는 생성자를 통해 injection 되었다. private를 붙여줌으로써 선언과 초기화를 동시에 실행한다.

Dependency injection

Nest는 Dependency Injection(DI)라고 알려진 디자인 패턴을 적용하고 있다. Angular 공식 문서에서 이러한 개념에 대해 읽어보기를 추천한다. Nest에서는 TypeScript를 사용하는 이점으로 타입을 통해 쉽게 의존성을 관리할 수 있다. 아래의 예제처럼 Nest는 CatsService의 인스턴스를 생성하고 반환함으로써 catsService를 의존성을 주입한다(대부분의 경우는 singleton으로 이미 다른곳에서 요청되었다면 이미 존재하는 인스턴스를 반환한다). 이러한 의존성은 controller의 생성자를 통해 주입된다.

constructor(private catsService: CatsService) {}

Scopes

provider는 일반적으로 애플리케이션의 lifecycle과 일치하는 lifecycle(“scopes”)를 갖는다. 애플리케이션이 동작하면, 의존성이 주입되고, 모든 provider가 인스턴스화 된다. 애플리케이션이 종료되면, 각각의 provider들은 파괴된다. 그러나, provider의 lifecycle을 requested-scope로 만들수도 있다. 이러한 방법에 대해서는 이 곳에서 읽어볼 수 있다.

Custom providers

Nest는 provider간의 관계를 관리하는 inversion of control(“IoC”) container를 갖고 있다. 이러한 특성은 위에서 설명한 dependency injection 특성에 기반하고 있다. provider를 정의하기 위한 여러가지 방법이 있다. plain value, class, asynchronous factory 또는 synchronous factory를 사용할 수 있다. 이에 대한 예제는 이 곳에서 볼 수 있다.

Optional providers

가끔, 반드시 주입될 필요가 없는 의존성이 있을 수 있다. 예를 들어, configuration object에 의존하는 class의 경우, configuration obejct가 사용되지 않으면 기본값이 적용된다. 이러한 경우, 의존성은 optional하다. provider를 optional로 지정하기 위해서는 @Optional() 데코레이터를 생성자에 사용한다.

import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

위의 예제는 HTTP_OPTION token을 포함한 custom provider를 사용하였다.

Property-based injection

지금까지 우리가 사용한 기술은 생성자를 통해 provider를 주입하기 때문에 constructor-based injection이라고 불린다. 일부의 경우, property-based injection이 유용하다. 예를 들어, 하나 이상의 provider에 의존성을 가진 최상위 class의 경우, sub class로부터 super()를 호출하여 이를 주입하는 것은 좋지 못한 방법이다. 대신 @Inject() 데코레이터를 property-level 에서 사용한다.

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

class가 다른 provider를 확장하고 있는 것이 아니라면, 항상 constructor-based injection을 사용하는 것이 좋다.

Provider registration

CatsService와 CatsController를 정의하였다. 이제 service가 injection 될 수 있도록 등록해주어야 한다. Module의 @Module() 데코레이터에 providers 배열에 추가함으로써 등록할 수 있다.

// app.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

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

Nest는 이제 CatController에 의존성을 주입할 수 있다.

디렉토리 구조

src
 |
 +-- cats
 |    |
 |    +-- dto
 |    |    |
 |    |    +-- create-cat.dto.ts
 |    |
 |    +-- interfaces
 |    |   |
 |    |   +-- cat.interface.ts
 |    |
 |    +-- cats.controller.ts
 |    |
 |    +-- cats.service.ts
 |
 +-- app.module.ts
 |
 +-- main.ts

Manual instantiation

지금까지 어떤 방식으로 Nest에서 의존성을 자동으로 주입하는지에 대해 설명했다. 그러나 특정한 경우, DI system 외부에서 수동으로 의존성을 주입해야 하는 경우가 있다. 이미 존재하는 인스턴스를 가져오거나, provider를 동적으로 인스턴스화 하기 위해서는 Module reference를 사용한다. bootstrap() 함수에 포함된 provider를 가져오기 위해서는 Standalone applications 페이지를 참고하자.