Nest의 기본 클래스는 Service, Repository, Factory, Helper 등이 있으며, 이들은 Provider로 취급된다.
Provider의 주요 아이디어는 종속성으로 주입할 수 있다는 것이다.
즉, 객체는 서로 다양한 관계를 만들 수 있으며, 객체의 인스턴스를 연결하는 기능은 대부분 Nest 런타임 시스템에 위임된다.
Controller는 HTTP 요청을 처리하고 더 복잡한 작업을 Provider에 위임한다.
Provider는 Module에서 provider
로 선언된 일반 자바스크립트 클래스이다.
이처럼 NestJs는 객체자향 프로그래밍을 할 수 있으므로, SOLID 원칙을 따르는 것이 좋다
CatsService
를 예로 들어보자.
해당 Service는 데이터를 저장 및 검색 담당이며, CatsController에서 사용하도록 설계하였다.
Provider의 일종이다.
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;
}
}
export interface Cat {
name: string;
age: number;
breed: string;
}
해당 서비스는 하나의 속성과 두개의 메서드가 있는 기본 클래스이다.
@Injectable()
데코레이더는 해당 서비스를 Nest IoC 컨테이너에서 관리할 수 있는 클래스임을 선언하는 메타데이터에 첨부한다.(Nest가 관리하게 된다)
이 Service 클래스는 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
를 선언과 동시에 초기화할 수 있다.
클래스는 종속성을 주입받을 수 있지만, 전달되는 것이 없으면 기본값을 사용해야 한다.
이러한 경우 Provider가 없어도 오류가 발생하지 않도록 종속성을 선택하게 할 수 있다.
Provider가 선택사항임을 나타내려면 생성자의 Signiture에 @Optional()
데코레이터를 사용하면 된다.
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
위 예에는 Custom Provider를 사용하고 있으므로, HTTP_OPTIONS
Custom Token을 포함한다.
여태까지 사용한 기술은 Provider가 생성자 메서드를 통해 주입되기에 생성자 기반 주입이라 한다.
특수한 경우에 이같은 방법 이외에 속성 기반 주입이라는 방식을 사용할 수도 있다.
예를 들어, 최상위 클래스가 하나 또는 여러 Provider에 의존하는 경우 생성자의 하위 클래스에서 super()
를 호출해 모든 Provider를 전달하는 것은 비효율적일 수 있다. 이를 위해 속성 수준에서 @Inject()
데코레이터를 사용할 수 있다.
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
이제 Provider를 정의하고 해당 서비스를 사용하는 Controller를 확보했으므로 주입을 수행할 수 있게 서비스를 Nest에 등록해야 한다. 모듈 파일을 편집하고, @Module()
데코레이터의 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
클래스의 종속성을 해결할 수 있다.(CatsController
가 CatsService
를 DI 했으므로 이를 모듈에 등록해줌으로써)
이렇게까지 했을때의 디렉토리 구조는 다음과 같다.