[Nest.js] Provider 란 무엇인가!

정지현·2022년 10월 27일
1

본 글은 Nest.js 공식 도큐먼트 - Provider 를 바탕으로 정리한 글입니다.

이번에는 원문 번역한 것 같은 정리글이 아니라 진짜 정리를 해야지... -> 하지만 그렇게 되지 못 했습니다...

Provider

  • Provider 에는 서비스, 팩토리 등이 포함된다.

  • Provider 의 가장 메인 아이디어는 의존성으로서 주입될 수 있다는 데에 있다.

  • 의존성 주입을 통해 서로 다른 객체들이 다양한 관계를 맺을 수 있다. 물론 모듈에 필요한 Provider 들을 정의하고, 실제 시스템에 반영되는 내부적인 작업은 Nest 런타임 시스템이 위임하여 처리한다고 한다.

  • Provider 는 주로 Controller 를 통해 전달받은 요청에 대하여 복잡한 기능을 수행하는 비즈니스 로직이 담긴다.

  • 자바스크립트 클래스이며, 해당 클래스는 모듈 클래스 내 providers 속성에 정의된다. 그래야지 Nest 가 Provider 의 존재를 인지한다.

참고

공식 도큐먼트에 따르면, 객체지향 설계원칙, SOLID 를 강력히 권장한다고 한다.

서비스 (Services)

  • 다음 코드는 데이터 저장과 조회를 담당한다. 또한, 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;
  }
}

참고

서비스를 생성하려면 Nest CLI 명령어인 $ nest g service cats 를 사용하면 된다.

  • 클래스의 상단에 쓰인 @Injectable() 데코레이터를 사용하여 해당 서비스는 Nest IoC 컨테이너에 의해 관리됨을 나타낼 수 있다.

  • 해당 예제에서는 Cat 인터페이스를 정의하여 사용한다. 코드는 다음과 같다.

interfaces/cat.interface.ts

export interface Cat {
  name: string;
  age: number;
  breed: string;
}
  • 해당 서비스를 Controller 에서 사용하는 코드는 다음과 같다.
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 를 선언하고 초기화하여 동일한 클래스 내에서 해당 클래스의 멤버 메소드들을 사용할 수 있다.

의존성 주입 (Dependency Injection)

  • Nest 는 의존성 주입으로 알려진 디자인 패턴을 사용한다. 보다 자세한건 Angular 공식 도큐먼트를 참고하기를 권한다. (Nest.js 는 Angular 에 많은 영향을 받았다고 함.)

  • 아래 코드를 통해 Nest 는 CatsService 의 인스턴스를 생성하고 catsService 변수에 이를 주입한다.

constructor(private catsService: CatsService) {}

범위 (Scopes)

  • Provider 의 생명주기(도큐먼트에서는 lifetime("scope") 으로 표현되어있음)는 일반적으로 어플리케이션의 생명주기와 동일하다.

  • 어플리케이션이 실행될 때, 모든 의존성이 체크되고 이를 통해 모든 Provider 들이 인스턴스화 된다. 이와 유사하게, 어플리케이션이 꺼지면 모든 Provider 들은 소멸한다.

  • Provider 의 생명주기를 요청 단위 로 설정할 수 있는데, 이와 관련한 자세한 사항은 여기를 참고하면 된다.

Custom Providers

  • Nest 에는 Provider 들의 관계를 지정하기 위한 IoC 가 존재한다. 이러한 점을 활용하여 다양한 Provider 를 정의할 수 있다. 가령, Plain 값을 사용할 수 있고, 클래스, 동기/비동기 팩토리 등을 사용할 수 있다. 자세한 사항은 여기를 참고하면 된다.

Optional Providers

  • 간혹 필수적으로 주입되지 않아도 되는 의존성이 존재할 수도 있다. 예를 들어, 어떤 클래스는 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) {}
}
  • 보다 자세한 Custom Provider 를 알아보기 위해서는 여기를 참고하면 된다.

속성 기반 주입 (Property-based Injection)

  • 어떤 경우에는 생성자 기반 주입보다 속성 기반 주입 방식이 더 효과적일 수 있다. 예를 들어, 최상위 클래스가 하나 또는 여러 개의 Provider 에 의존한다면, 하위 클래스의 생성자에 매번 super() 키워드를 사용한다는 건 비효율적이다. (이와 관련하여 쉽게 정리된 이 블로그를 참고해보면 좋을 것 같다.)
import { Injectable, Inject } from '@nestjs/common';

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

주의

만일 클래스가 다른 Provider 를 상속받고 있지 않다면, 생성자 기반 주입 방식을 사용해야한다.

모듈에 Provider 정의

  • Nest 가 의존성 주입을 수행할 수 있도록 모듈에 Provider 를 정의하는 과정이 필요하다.

  • @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], // Provider 추가
})
export class AppModule {}
  • 이를 통해 CatsControllerCatsService 를 의존성 주입하여 사용할 수 있다.

수동 인스턴스화 (Manual Instantiation)

  • 특정 상황에서는 내장된 DI 시스템 바깥에서 특정 Provider 를 인스턴스화 해야할 수 있다.

  • 이미 인스턴스화 된 객체를 얻거나, Provider 를 동적으로 인스턴스화 하고 싶다면, Module Reference를 참고하면 된다.

  • bootstrap() 메소드에서 Provider 를 얻고 싶다면(컨트롤러가 존재하지 않는 Standalone 어플리케이션이라던가, Bootstrapping 중 Configuration 이 필요한 경우) Standalone Applications 항목을 참고하면 된다.

끗.

profile
나를 성장시키는 좌절에 감사하고 즐기려고 노력 중

0개의 댓글