[ 살펴보기 ] NestJS - Module
![[ 살펴보기 ] NestJS - Module](https://cdn.hashnode.com/res/hashnode/image/upload/v1721292778473/f5e35317-3575-4389-a86f-3a9e7ca47c0f.jpeg)
NestJS는 서로 관련된 컴포넌트, 서비스, 컨트롤러를 모듈이라는 하나의 단위로 묶어 기능을 수행할 수 있도록 한다. 만약 쇼핑몰 관련 api를 제공한다면 상품과 관련된 controller, service등을 ProductModule로 구성하고, 그리고 고객과 관련된 controller, service등을 CustomerModule로 구성하며 각각의 module이 모여 전체 application 서비스를 이룬다 그리고 이렇게 각 목적에 따라 module을 구성하며 전체 코드의 응집도를 높인다
NestJS에서 module의 선언은 @module decorator를 통해 이루어 진다. 다음 예제를 살펴보자
@Module({
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {}
이전 post에서 살펴 보았듯이 Module을 통해 해당 module에서 사용할 controllder나 provider를 제공한다
그리고 아래와 같이 app.module.ts 파일에서 root module을 선언하며 application에서 사용될 module을 import한다
@Module({
imports: [UsersModule, OrdersModule],
...
})
export class AppModule {}
그리고 이전 post에서는 Service를 특정 module의 provider로 제공하여 해당 module의 provider나 service에서 사용하는 예제를 살펴보았다, 이번에는 특정 module을 제공하여 해당 module이 제공하는 service를 바로 사용할 수 있도록 해보자
User Module을 Order Module에 제공하여 Order Controller에서 User Module이 제공하는 UserService를 사용할 수 있게 해보겠다. 먼저 User Module에서 export하고 싶은 provider를 지정해준다. 다른 module에서 User Module을 주입 받아 사용할 때 export에 추가된 provider를 사용할 수 있게 된다
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // export service
})
export class UsersModule {}
그리고 Order Module에 다음과 같이 User Module을 제공해준다
@Module({
imports: [UsersModule], // user module
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {}
위와 같이 User Module을 제공받으면 Order controller혹은 service에서 다음과 같이 UserService를 사용할 수 있다
@Controller('orders')
export class OrdersController {
constructor(
private readonly ordersService: OrdersService,
private readonly usersService: UsersService,
) {}
@Get()
findAll() {
return this.usersService.findAll();
}
...
Global Module
NestJS의 provider는 자신이 소속된 module의 scope에 제한된다. 그렇기에 특정 provider를 쓰고 싶다면 해당 provider가 소속된 module을 import해서 사용해야 한다. 하지만 helper나 db connection과 같이 전역에서 사용되는 module은 전역 모듈로 선언하고 root module이나 전역 모듈을 모아서 관리하는 core module을 만들어서 거기에 한 번만 등록해주면 원하는 다른 곳에서 바로 주입하여 사용할 수 있다
테스트를 위해 User Module을 전역 모듈로 만들어 보자
@Global()
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Module({
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {}
위의 예제에서 볼 수 있듯이 UserModule에 @Global decorator를 추가해 User Module을 전역 module로 만들었다. 그리고 User Module은 이제 전역 모듈이므로 Order Module에서 User Module을 별도로 import하지 않아도 User Module에서 export하는 provider를 주입 받아 사용할 수 있다
별도로 import를 하지 않고 provider를 바로 주입하여 사용할 수 있기에 다른 module도 전역 모듈로 만들면 편할 것이라는 생각이 들 수 있지만 전역 module이 불필요하게 많을 수록 module간의 의존성 관리가 모호해진다. 그러므로 반드시 전체적으로 사용되는 module만 전역 module로 관리하고 module간의 의존성 관계를 import를 통해 명확히 하는 것이 좋다
Dynamic Module
위에서 살펴본 Module은 모두 정적 Module이다, 즉 Module이 제공하는 Provider를 그대로 사용하며 주입받는 Provider를 조건에 맞게 변경하진 못한다. 만약에 주입받는 Module을 옵션 값을 전달해 동적으로 변경하고 싶다면 Dynamic Module을 사용할 수 있다.
다음은 NestJS Documentation에서 가져온 예제이다.
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({})
export class ConfigModule {
static register(options: Record<string, any>): DynamicModule {
console.log(options)
return {
module: ConfigModule,
providers: [
ConfigService,
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
],
exports: [ConfigService],
};
}
}
위의 예제에서 볼 수 있듯이 Danymic Module은 register static method를 통해 parameter 받는다. 그리고 전달받은 parameter에 따라 다른 provider를 export하여 Module에서 제공하는 provider를 다르게 한다거나 또는 전달 받은 값을 provider에 등록하여 같은 module안에 소속된 provider에 주입하여 사용할 수도 있다.
위의 예제에선 register static method가 전달받은 parameter를 Config 모듈 provider에 등록하여 options이란 값은 'CONFIG_OPTIONS'이라는 토큰을 통해 같은 module에 속해있는 ConfigService에 주입할 수 있도록 했다.
아래는 config service 파일이다
import { Inject, Injectable } from '@nestjs/common';
import { CreateConfigDto } from './dto/create-config.dto';
import { UpdateConfigDto } from './dto/update-config.dto';
@Injectable()
export class ConfigService {
constructor(
// provider에 등록한 option을 주입
@Inject('CONFIG_OPTIONS') private options: Record<string, any>
) {}
create(createConfigDto: CreateConfigDto) {
return 'This action adds a new config';
}
findAll() {
console.log(this.options); // 주입된 option을 사용
return `This action returns all config`;
}
findOne(id: number) {
return `This action returns a #${id} config`;
}
update(id: number, updateConfigDto: UpdateConfigDto) {
return `This action updates a #${id} config`;
}
remove(id: number) {
return `This action removes a #${id} config`;
}
}
만약 Order module에서 위에 선언한 dynamic module을 사용한다면 다음과 같이 사용할 수 있을 것이다
@Module({
imports: [ConfigModule.register({ file: 'test-file-path' })],
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {}
Order module에서 register static method를 통해 test-file-path라는 값을 file parameter로 넘겨주고 있다. 그렇게 전달한 값은 Config Dynamic Module의 register static method가 전달 받으며 그 값을 이용해 Dynamic Module의 내부 처리를 분기할 수 있다.
![[ 살펴보기 ] RDB - Relationships](https://cdn.hashnode.com/res/hashnode/image/upload/v1739711556668/48dc9e84-a621-42aa-9c9f-5fc5c436f0ec.jpeg)
![[ 살펴보기 ] MySQL - Data types](https://cdn.hashnode.com/res/hashnode/image/upload/v1739593589113/530f8704-4d27-42c9-a451-bb5c63150b99.jpeg)
![[ 살펴보기 ] TypeORM - Transactions, Migration](https://cdn.hashnode.com/res/hashnode/image/upload/v1739106042581/980b8133-61d4-406a-a026-65be9c28eace.jpeg)
![[ 살펴보기 ] TypeORM - Relations](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666874402/b688bd0b-b6bb-4f43-87d8-c1b46b59f1b7.jpeg)
![[ 살펴보기 ] TypeORM - Basics](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666803591/bef5df17-7dc7-4123-ae55-004d5042df39.jpeg)