목차

프로바이더 사용

프로바이더 생성

프로바이더 등록

Custom Provider

  - Value Provider

  - Class Provider

  - Factory Provider

  - Alias Provider

  - Export Provider


 

 

 

 

프로바이더란 Spring에서 service의 역할을 한다고 보면된다.

핵심 기능의 비지니스 로직을 해결하는 방법을 다룬다.

단일 책임 원칙(SRP)를 지키기 위해 controller와 역할을 명확하게 나눈다.

 

nest 에서 제공하는 프로바이더의 핵심은 의존성을 주입할 수 있다는 것이다.

예제 코드를 보자.

 

프로바이더 사용

아래에서 보면 userService는 Controller의 생성자에서 주입받도록 되어 있는 프로바이더다.

외부에서 해당 서비스를 주입해 주는 것이다.

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
    ...

    @Delete(':id')
    remove(@Param('id') id: string) {
      return this.usersService.remove(+id);
    }
}

 

 

프로바이더 생성

어떻게 위 코드 처럼 외부에서 userService를 할당할 수 있을까?

방법은 간단하다 userService에 @Injectable 데코레이터를 설정해주면 된다.

별도 설정을 하지 않으면 일반적으로 싱글톤 인스턴스가 생성된다.

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

@Injectable() // 이것이 핵심!
export class UsersService {
    ...

  remove(id: number) {
    return `This action removes a #${id} user`;
  }
}

 

프로바이더 등록

프로바이더 인스턴스도 모듈에서 사용할 수 있도록 등록을 해 주어야 한다.

UserModule에 아래 처럼 등록하면 된다.

@Module({
    ...
  providers: [UsersService]
})
export class UsersModule {}

상속 관계에 있는 경우 프로바이더 주입은 어떻게 해야할까?

자식클래스에서 부모 클래스가 제공하는 함수를 호출하기 위해서는 부모 클래스에서 필요한 프로바이더를 super() 를 통해 전달해야한다.

// @Injectable() 이 선언되어 있지 않습니다. BaseService 클래스를 직접 참조하지 않기 때문입니다.
export class BaseService {
  constructor(private readonly serviceA: ServiceA) {}

  getHello(): string {
    return 'Hello World BASE!';
  }

  doSomeFuncFromA(): string {
    return this.serviceA.getHello();
  }
}

ServiceA

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

@Injectable()
export class ServiceA {
  getHello(): string {
    return 'Hello World A!';
  }
}

ServiceB

@Injectable()
export class ServiceB extends BaseService {
  getHello(): string {
    return this.doSomeFuncFromA();
  }
}

만약 컨트롤러에서 ServiceB를 주입받고 getHello()를 호출하면 BaseService의 doSomeFuncFromA 함수를 호출하게 된다. 

하지만 Base는 주입을 받을수 있는 클래스로 선언되지 않아서 ServiceA를 주입하지 않는다.

 

해당 동작을 실행해보면 에러가 발생한다. 이를 해결하기 위해서는 아래처럼 서비스 B에서 super를 통해 ServiceA의 인스턴스를 넘겨줘야한다.

@Injectable()
export class ServiceB extends BaseService {
  constructor(private readonly _serviceA: ServiceA) {
    super(_serviceA); // 서비스 넘겨줌
  }

  getHello(): string {
    return this.doSomeFuncFromA();
  }
}

이렇게 매번 super를 호출하지 않으려면 아래처럼 @Inject 데코레이터를 사용할 수 있다.

export class BaseService {
  @Inject(ServiceA) private readonly serviceA: ServiceA; // Inject!!
    ...

    doSomeFuncFromA(): string {
    return this.serviceA.getHello();
  }
}

 

 상속관계에 있는 경우 위처럼 속성 기반으로 @Inject를 이용하고, 이외에는 생성자에서 주입하도록 한다.

 

 

Custom Provider

아래 세 가지 경우는 커스텀 프로바이더를 사용해야 한다.

1. Nest 프레임워크가 만들어주는 인스턴스 또는 캐시된 인스턴스 대신 직접 생성할 경우

2. 여러 클래스가 의존관계에 있을 때 이미 존재하는 클래스를 재 사용할 때

3. 테스트를 위해 Mock 버전으로 프로바이더를 재정의 하는 경우

 

Value Provider

밸류 프로바이더는 provider와 useValue 속성을 가진다.

useValue는 어떤 타입도 받을 수 있기 때문에 useValue 구문을 넣어서 외부 라이브러리로부터 프로바이더를 삽입하거나 실제 구현을 모의 객체로 대체할 수 있다. 

// 모의 객체 선언
const mockCatsService = {
  /* 테스트에 적용할 값을 변경한다
  ...
  */
};

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

Class Provider

클래스 프로바이더는 useClass 속성을 사용한다.

프로바이더로 사용할 인스턴스를 동적으로 구성할 수 있다.

ConfigService가 있고 이를 상속받은 DevelopmerConfigService, ProductionConfigService가 있다고 할 때 상황에 따라 다른 ConfigService를 등록해서 사용할 수 있다.

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

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

Factory Provider

팩토리 프로바이더 또한 인스턴스를 동적으로 구성하고자 할 때 사용한다.

useFactory 속성을 이용한다.

아래 예시를 보자 CONNECTION 프로바이더 인스턴스를 생성하는 과정에서 OptionsProvider가 필요한 경우

const connectionFactory = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

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

 

Alias Provider

alias  프로바이더는 프로바이덩 별칭을 붙여 동일한 프로바이더를 별칭으로 접근할 수 있게 한다.

둘의 종속관계가 싱글톤일 경우 같은 인스턴스가 된다.

@Injectable()
export class LoggerService {
  private getHello(): string {
    return 'This is LoggerService provider';
  }
}
const loggerAliasProvider = {
  provide: 'AliasedLoggerService',
  useExisting: LoggerService,
};

@Module({
    ...
  providers: [LoggerService, loggerAliasProvider],
    ...
})
export class AppModule {}

LoggerService 프로바이더를 별칭(AliasedLoggerService)으로 다시 정의한 후 useExisting 속성에는 별칭프로바이더의 원본 프로바이더를 지정하여 직접 접근할 수 없었던 LoggerService를 사용한다고 선언한다.

 

@Controller()
export class AppController {
  constructor(
    @Inject('AliasedLoggerService') private readonly serviceAlias: any,
  ) {}

  @Get('/alias')
  getHelloAlias(): string {
    return this.serviceAlias.getHello();
  }
}

아래 처럼 설정 후 요청했을 때 정상적으로 This is LoggerService provider 가 노출되는 것을 확인 가능하다.

 

Export Provider

다른 모듈에 있는 프로바이더를 가져다 쓰기 위해서는 해당 모듈에서 내보내기를 해야한다.

내보내기는 토큰을 사용할 수도 있고, 프로바이더 객체를 사용할 수도 있다.

 

- 'CONNECTION' 토큰을 이용하는 경우

const connectionFactory = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

@Module({
  providers: [connectionFactory],
  exports: ['CONNECTION'],
})
export class AppModule {}

- connectionFactory 객체를 그대로 내보내는 경우

const connectionFactory = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

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

 

 

출처 : NestJS로 배우는 백엔드 프로그래밍

+ Recent posts