본 글은 Nest.js 공식 도큐먼트 - Provider 를 바탕으로 정리한 글입니다.
이번에는 원문 번역한 것 같은 정리글이 아니라 진짜 정리를 해야지... -> 하지만 그렇게 되지 못 했습니다...
Provider 에는 서비스, 팩토리 등이 포함된다.
Provider 의 가장 메인 아이디어는 의존성으로서 주입될 수 있다는 데에 있다.
의존성 주입을 통해 서로 다른 객체들이 다양한 관계를 맺을 수 있다. 물론 모듈에 필요한 Provider 들을 정의하고, 실제 시스템에 반영되는 내부적인 작업은 Nest 런타임 시스템이 위임하여 처리한다고 한다.
Provider 는 주로 Controller 를 통해 전달받은 요청에 대하여 복잡한 기능을 수행하는 비즈니스 로직이 담긴다.
자바스크립트 클래스이며, 해당 클래스는 모듈 클래스 내 providers
속성에 정의된다. 그래야지 Nest 가 Provider 의 존재를 인지한다.
참고
공식 도큐먼트에 따르면, 객체지향 설계원칙, SOLID 를 강력히 권장한다고 한다.
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;
}
}
참고
서비스를 생성하려면 Nest CLI 명령어인
$ nest g service cats
를 사용하면 된다.
클래스의 상단에 쓰인 @Injectable()
데코레이터를 사용하여 해당 서비스는 Nest IoC 컨테이너에 의해 관리됨을 나타낼 수 있다.
해당 예제에서는 Cat
인터페이스를 정의하여 사용한다. 코드는 다음과 같다.
export interface Cat {
name: string;
age: number;
breed: string;
}
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); // 서비스에 DTO 전달
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
CatsService
는 클래스의 생성자를 통해 주입된다. 의존성 주입을 통해 CatsService
를 선언하고 초기화하여 동일한 클래스 내에서 해당 클래스의 멤버 메소드들을 사용할 수 있다.Nest 는 의존성 주입으로 알려진 디자인 패턴을 사용한다. 보다 자세한건 Angular 공식 도큐먼트를 참고하기를 권한다. (Nest.js 는 Angular 에 많은 영향을 받았다고 함.)
아래 코드를 통해 Nest 는 CatsService
의 인스턴스를 생성하고 catsService
변수에 이를 주입한다.
constructor(private catsService: CatsService) {}
Provider 의 생명주기(도큐먼트에서는 lifetime("scope") 으로 표현되어있음)는 일반적으로 어플리케이션의 생명주기와 동일하다.
어플리케이션이 실행될 때, 모든 의존성이 체크되고 이를 통해 모든 Provider 들이 인스턴스화 된다. 이와 유사하게, 어플리케이션이 꺼지면 모든 Provider 들은 소멸한다.
Provider 의 생명주기를 요청 단위 로 설정할 수 있는데, 이와 관련한 자세한 사항은 여기를 참고하면 된다.
간혹 필수적으로 주입되지 않아도 되는 의존성이 존재할 수도 있다. 예를 들어, 어떤 클래스는 Configuration 객체에 의존할 수도 있고, 아닐 수도 있다. 이때, 아무 것도 주입되지 않으면 기본 동작으로만 돌아갈 수 있다. 이러한 케이스에서 의존성은 선택사항이 된다. 이를 통해 Configuration Provider 가 없어도 오류는 발생하지 않는다.
주입되는 Provider 가 선택 사항임을 나타내기 위해서 @Optional()
데코레이터를 사용한다.
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
super()
키워드를 사용한다는 건 비효율적이다. (이와 관련하여 쉽게 정리된 이 블로그를 참고해보면 좋을 것 같다.)import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
주의
만일 클래스가 다른 Provider 를 상속받고 있지 않다면, 생성자 기반 주입 방식을 사용해야한다.
Nest 가 의존성 주입을 수행할 수 있도록 모듈에 Provider 를 정의하는 과정이 필요하다.
@Module()
데코레이터 내에 providers
프로퍼티에 배열 형태로 값을 작성하면 된다.
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService], // Provider 추가
})
export class AppModule {}
CatsController
가 CatsService
를 의존성 주입하여 사용할 수 있다.특정 상황에서는 내장된 DI 시스템 바깥에서 특정 Provider 를 인스턴스화 해야할 수 있다.
이미 인스턴스화 된 객체를 얻거나, Provider 를 동적으로 인스턴스화 하고 싶다면, Module Reference를 참고하면 된다.
bootstrap()
메소드에서 Provider 를 얻고 싶다면(컨트롤러가 존재하지 않는 Standalone 어플리케이션이라던가, Bootstrapping 중 Configuration 이 필요한 경우) Standalone Applications 항목을 참고하면 된다.
끗.