3. Providers
프로바이더는 Nest의 근본적인 컨셉이다. Nest 클래스의 대부분은 서비스, 리포지토리, 팩토리, 헬퍼 등 프로바이더로 취급될 수 있다. 프로바이더의 주요 아이디어는 종속성으로 주입될 수 있다는 것이다. 즉, 객체는 서로 다양한 관계를 생성할 수 있으며 객체의 인스턴스를 연결하는 기능은 대부분 Nest 런타임 시스템에 위임될 수 있다.
이전 챕터에서 간단한 CatsController
를 만들었다. 컨트롤러는 HTTP 요청을 처리하고 더 복잡한 작업을 프로바이더에게 위임해야 한다. 프로바이더는 모듈에서 providers
로 선언된 일반 JavaScript 클래스를 의미한다.
1. Services
간단한 CatsService
를 만드는 것으로 시작해 보자. 이 서비스는 데이터 저장 및 검색을 담당하며 CatsController
에서 사용하도록 설계되었으므로 프로바이더로 정의하기에 좋다.
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
인터페이스를 생성한다.
이제 고양이를 검색하는 서비스 클래스가 있으므로 CatsController
내에서 이를 사용한다.
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
를 리졸브한다(또는 일반적인 싱글톤의 경우 다른 곳에서 생성된 기존 인스턴스를 반환). 이 종속성은 리졸브되어 컨트롤러의 생성자로 전달된다.
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
배열을 넣어준다.
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