NestJs Provider

이우길·2022년 3월 30일
0

NestJs

목록 보기
3/20
post-thumbnail

NestJs에서 Provider는 무엇일까?

Goal

  • NestJs의 Provider라는 개념을 이해하기

Provider란?

ProviderNestJs의 기본 개념이다. 기본 NestJs class의 대부분은 Service, Repository, Factory, Helper 등 여러가지 형태로 구현이 가능하다.

Provider의 핵심은 종속성으로 주입(Injection) 될 수 있다는 것이다.(스프링의 bean과 개념이 유사하다. LifeCycle을 가지며 NestJs안에 IoC가 관리를 한다.)

즉, 객체는 서로 다양한 관계를 생성할 수 있으며 객체와 객체의 관계를 맺어주는 역할을 분리할 수 있다. (해당 역할을 NestJs 런타임 시스템에 위임하게 되는 것)


Java에서는 Spring을 이용하여 DIP를 지킬 수 있게되고 Node에서는 NestJs를 이용히여 DIP를 지킬 수 있게 되는 것이다.

그렇다고 Java와 같이 NestJsInterface 타입으로 Provider를 등록하는 것은 불가능하다.(CustomProvider 글을 정리할 때 code로 작성해 볼 예정)


Provider라는 개념이 왜 나오게 되었을까?

공식 문서의 HINT에 적혀있는 내용을 보면 알 수 있다.

HINT
Since Nest enables the possibility to design and organize dependencies in a more OO-way, we strongly recommend following the SOLID principles.

객체지향의 속성들을 이용하여 보다 좋은 구조 및 code를 작성하기 위해서이다!


DIP(Dependency inversion principle)

객체는 저수준 모듈보다 고수준 모듈에 의존을 해야한다라는 원칙이지만 쉽게 이야기하여 구현 class(구현)에 의존하지 말고, 인터페이스(역할)에 의존하라는 뜻이다.

구현체를 직접 의존하고 있게 되면 코드의 유연성이 떨어지게 되며 구현체를 바꿀 때 OCP까지 위반되기 때문에 인터페이스를 의존하여 조립되는 시점에서 처리하여 의존성을 낮출 수 있는 장점이 있다.


Provider 사용법

등록하기

NestJs cli를 이용하여 project를 생성 후 src안에 있는 app.module.ts를 살펴보면 다음과 같은 코드가 보일 것이다. (만약 NestJs 프로젝트 구조가 궁금하다면 이전 글을 참조하자.)

@Module({
  // ...
  providers: [AppService],
})
export class AppModule {}

이미 AppService라는 Provider가 등록되어 있다. 이와 같이 Provider로 사용 할 classModule에 등록을 해주면 된다.


Injectable() 살펴보기

AppService class를 살펴보면 데코레이터로 @Injectable()이 붙어있는 것을 볼 수 있다.

@Injectable()
export class AppService { ... }

공식문서에 따르면 Injectable() 데코레이터를 사용하게 되면 NestJs IoC에 의해 관리되는 것이라고 명시해주는 것이라 한다.

@Injectable이라는 데코레이터가 붙어 있어야 현재 Module가 아닌 다른 Module에서도 DI를 받을 수 있게 된다. (같은 Module에서는 @Injectable이 없어도 가능하다.)


DI 받기

AppController에서 보면 생성자를 통해 AppServiceDI받고 있다. (property 주입도 가능하지만 생성자를 통해 주입받는 것을 지향하자.)

아래의 코드가 가능한 이유는 Typescript의 기능을 사용한 것이다. Typescript에서는 JS에서 사용할 수 없는 접근 지시자를 생성자 파라미터 앞에 지정해주면 해당 class의 property로 선언이 된다.

@Controller()
export class AppController {
  constructor(private readonly configService: ConfigService) {}
}

이 후 AppController안에서 this.configService라고 접근하여 사용할 수 있다.


다른 Module의 Provider를 DI받으려면?

여기서 중요한 점은 다른 Module에서 Provider를 주입을 받으려면 Provider를 정의한 Module에서 exports해줘야 한다. 만약 exports를 하지 않는다면 다음과 같은 error를 볼 수 있을 것이다.

//error
Error: Nest can not resolve dependencies of the [생성자에서 DI를 받으려는 class명] (?). \ 
Please make sure that the argument [Provider 명] at index [0] is available in the [현재 Moduel명] context.

//solution
Potential solutions:
- If [Provider 명] is a provider, is it part of the current [현재 Moduel명]?
- If [Provider 명] is exported from a separate @Module, is that module imported within [현재 Moduel명]?
  @Module({
    imports: [ /* the Module containing [Provider 명] */ ]
  })

간단하게 정리하자면

  • error: NestJs가 의존성 주입을 해결하지 못했다.

  • solution

    • 현재 의존성 주입을 하려는 Provider가 현재 Module의 일부인지 확인

    • 현재 Module가 아니라면 Provider를 정의한 Module에서 exports를 하였는지 확인

    • 현재 Module에서 Provider를 정의한 Moduleimports받았는지 확인


Optional Provider 주입받기.

DI를 받으려는 Provider가 없으면 다른 Module의 Provider를 DI받으려면?에서 봤던 것 처럼 error를 만날 수 있다.

하지만 반드시 DI를 받아야 하지 않는 Provider가 있을 수도 있다. 예를 들어 의존할 수 있지만 전달되지 않은 경우 기본값을 사용해야 할 때가 있다. 이러한 경우 @Optional()데코레이터를 이용하면 DI를 받으려는 Provider가 없더라도 위와 같은 error가 발생하지 않는다.

@Controller('cat')
export class CatController {
  constructor(@Optional() private readonly dogService: DogService){}

  @Get()
  printDog(){
    // 만약 DogService라는 Provider를 주입받았으면 `printDog()`를 실행한다.
    // DogService가 @Optional()이고 주입이 되지 않은 상태로 접근을 하면 error가 발생한다.
    // ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'printDog') TypeError: Cannot read properties of undefined (reading 'printDog')
    this.dogService.printDog();
  }
}

다음으로

다음으로는 CustomProvider를 어떻게 정의하는지와 NestJs에서는 Interface타입으로는 Provider를 정의할 수 없는데 그럼 어떻게 DIP를 지킬 수 있을까?에 대하여 알아보고자 한다.


Reference

profile
leewoooo

0개의 댓글