NESTJS를 배워보자(4) - Providers

yoon·2023년 7월 4일
1

NESTJS를 배워보자

목록 보기
4/21
post-thumbnail

Providers

nest의 공식문서를 토대로 작성합니다.

Providers는 Nest의 기초 컨셉입니다. services, repositories, factories, helpers 등 많은 기본 Nest 클래스는 provider로 취급됩니다.
provider의 주요 아이디어는 의존성을 주입할 수 있다는 것입니다. 즉 객체가 서로 많은 관계를 생성할 수 있습니다. 객체의 인스턴스를 '연결'하는 기능은 대부분 Nest 런타임 시스템에 위임할 수 있습니다.

이전 챕터에서 간단한 CatsController를 만들었습니다. 컨트롤러는 HTTP 요청을 처리하고 더 복잡한 작업은 providers에게 위임해야 합니다. providers는 모듈에서 providers라고 선언된 순수 자바스크립트 클래스입니다.

HINT
Nest를 사용하면 종속성을 보다 다양한 방식으로 설계하고 구성할 수 있으므로 SOLID 원칙을 따르는 것을 적극 권장

SOLID 원칙?

객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙입니다.

S(Single reponsibility principle)
단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.

O(Open/closed principle)
개방, 폐쇄의 원칙 : 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다

L(Liskov substitution principle)
리스코프 치환 원칙 : 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

I(Interface segregation principle)
인터페이스 분리 원칙 : 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

D(Dependency inversion principle)
의존관계 역전 원칙 : 프로그래머는 추상화에 의존해야지 구체화에 의존하면 안 된다.

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

이렇게 직접 파일을 만들어서 작성하셔도 되지만 저는 CLI를 사용하여 파일을 생성하겠습니다.

$ nest g s cats

현재 폴더 안에 src 폴더 안에 cats 폴더 안에 생성되니 현재 폴더를 잘 확인해주세요.

파일을 생성하고 위 내용을 옮겨 적습니다.

그 후 interface 폴더를 생성하고 cat.interface.ts파일을 다음과 같이 작성합니다.

# cat.interface.ts

export interface Cat {
  name: string;
  age: number;
  breed: string;
}

CatsService는 하나의 프로퍼티와 두 개의 메서드가 있는 기본 클래스입니다. 새로 보이는 @Injectable() 데코레이터는 메타데이터를 첨부하는데 이 메타데이터는 CatsService가 Nest IoC 컨테이너에서 관리할 수 있는 클래스임을 선언합니다.

이제 CatsController에서 CatsService를 사용해봅시다.

# cats.controller.ts

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from '../cats/dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interface/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();
  }
}

기존에 있던 findOne, update, remove가 없어진 것을 확인할 수 있는데 저도 당황스럽네요.. 아무튼 저도 공식문서대로 불필요한 부분은 지우고 진행하였습니다.

CatsService는 클래스 생성자를 통해 주입됩니다. private이 사용된 이유는 같은 위치에서 catsService 멤버를 즉시 선언하고 초기화할 수 있기 때문입니다.

현재까지의 폴더 구조입니다.

Dependency injection

Nest는 일반적으로 의존성 주입이라고 알려진 강력한 디자인 패턴을 기반으로 구축됩니다. 이 개념에 대한 자세한 내용은 Angular 공식 문서를 읽어보시기 바랍니다.
Nest는 TS의 기능 덕분에 종속성을 타입으로만 확인하기 때문에 매우 쉽게 관리할 수 있습니다. 아래 예시에서 Nest는 CatsService의 인스턴스를 생성하고 반환함으로써 catsService를 해결합니다. 이 종속성은 해결되어 컨트롤러의 생성자에게 전달되거나 지정된 프로퍼티에 할당됩니다.

constructor(private catsService: CatsService) {}

Scopes

providers는 일반적으로 애플리케이션 수명 주기와 동기화된 수명을 갖습니다. 애플리케이션이 실행되면 모든 종속성이 해결되어야 하므로 모든 provider가 인스턴스화되어야 합니다. 마찬가지로 애플리케이션이 종료되면 각 provider가 삭제됩니다. 하지만 provider 수명을 평생 요청 범위로 설정할 수 있습니다. 이 기술에 대한 자세한 내용은 여기로

공식 문서를 순차적으로 보고있기 때문에 나중에 다 다룰 것입니다. 궁금하신 분들은 보세요!!

Custom providers

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

Optional providers

때로는 반드시 해결해야 할 필요가 없는 종속성이 있을 수 있습니다. 예를 들어 클래스가 configuration object에 종속될 수 있지만 아무것도 전달되지 않으면 기본 값을 사용해야 합니다. 이 경우 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를 사용하고 있으며 이것이 바로 HTTP_OPTIONS custom 토큰을 포함하는 이유입니다. 이전 예에서는 constructor 클래스를 통해 종속성을 나타내는 constructor-based 주입을 보여주었습니다.
custom provider와 관련 토큰에 대해 더 자세히 보려면 여기로

Property-based injection

우리가 이제까지 사용한 기술은 constructor 메소드를 통해 provider를 주입하기 때문에 constructor-based 주입이라고 합니다. 매우 특정한 경우에는 property-based 주입이 유용할 수 있습니다. 예를 들어 최상위 클래스가 하나 또는 여러 개의 provider에 의존하는 경우 constructor에서 하위 클래스에서 super()를 호출하여 provider를 모두 전달하는 것은 매루 지루할 수 있습니다. 이를 방지하기 위해 property 수준에서 @Inject() 데코레이터를 사용할 수 있습니다.

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

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

Warning
클래스가 다른 provider를 확장하지 않는 경우 항상 constructor-based 주입을 사용해야 합니다.

Provider registration

이제 provider CatsService를 정의하고 해당 서비스를 CatsController가 사용하므로 서비스를 Nest에 등록하여 주입을 수행할 수 있도록 해야 합니다. 모듈 파일 app.module.ts를 편집하고 @Module() 데코레이터의 providers 배열에 서비스를 추가하여 이를 수행합니다.

저는 CLI로 파일을 생성했기 때문에 이미 들어가 있습니다!

# app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  imports: [],
  controllers: [AppController, CatsController],
  providers: [AppService, CatsService],
})
export class AppModule {}

이제 Nest가 CatsController 클래스의 종속성을 해결할 수 있습니다.

현재까지의 코드 및 구조가 궁금하신 분들은 제 코드를 보셔도 됩니다!
https://github.com/cxzaqq/cxzaqq-velog/tree/2.3-provider

Manual instantiation

지금까지 Nest가 종속성 해결을 위한 대부분의 세부 사항을 자동으로 처리하는 방법에 대해 설명했습니다. 특정 상황에서는 기본 제공 종속성 주입 시스템에서 벗어나 수동으로 provider를 검색하거나 인스턴스화해야 할 수도 있습니다. 아래에서는 이러한 두 가지 주제에 대해 간략하게 설명합니다.

기존 인스턴스를 가져오거나 provider를 동적으로 인스턴스화하려면 모듈 참조를 사용할 수 있습니다.

bootstrap() 함수 내에서 provider를 가져오려면(예: 컨트롤러가 없는 독립형 애플리케이션 또는 bootstrap 중에 configuration 서비스를 활용하려면) 독립형 애플리케이션을 참조.

아까도 말씀드렸지만 궁금하신 분들은 가서 보시면 되고 지금은 아 그냥 이런 게 있구나~ 정도만 생각하시면 됩니다!! 나중에 다 나오니까요 👍

고생하셨습니다!
다음 글에서 만나요~~😀


저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!

profile
백엔드 개발자 지망생

0개의 댓글