본문 바로가기
Node.js/Nest.js

NestJS Provider(프로바이더)

by Marades 2021. 8. 19.
반응형

Providers

Service, Repository, Factory, Helper 등등, 대부분의 Nest클래스는 프로바이더로 취급 가능하다.

프로바이더는 Nest에서 종속성 주입(Dependency Injection)이 가능한데, 이로 인해 Nest에서는 다양한 관계를 지닌 객체 인스턴스의 연결을 Nest의 런타임 시스템에 위임할 수 있다.

 

Nest에는 프로바이더 간의 관계를 담당하는 내장 IoC(Inversion of Control)컨테이너가 존재하는데

@Injectable 데코레이터를 사용하여 Nest IoC 컨테이너에서 관리할 수 있는 클래스임을 선언하는 메타데이터를 첨부할 수 있다.

 

Nest는 기본적으로 싱글톤 패턴을 따르는 프레임워크이다. Nest는 Bootstrap될 때 Module에 등록된 의존성들을 검사하고 프로바이더 인스턴스를 생성한다. 이는 애플리케이션 전체에서 단일 인스턴스로서 공유되며 이 덕에 Nest는 빠른 속도로 요청들을 처리할 수 있다.

기본적으로 Nest에서 인스턴스 수명은 애플리케이션과 동기화되며, 각 요청별 또는 호출 될 때마다 생성되고 Garbage-Collected되도록 따로 Scope를 지정할 수도 있다.

Example

// cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  findAll(): Cat[] {
    return this.cats;
  }
}
  • @Injectable() 데코레이터가 CatsService 클래스를 프로바이더로 표시

 

// cats.controller.ts
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}
  1. Nest가 providers를 컨트롤러 클래스에 주입하도록 요청
    1. 이를 통해 CatController 안에서 별도의 작업없이도 this를 통해 catService의 호출이 가능해진다.

 

// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
  1. provider에 CatsService를 넣어 Nest IoC 컨테이너에 Provider로서 등록한다.

Custom Provider

프로바이더는 service가 아니더라도 모든 값을 제공할 수 있다. 

이에 기반하여 표준 Provider에서 제공하는 것 이상의 기능이 필요할 경우 아래 등의 목적을 위해 Custom Provider를 제작할 수 있다.

  1. 사용자 지정 인스턴스를 만들고 싶을 때
  2. 두번째 종속성에서 기존 클래스를 재사용하려 할 때
  3. 테스트를 위해 모의(mock)버전으로 클래스를 재정의 할 때

축약 표기법과 명시적 표기법

Custom Provider를 사용하려면 먼저 Provider를 등록하는 명시적 방법을 알아야 한다.

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})

위는 nest g 명령어를 사용하면 흔히 볼 수 있는 기본적인 형태(축약표기법)이다.

아래는 위 구문의 명시적 형태이다.

@Module({
  controllers: [CatsController],
  providers: [
	  {
	    provide: CatsService,
	    useClass: CatsService,
	  },
	]
});

provide에 제공된 토큰과 useClass 에 제공된 값은 각각 일종의 key와 value가 되어 IoC Container에 등록되고 DI(Dependency Injection)을 진행할 수 있게 된다.


주요 사용

value provider : useValue

provide로 제공되는 key 값이 useValue에 제공되는 value값에 매칭된다.

사용목적은 아래와 같다.

  1. 상수값을 삽입 → provide에 들어갈 값을 costant.ts 등에서 미리 정의해두기도 함
  2. Nest컨테이너에 외부 라이브러리 삽입
  3. 실제 구현을 모의 객체로 대체 → 테스트

Example1

import { CatsService } from './cats.service';

const mockCatsService = {
  /* mock implementation
  ...
  */
};

@Module({
  imports: [CatsModule],
  providers: [
    {
      provide: CatsService,
      useValue: mockCatsService,
    },
  ],
})
export class AppModule {}

위의 예제에서 CatsService 토큰은 mockCatsService로 resovle된다.

이는 TypeScript의 Type Compatibility 덕분에 호환되는 인터페이스가 있는 모든 객체를 사용 가능하기 때문이다.

 

Example2

import { connection } from './connection';

@Module({
  providers: [{
      provide: 'CONNECTION',
      useValue: connection,
    }, {
      provide: 'MASTER_NAME',
      useValue: 'KGH'
    }],
})
export class AppModule {}

위와 같이 프로바이더를 정의했을 때 아래와 같은 방법들로 사용이 가능하다.

// DI 방식
@Injectable()
export class CatsRepository {
  constructor(@Inject('CONNECTION') connection: Connection) {	}
}

// 단순 삽입
@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];
  @Inject('MASTER_NAME') name: string;
}

 

Example3

// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: 'PORT',
      useValue: 3000
    }
  ],
})
export class AppModule {}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const port = app.get('PORT')
  console.log(port) // -> 3000
  await app.listen(port);
}
bootstrap();

위와 같이 등록된 프로바이더를 main.ts에서 app.get을 통해 접근할 수도 있다.


value provider : useClass

객체(프로바이더, 가드 등)를 재정의할 인스턴스를 제공하기 위해 인스턴스화될 클래스를 제공한다.

토큰이 확인하는 클래스를 동적으로 결정하며 다음과 같이 쓰일 수 있다.

  1. 객체(프로바이더, 가드 등)를 재정의
  2. 변수나 개발환경에 따른 제공 인스턴스 변경

Example1

const configServiceProvider = {
  provide: ConfigService,
  useClass:
    process.env.NODE_ENV === 'development'
      ? DevelopmentConfigService
      : ProductionConfigService,
};

@Module({
  providers: [configServiceProvider],
})
export class AppModule {}

위와 같이 애플리케이션이 개발버전인지, 배포버전인지에 따라 다른 Class를 사용할 수 있다.

 

Example2

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}

위의 예제는 모듈 외부에서 선언된 RolesGuard 객체를 의존성 주입하기 위해 사용

@nestjs/core

의 예제는 Nest는 위와 같이 Filter, Guard, Interceptor, pipe를 위한 상수를 제공한다.


value provider : useFactory

객체(프로바이더, 가드 등)를 재정의할 인스턴스를 제공하기 위해 인스턴스화될 클래스를 제공한다.

Factory라는 이름에서 보이듯, 동적으로 Provider를 생성할 수 있으며, 인수를 받고 함수로 값을 만드는 것 또한 가능하다. 또한 async/await을 사용하여 database연결 등의 비동기 작업에 대한 DI가 가능하다.

 

Example1

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    type: 'mysql',
    host: configService.get('HOST'),
    port: configService.get<number>('PORT'),
    username: configService.get('USERNAME'),
    password: configService.get('PASSWORD'),
    database: configService.get('DATABASE'),
    entities: [__dirname + '/**/*.entity{.ts,.js}'],
    synchronize: true,
  }),
  inject: [ConfigService],
});

인수를 받아 동적으로 인스턴스를 생성하는 예제이다.

 

Example2

{
  provide: 'ASYNC_CONNECTION',
  useFactory: async () => {
    const connection = await createConnection(options);
    return connection;
  },
}

 

Provider 파트는 DI개념을 처음 접했던 나로서는 Nest를 공부하면서 뭔가 100% 이해하기가 힘든 부분이였다.

하지만 프로바이더를 공부하며 SOLID원칙, DI, IoC등 Express로 개발하며 놓쳤던 개념들에 대해 공부할 수 있어서 의미 깊은 시간이였던 것 같다.(이런 시간을 갖게 해준 Mash-UP에게 Shout Out)

프로바이더와 DI와 관련된 내용은 Nest에 있어서도 매우 핵심적인 내용인 듯 하니 이를 아름답게 사용하기 위해선 꾸준한 공부와 연습이 필요할 것 같다.

 

Reference

https://dev.to/nestjs/advanced-nestjs-dynamic-providers-1ee

https://docs.nestjs.com/

반응형