Skip to content

3. Providers


프로바이더는 Nest의 근본적인 컨셉이다. Nest 클래스의 대부분은 서비스, 리포지토리, 팩토리, 헬퍼 등 프로바이더로 취급될 수 있다. 프로바이더의 주요 아이디어는 종속성으로 주입될 수 있다는 것이다. 즉, 객체는 서로 다양한 관계를 생성할 수 있으며 객체의 인스턴스를 연결하는 기능은 대부분 Nest 런타임 시스템에 위임될 수 있다.


001


이전 챕터에서 간단한 CatsController를 만들었다. 컨트롤러는 HTTP 요청을 처리하고 더 복잡한 작업을 프로바이더에게 위임해야 한다. 프로바이더는 모듈에서 providers로 선언된 일반 JavaScript 클래스를 의미한다.


1. Services

간단한 CatsService를 만드는 것으로 시작해 보자. 이 서비스는 데이터 저장 및 검색을 담당하며 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;
  }
}


Note

CLI를 사용하여 서비스를 생성하려면 $ nest g service cats 명령을 실행하면 된다.


CatsService는 하나의 프로퍼티와 두 개의 메서드가 있는 기본 클래스이다. 새로운 특징은 @Injectable() 데코레이터를 사용한다는 것이다. @Injectable() 데코레이터는 CatsService가 Nest IoC(Inversion of Control) 컨테이너에서 관리할 수 있는 클래스임을 선언하는 메타데이터를 첨부한다. 그리고 다음과 같이 CatService에 사용될 Cat 인터페이스를 생성한다.


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


이제 고양이를 검색하는 서비스 클래스가 있으므로 CatsController 내에서 이를 사용한다.


cats.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();
  }
}


CatsService는 클래스 생성자를 통해 주입된다. 생성자 내에 private 구문을 사용하면 catsService 멤버를 즉시 선언하고 초기화할 수 있다.


2. Dependency injection

Nest는 일반적으로 종속성 주입으로 알려진 강력한 디자인 패턴을 기반으로 구축되었다.


Nest에서는 TypeScript 기능 덕분에 타입별로 리졸브되기 때문에 종속성을 관리하기가 매우 쉽다. 아래 예제에서 Nest는 CatsService의 인스턴스를 생성하고 반환하여 catsService를 리졸브한다(또는 일반적인 싱글톤의 경우 다른 곳에서 생성된 기존 인스턴스를 반환). 이 종속성은 리졸브되어 컨트롤러의 생성자로 전달된다.


constructor(private catsService: CatsService) {}


3. Scopes

프로바이더는 일반적으로 애플리케이션 수명 주기와 동기화된 수명(범위)를 갖는다. 애플리케이션이 부트스트랩되면 모든 종속성을 리졸브해야 하므로 모든 프로바이더를 인스턴스화해야 한다. 마찬가지로 애플리케이션이 종료되면 각 프로바이더가 소멸된다. 그러나 프로바이더 수명을 요청 범위로 만드는 방법도 있다.


4. Optional Providers

경우에 따라 반드시 리졸브할 필요가 없는 종속성이 있을 수 있다. 예를 들어, 프로바이더가 옵셔널인 경우 아무 것도 전달되지 않을 때 기본값을 사용하도록 할 수 있다.


프로바이더가 옵셔널임을 나타내려면 생성자의 시그니처에서 @Optional() 데코레이터를 사용한다.


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

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


5. Property-based injection

지금까지 사용한 테크닉을 생성자 기반 주입이라고 하는데, 이는 생성자 메서드를 통해 프로바이더가 주입되기 때문이다. 특정한 경우에는 속성 기반 주입이 유용할 수 있다.


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

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


6. Provider registration

이제 프로바이더(CatsService)를 정의했고 해당 서비스의 컨슈머(CatsController)가 있으므로 주입을 수행할 수 있도록 Nest에 서비스를 등록해야 한다. 모듈 파일의 @Module 데코레이터에 CatsService를 포함하는 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는 이제 CatsController 클래스의 종속성을 리졸브할 수 있다.


이제 디렉터리 구조가 다음과 같이 표시되어야 한다.


src
├── app.module.ts
├── cats
│   ├── cats.controller.ts
│   ├── cats.service.ts
│   ├── dto
│   │   └── create-cat.dto.ts
│   └── interfaces
│       └── cat.interface.ts
└── main.ts

References