NestJS

Providers

2DC 2024. 4. 17. 16:55

Providers

프로바이더는 Nest의 기본 개념이다. 서비스, 리포지토리, 팩토리, 헬퍼 등 많은 기본 Nest 클래스가 프로바이더로 취급될 수 있다. 프로바이더의 주요 개념은 종속성으로 주입될 수 있다는 것이다. 즉, 각각의 객체가 다양한 관계를 생성할 수 있으며, 이러한 객체를 "wiring up(배선)"하는 기능은 대부분 Nest 런타임 시스템에 위임할 수 있다.

 

이전 챕터에서 간단한 CatsControllers를 구축했다. 컨트롤러는 HTTP 요청을 처리하고 더 복잡한 작업을 프로바이더에 위임해야 한다. 프로바이더는 모듈에서 프로바이더로 선언되는 일반 자바스크립트 클래스이다.

 

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;
  }
}

 

CatsService는 하나의 프로퍼티와 두개의 메서드가 있는 기본 클래스이다. 유일한 기능은 @Injectable() 데코레이터를 사용한다는 것이다. Injectable() 데코레이터는 메타데이터를 첨부하여 CatsService가 Nest IoC 컨테이너에서 관리할 수 있는 클래스임을 선언한다. 참고로 이 예제에서는 다음과 같이 보이는 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 구문을 사용하는 것을 주목하자. private를 사용하면 catsServices 멤버를 즉시 선언하고 초기화할 수 있다. (타입스크립트의 일종의 편의성 문법이다.)

 

Dependency injection

Nest는 일반적으로 의존성 주입이라고 알려진 강력한 디자인 패턴을 기반으로 구축되었다. 이 개념에 대한 자세한 내용은 Angular 공식문서에서 읽어볼 수 있다.

 

Nest에서는 타입스크립트 기능 덕분에 의존성을 매우 쉽게 관리할 수 있다. 아래 예제에서 Nest는 CatsService의 인스턴스를 생성하고 반환함으로써(또는 싱글톤의 일반적인 경우 다른 곳에서 이미 요청된 경우 기존 인스턴스를 반환함으로써) catsService를 resolve한다. 즉 의존성은 resolve 되어 컨트롤러의 생성자에 전달되거나 지정된 프로퍼티에 할당된다.

constructor(private catsService: CatsService) {}

 

Scopes

프로바이더는 일반적으로 애플리케이션 수명 주기와 동기화된 라이프사이클을 가진다. 애플리케이션이 부트스트랩되면 모든 종속성이 resolve되어야 하므로 모든 프로바이더가 인스턴스화되어야 한다. 마찬가지로 애플리케이션이 종료되면 프로바이더들은 삭제된다. 하지만 프로바이더에 request-scoped 생명주기를 부여할 수도 있다. 이 기술은 FUNDAMENTAL  파트에서 다룬다.

 

Custom providers

Nest에는 프로바이더 간의 관계를 해결하는 제어 역전(IoC) 컨테이너가 내장되어 있다. 이 기능은 위에서 설명한 의존성 주입 기능의 기반이 되지만, 사실 지금까지 설명한 것보다 훨씬 강력하다. 공급자를 정의하는 방법에는 일반값, 클래스, 비동기 또는 동기 팩토리를 사용할 수 있는 여러가지 방법이 있다. 이 기술은 FUNDAMENTAL 파트에서 다룬다. 

 

Optional providers

때로는 반드시 resolve 될 필요가 없는 의존성도 있을 수 있다. 예를 들어 클래스가 구성 객체에 의존될 수 있지만 아무것도 전달되지 않으면 기본값을 사용해야 한다. 이러한 경우 설정 프로바이더가 없어도 오류가 발생하지 않으므로 의존성은 선택 사항이 된다.

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

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

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

 

위의 예시에서는 사용자 정의 프로바이더를 사용하고 있으며, 이것이 바로 HTTP_OPTIONS 사용자 정의 토큰을 사용하는 이유이다. 이전 예제에서는 생성자의 클래스를 통해 의존성을 나타내는 생성자 기반 인젝션을 보여주었다. 이 기술은 FUNDAMENTAL 파트에서 다룬다.

 

Property-based injection

지금까지 사용한 기술은 생성자 메서드를 통해 프로바이더를 주입하기 때문에 생성자 기반 주입이라고 한다. 매우 특정한 경우에는 프로퍼티 기반 주입이 유용할 수 있다. 예를 들어 최상위 클래스가 하나 또는 여러개의 프로바이더에 의존하는 경우 생성자에서 하위 클래스에서 super()를 호출하여 모든 프로바이더를 전달하는 것은 매우 지루할 수 있다. 이를 방지하기 위해 프로퍼티 수준에서 @Inject() 데코레이터를 사용할 수 있다.

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

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

 

Provider registration

이제 프로바이더(CatsService)를 정의했고 해당 서비스의 컨슈머(CatsController)가 있으므로 서비스를 Nest에 등록하여 인젝션을 수행할 수 있도록 해야한다. 모듈 파일(app.module.ts)을 편집하고 @Module() 데코레이터의 프로바이더 배열에 서비스를 추가하여 이를 수행한다.

// 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 클래스의 종속성 문제를 해결할 수 있다. 디렉토리 구조는 아래와 같을 것이다.