NestJS 파헤치기4 - Modules

재로미·2022년 11월 27일
0

nestjs

목록 보기
4/5
post-thumbnail

Overview: App Architecture

NestJS 앱을 생성하면 기본적으로 app.controller.ts, app.module.ts, app.service.ts 등으로 보일러플레이트가 구성되어 있고 이를 기본으로 애플리케이션이 구동된다.

NestJS 파헤치기 1 말미에서 다룬 것처럼 Nest App을 최초 생성했을 때 아래와 같이 파일이 생성된다.

src
 |- main.ts                    # Nest 앱의 실행파일
 |- app.module.ts              # 실행파일에서 등록, 사용하는 root 모듈파일
 |- app.controller.ts          # Nest 앱의 root 기본 컨트롤러
 |- app.service.ts             # Nest 앱의 root 기본 비즈니스 서비스
 

기본적인 역할들은 주석에 명시해놓았지만 그래도 처음 보면 이해하기 어렵다. 하나씩 개념과 그 의의를 살펴보자. 이번 포스트에서는 이전 Providers에 이어 Modules에 대해 공부해보겠다.

Modules

기본 설명

프로젝트를 생성했을 때 처음으로 볼 수 있는 module인 app.module.ts를 살펴보자.

// app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule {}

모듈은 @Module 데코레이터를 통해 메타데이터를 생성하며, App Module은 root 모듈로써 하위에 CatsModule을 import해서 갖고 있다.

그 하위 cats module은 그렇다면 어떻게 구성되어 있을까?
cats.module.ts는 다음과 같다.

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

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  // exports: [CatsService] // CatsService를 다른 모듈에서 사용하도록 하고 싶을 때
})
export class CatsModule {}

아까 import는 안보이고 controllers와 providers 옵션이 있고 각각 CatsController와 CatsService를 갖는 것을 볼 수 있다. 이 controller와 service는 이전 포스트들에서 구현했으니 아직 모르겠다면 참고하도록 하자.

이 CatsModule은 쉽게 말하자면 구현했던 cats의 controller와 service, repository 등 provider들을 하나의 'cats' 모듈로 묶어주는 역할을 한다.

그리고 루트 모듈인 app module은 이 cats module을 import함으로써 nestJS 애플리케이션에서 사용될 기능을 제공한다.

module 도식

거시적인 관점으로 보면, module의 개념은 위 도식으로 설명할 수 있겠다.

모듈 구성 요소

공식 docs의 설명에 따르면 각 응용 프로그램에는 하나 이상의 모듈인 루트모듈이 있고 이는 Nest 프레임워크가 응용 프로그램 그래프를 빌드하기 위해 사용하는 시작점이다. 애플리케이션 내 구성요소들, 서비스들을 효과적으로 구성하려면 이 모듈을 사용하여 캡슐화 하는 것을 강력히 권장하고 있다.

@Module() 데코레이터 안에는 단일 객체가 들어가는 데 이 객체 내 속성들로는 다음과 같다. 아래 속성들은 복수형에 맞게, value로 배열([])을 취한다.

  • providers: Nest의 Injector에 의해 인스턴스화 되어 이 모듈내에서 공유될 수 있는 공급자(service, repository 등) 리스트
  • controllers : 모듈 내 인스턴스화 되어야할 기능에 부합하는 컨트롤러 리스트
  • imports : 이 모듈에 필요한 provider를 내보내는 가져온 외부 모듈 목록
  • exports : 이 모듈에서 제공하고, 모듈을 import하는 다른 모듈에서 사용할 수있는 provider의 하위 집합

이렇게 현재 모듈의 일부가 아니거나 가져온 모듈 쪽에서 export하지 않은 공급자를 inject 할 수 없게 nest에서는 모듈로 provider를 캡슐화한다.

exports 속성에 관해서

위 예제에서는 exports를 사용하는 부분은 주석처럼 부연설명을 달아 놓았는데, 이렇게 함으로써 CatsService 인스턴스가 모든 모듈에서 동일하게 공유되게 할 수 있다. 이는 Singleton 패턴을 지향하는 Nest의 철학이 강하게 반영된 것이다.
한편, module을 re-export하는 것은 다음과 같이 가능하다.

@Module({
  imports: [CommnonModule],
  exports: [CommonModule],
 })
export class CoreModule {}

이를 통해 가져온 CommonModule을 CoreModule에서 내보내고는 이 모듈을 가져오는 다른 모듈에서 사용할 수 있다.

모듈 클래스의 의존성 주입

모듈 클래스는 provider를 다음과 같이 주입할 수도 있다.

// 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 {
  constructor(private catsService: CatsService) {}
}

그러나 주의해야 할 것은 circular dependency 문제로 인해 모듈 클래스 자체는 provider 로 취급되어 주입될 수 없다는 것이다.

글로벌 모듈

Nest는 모듈 범위 내에서 provider를 캡슐화하기 때문에 모듈을 먼저 import 해야 해당 모듈의 provider를 다른 곳에서 사용할 수 있다.
만약 이 과정 없이 즉시 사용할 수 있는 provider를 제공하고 싶다면 @Module() 데코레이터 위에 @Global() 데코레이터로 글로벌 모듈로써 등록하면 된다.

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

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

이 데코레이터는 모듈을 전역으로 만듦으로써 imports 속성에 넣지 않고도 어느 곳에서든 import 할 수 있는 이점을 제공하지만 가급적이면 사용하지 않는 것을 Nest에서는 권장한다.

동적 모듈

// db.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';

@Module({
  providers: [Connection],
})
export class DatabaseModule {
  // forRoot() 메서드는 동적 모듈을 동기식이나 비동기식으로 반환할 수 있음
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}

동적 모듈 기능을 통해 사용자가 지정가능한 모듈을 쉽게 만들 수 있게 된다. 이렇게 만들고 다음처럼 root module에서 설정해주면 동적 모듈을 효율적으로 사용할 수 있게 된다.

import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

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

NestJS의 기본 구성 소개를 마치며...

이전 포스트들부터 이번 포스트까지 NestJS application을 이해하는데에 기초적인 provider, controller, module까지 알아보았다. 이 다음부터는 실전에서 적용해보고, 얻었던 인사이트들을 기록해보도록 하겠다.

profile
정확하고 체계적인 지식을 가진 개발자 뿐만 아니라, 가진 지식을 사람들과 함께 나눌 수 있는 계발자가 되고 싶습니다

0개의 댓글