Module

장현욱(Artlogy)·2022년 10월 10일
0

Nest.js

목록 보기
4/18
post-thumbnail


모듈은 여러 기능들이 합쳐서 하나의 작업이 되는 단위를 뜻한다.
NestJS에서는 하나의 루트모듈이 무조건 존재하게되고 이것은 우리가 새로운 Nest 프로젝트를 만들때 자동으로 생성되는 app.modeul.ts이 그것이다. 이렇게 모듈을 나누는 이유는 책임을 나누고 응집도를 높이기 위함이다.

@Module


모듈은 @Module()데코레이터를 사용한다. 인자로는 ModuleMetadata를 받는데 인터페이스는 다음과 같다.

export declare function Module(metadata: ModuleMetadata): ClassDecorator;

export interface ModuleMetadata {
    imports?: Array<Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
    controllers?: Type<any>[];
    providers?: Provider[];
    exports?: Array<DynamicModule | Promise<DynamicModule> | string | symbol | Provider | ForwardReference | Abstract<any> | Function>;
}
  • import : 이 모듈에서 사용하고싶은 다른 모듈을 가져오기 위해 사용한다.
  • controller, provider : 모듈 전반에서 컨트롤러와 프로바이더를 사용할 수 있도록 Nest가 객체를 생성하고 주입할 수 있도록 해준다.
  • export : 이 모듈에서 제공하는 컴포넌트를 다른 모듈에서 import 해서 사용하고자 한다면 export 해야 한다. export로 선언했다는 뜻은 어디에서나 가져다 쓸 수 있으므로 public 인터페이스 또는 API로 간주된다.

모듈 다시 내보내기

진짜 별거없고 import한 모듈을 export로 다시 내보내는 작업이다.

  • common.module.ts
@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule { }
  • common.service.ts
@Injectable()
export class CommonService {
  hello(): string {
    return 'Hello from CommonService';
  }
}
  • core.module.ts
@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule { }
  • app.module.ts
@Module({
  imports: [CoreModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • app.controller.ts
@Controller()
export class AppController {
  constructor(private readonly commonService: CommonService) { }

  @Get('/common-hello')
  getCommonHello(): string {
    return this.commonService.hello();
  }
}

전역 모듈

Nest는 모듈 범위내에서 프로바이더를 캡슐화한다. 따라서 어떤 모듈에 있는 프로바이더를 사용하려면 모듈을 먼저 가져와야 한다. 하지만 헬퍼와 같은 공통 기능이나 DB 연결과 같은 전역적으로 쓸 수 있어야 하는 프로바이더가 필요한 경우가 있다. 이런 프로바이더를 모아 전역 모듈로 제공하면 좋다.

@Global()	//
@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule { }

@Global() 데코레이터만 선언하면 전역적인 모듈이 된다.
모듈은 응집도를 높이기 위함이라 했는데 모든 것을 전역으로 만들면 응집도가 떨어지게 된다.
때문에 꼭 필요한 것만 선언해주자.

동적 모듈

호스트에 따라 환경은 다르기 마련이며, 모듈 또한 호스트 환경에 따라 동적으로 구성이 가능하다.
NestJS가 내부적으로 제공해주는 @nestjs/config패키지를 활용해서 동적 모듈구성을 해보자.

$ yarn add @nestjs/config

이 패키지에선 ConfigModule이름을 가진 모듈이 이미 존재하기 때문에 imports를 통해 가져오면된다.

  • app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports:[ConfigModule.forRoot()],
})
export class AppModule {}

정적으로 모듈을 가져올 때와는 달리 .forRoot()를 호출하는 걸 볼 수 있는데, forRoot 메소드는 동적모듈을 리턴하는 정적 메서드이다.

동적 모듈을 작성할대 forRoot, register의 이름을 붙이는게 관례이다.

  • 비동기 경우 forRootAsync, registerAsync 이름을 붙인다.

이제 루트 디렉토리에 .env파일을 만들어 등록해보겠다.

.env파일은 환경변수를 정의하는 파일로 민감한 정보를 다수 포함하기 때문에
.gitignore에 등록하는 걸 추천한다. ( 예제에는 그냥 올라갈것이다. )

  • .dev.env
DATABASE_HOST=local
  • .prod.env
DATABASE_HOST=somecompany.dbdomain.com

후 서버를 실행할때 환경변수를 지정해주자

  • package.json
"start": "NODE_ENV=dev nest start",
"start:dev": "NODE_ENV=dev nest start --watch",
"start:prod": "NODE_ENV=prod node dist/main",

물론 실행 할 때마다 $ NODE_ENV=dev nest start --watch명령어를 입력해도 되나
너무 길어서 이렇게 매크로등록을 해둔것이다.

이제 환경변수에따라 읽어오는 .env파일을 동적으로 구성해보자.

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports:[ConfigModule.forRoot({
    envFilePath:(process.env.NODE_ENV === 'prod') ? '.prod.env' : '.dev.env'
  })],
  controllers:[AppController],
  providers:[AppService, ConfigService],
  exports:[]
})
export class AppModule {}

ConfigService .env파일의 내용에 접근 할 수 있는 메소드를 제공하는 프로바이더이다.

  • app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

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

  @Get()
  getHello(): string {    
    return this.configService.get('DATABASE_HOST');
  }
}

이제 환경을 달리하여 서버를 실행시킨 후 결과를 확인해보자.

커스텀 동적 모듈

지금까지 있는 모듈을 동적으로 사용했는데 이번엔 직접 커스텀모듈을 동적으로 할당해보자.

  • custom.module.ts
import { DynamicModule, Injectable, Module } from '@nestjs/common';
import { CustomService } from './custom.service';

export interface CustomOption {
    name:string,
    email:string
}

@Module({})
export class CustomModule {
  static forRoot(options : CustomOption): DynamicModule {
    return {
        module:CustomModule,
        providers: [
        CustomService,
        {
          provide: 'CUSTOM_OPTIONS',
          useValue: options,
        },
      ],
      exports: [CustomService,{
        provide: 'CUSTOM_OPTIONS',
        useValue: options,
      }],
    };
  }
}

핵심은 동적으로 값을 넣어주는 메소드(forRoot)의 리턴타입을 DynamicModule을 선언 해주는 것이다.

  • custom.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { CustomOption } from './custom.module';

@Injectable()
export class CustomService {
  constructor(@Inject('CUSTOM_OPTIONS') private readonly options : CustomOption){}
  getHello(): string {
    return `이름은 ${this.options.name} 이메일은 ${this.options.email}`;
  }
}

모듈에서 사용하는 프로바이더를 쓰는 서비스 하나를 만들었다.

  • app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CustomModule } from './custom.module';
import { CustomService } from './custom.service';

@Module({
  imports:[ConfigModule.forRoot({
    envFilePath:(process.env.NODE_ENV === 'prod') ? '.prod.env' : '.dev.env'
  }), CustomModule.forRoot({email:"wkdgusdnr55@gmail.com",name:"artlogy"})],
  controllers:[AppController],
  providers:[AppService, ConfigService, CustomService],
  exports:[]
})
export class AppModule {}

0개의 댓글