상똥이의 Back-End 공부방
[Nest.js] Providers 본문
Providers
Provider는 Nest의 근본적인 개념입니다. 많은 Nest의 기본 클래스는 Provider로 취급될 수 있습니다. Provider의 주 목적은 의존성 주입입니다. 이는 객체가 서로 다양한 관계를 맺을 수 있음을 의미하며 이 객체들을 "연결하는" 기능은 크게 Nest 런타임 시스템에 위임될 수 있습니다.
이전 장에서 간단한 CatsController를 작성했습니다. Controller는 HTTP 요청을 처리하고 더 복잡한 작업은 Provider에 위임합니다. Provider는 module에 'providers'로 선언되는 순수 자바스크립트 클래스입니다.
HINT
Nest는 객체 지향적인 방식으로 의존성을 설계하고 구성할 수 있는 가능성을 제공하기 때문에, SOLID 원칙을 따르는 것을 강력히 권장합니다.
Services
간단한 CatsService를 구현하며 시작해보겠습니다. 이 서비스는 데이터 저장과 검색을 책임지며 CatsController에 사용되도록 설계되어 Provider로 정의되기에 좋은 후보입니다.
//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;
}
}
HINT
CLI 명령어로 Service를 구현하고 싶다면, $ nest g service [name] 명령어를 입력하세요.
이 CatsService는 한 개의 속성과 두 개의 메서드를 갖는 기본 클래스입니다. 새로운 특징은 @Injectable() 데코레이터를 사용한다는 것뿐입니다. @Injectable() 데코레이터는 CatsService가 Nest의 IoC 컨테이너에서 관리될 수 있는 클래스임을 선언하는 메타데이터에 접근합니다. 위의 예시 또한 아래와 같은 Cat 인터페이스를 사용합니다.
//interface/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 구문 사용 방식에 유의하세요. 이 약어는 CatsSerivce의 구성요소를 해당 위치에 선언과 초기화를 동시에 할 수 있도록 해줍니다.
Dependency Injection
Nest는 의존성 주입으로 알려진 강력한 디자인 패턴으로 설계되었습니다. 이에 관한 Angular 문서를 읽도록 권장합니다.
Nest에서는 TypeScript의 기능 덕분에 의존성을 매우 쉽게 관리할 수 있습니다. 의존성은 타입만으로도 자동으로 해결되기 때문입니다. 아래 예시에서 Nest는 catsService를 해결하기 위해 CatsService의 인스턴스를 생성하여 반환합니다. (일반적인 싱글톤의 경우, 이미 다른 곳에서 요청된 적이 있다면 기존 인스턴스를 반환합니다.) 이 의존성은 해결된 후 컨트롤러의 생성자에 전달되거나 지정된 속성에 할당됩니다.:
constructor(private catsService: CatsService) {}
Scopes
Providers는 보통 애플리케이션 생애주기(scope)와 동기화된 생애주기를 가집니다. 애플리케이션이 실행되면 모든 의존성이 해결되어야 하며, 그로 인해 모든 Provider는 인스턴스화 되어야 합니다. 비슷하게, 애플리케이션이 종료되면 각각의 Provider 또한 종료됩니다. 그러나, Provider의 생애주기를 설정할 수 있습니다. 여기에서 더 자세히 볼 수 있습니다.
Custom providers
Nest는 Provider 사이의 관계를 해소하는 제어 역전 컨테이너를 내장하고 있습니다. 이 기능은 앞서 설명한 의존성 주입 기능의 기초가 되지만, 실제로는 지금까지 설명한 것보다 훨씬 더 강력합니다. Provider를 정의할 수 있는 방법이 몇 가지 있습니다.: 순수 값, 클래스를 사용하거나 동기/비동기 팩토리를 사용할 수 있습니다. 더 많은 예시는 여기에서 제공됩니다.
Optional providers
때때로 반드시 해결할 필요가 없는 의존성이 있을 수 있습니다. 예를 들어 클래스가 설정 객체에 의존할 수 있지만, 전달된 값이 없다면 기본값을 사용해야 할 수도 있습니다. 이 경우 설정 provider의 부재가 에러를 발생시키는 것이 아니기 때문에 의존성은 선택적인 항목이 됩니다.
Provider가 선택적임을 나타내기 위해 @Optional() 데코레이터를 생성자에 사용하겠습니다.
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
위의 예시는 HTTP_OPTIONS의 커스텀 토큰을 사용하기 위해 커스텀 Provider를 사용하고 있음에 유의하세요. 앞선 예시들은 생성자 클래스를 통한 의존성을 나타내는 생성자 기반 주입을 보여줬습니다. 여기에서 커스텀 Provider와 관련된 토큰을 자세히 알아보세요.
Property-based injection
지금까지 사용해온 기술은 생성자 메서드를 통해 Provider가 주입된 생성자 기반 주입이라고 불립니다. 몇몇의 특정 경우에 속성 기반 주입이 유리할 수 있습니다. 예를 들어 최상위 클래스가 여러개의 Provider에 의존하고 있는 경우 하위 클래스의 생성자에서 super()를 호출해 전체를 가져오는 것은 느릴 수 있습니다. 이를 피하기 위해 @Inject() 데코레이터를 속성 단계에서 사용할 수 있습니다.
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
WARNING
만약 클래스가 다른 클래스로 확장되지 않은 경우 생성자 기반 주입 방식을 사용할 것을 권고합니다. 생성자는 명시적으로 필요한 의존성을 설명하며, @Inject로 주석이 달린 클래스 속성보다 더 나은 가시성을 제공합니다.
Provider registration
CatsService라는 Provider를 정의했고, 이 서비스를 사용하는 CatsController가 있으므로 Nest에 서비스를 등록하여 주입을 실행할 수 있도록 해야합니다. app.module.ts라는 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는 CatsController의 의존성을 해결할 수 있습니다.
파일 구조는 아래와 같을 것입니다.:
Manual instantiation
지금까지 Nest가 어떻게 자동으로 의존성을 해결하는지에 대해 자세히 살펴보았습니다. 어떤 상황에서는 내장된 의존성 주입 방식 외에 다른 방식을 사용하고 수동으로 Provider를 검색하거나 추상화해야 할 수 있습니다. 아래의 두 토픽을 살펴보겠습니다.
이미 존재하는 인스턴스를 가져오거나 Provider를 동적으로 인스턴스화해야 하는 경우, Module reference를 사용할 수 있습니다.
bootstrap() 기능 내에 있는 Provider를 가져오기 위해서는 Standalone applications를 확인하세요.
'Nest.JS > Docs' 카테고리의 다른 글
[Nest.js] Middleware (0) | 2024.09.05 |
---|---|
[Nest.js] Modules (1) | 2024.09.04 |
[Nest.js] Controllers (0) | 2024.09.04 |
[Nest.js] First steps (0) | 2024.03.19 |
[Nest.js] Introduction (1) | 2024.03.19 |