공식문서 공부하기
https://docs.nestjs.com/modules
필요한 부분만 발췌해서 정리
Module 정의
Module은 그냥 @Module() 데코레이터가 붙은 클래스이다. @Module() 데코레이터는 Nest가 application 구조를 만드는 데 사용할 metadata를 제공해준다.
모든 Nest application은 한 개의 root module(AppModule)을 갖는다. root module은 Nest가 application graph(module, provider 간의 관계와 의존성을 엮어주는 데이터 구조)를 그리는 시작점이 된다.
대부분의 application은 한 개가 아닌 여러 module이 있고, 공통 기능으로 컴포넌트를 특정 모듈 내에 분류해놓는다.
@Module() 데코레이터가 가지는 property는 아래와 같다.
providers | 이 module에서 정의되고 인스턴스화될 provider들 |
controllers | 이 module에서 정의되고 인스턴스화될 controller들 |
imports | 이 module에서 사용할 provider를 export하는 module들 |
exports | 이 module에서 제공되고, 다른 module에서 이 module을 import함으로써 사용될 수 있게끔 해야할 provider들 |
현재 module의 직접 포함되지 않았거나 import한 다른 module로부터 export되지 않은 provider를 사용하는 것은 불가능하다. 따라서, 그런 경우엔 어떤 module로부터 export된 provider를 사용하거나 API를 사용해야 한다.
Feature Modules
기능적으로 연관된 controller나 provider들은 한 module로 묶어서 사용하는 것이 유지보수에도 좋고 SOLID 원칙에 입각해서 개발할 수 있다.
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
이렇게 따로 module을 만들었다면, 그 module을 root module(AppModule)에서 import 해줘야 한다.
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
Shared Modules
기본적으로 Nest에서 module은 singleton이다. 따라서 어떤 provider든지 여러 module 간에 같은 instance로써 공유될 수 있다. 모든 module은 자동적으로 shared module로 취급된다. 한 번 만들어지면 어떤 module에서든지 재사용될 수 있다.
어떤 provider를 다른 module과도 공유하고 싶다면, 우선 그 provider를 export 해준다.
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
그리고 나서 그 provider를 export하고 있는 module을 다른 module에서 import해주면 된다.
import { Module } from '@nestjs/common';
import { AnimalController } from './animal.controller';
import { AnimalService } from './animal.service';
import { CatsModule } from './cats.module';
@Module({
imports: [CatsModule],
controllers: [AnimalController],
providers: [AnimalService],
})
export class CatsModule {}
Module re-exporting
module은 내부의 provider뿐만 아니라 import했던 module도 다시 export할 수 있다. 아래 예시에선 CoreModule이 CommonModule을 import하고 또 export하고 있다. 이렇게 하면 다른 module에서 CoreModule을 import하여 CommonModule까지 같이 이용할 수 있다.
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
Dependency injection
controller에서 service 의존성을 주입했던 것처럼, module 역시도 내부에서 provider의 의존성을 주입할 수 있다.
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {
constructor(private catsService: CatsService) {}
}
다만 module 자체는 의존성으로써 주입될 순 없다. (순환 참조 때문)
Global modules
어떤 module을 모든 곳에서 사용해야 할 경우, 일일히 import해주는 건 귀찮을 수 있다.
모든 provider들이 global scope인 Angluar와 달리, Nest는 module scope이고, module을 import하지 않는 이상 다른 모듈에서 그 module의 provider를 사용할 순 없다.
만약 모든 곳에서 사용할 수 있는 provider들(e.g. helper, database connection, etc, ..)을 묶어서 제공하고 싶다면 @Global() 데코레이터를 module에 사용해주면 된다.
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 {}
@Global() module은 반드시 한 번만 등록되어야 하고, 보통 root module이나 핵심 module에서 이를 수행한다. 위의 코드에선 CatsService가 CatsModule을 import할 필요 없이 어디서든지 사용 가능한 상태가 된다.
그러나 global module을 쓰는 건 별로 권장되는 방식은 아니다. provider를 module scope로 관리하는 것이 설계상 더 이롭다.
Dynamic modules
Dynamic module은 provider들을 동적으로(dynamically) 등록하고 구성할 수 있는 이점이 있다.
(자세한 건 여기)
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
providers: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
위의 예시에서, DatabaseModule은 기본적으로 'Connection' provider를 제공하지만, forRoot()로 들어오는 entities나 options에 따라 추가적인 provider를 제공할 수 있게 된다.
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 {}
+) dynamic module의 대표적인 활용예시는 typeorm 이다.
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.host,
port: +process.env.port,
username: process.env.username,
password: process.env.password,
database: process.env.database,
entities: [User, History],
synchronize: false,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}