Module : 응집성 있는 설계
일반적으로 모듈이라고 하면 조그만 클래스나 함수처럼 한 가지 일만 수행하는 소프트웨어 컴포넌트가 아니라,
여러 컴포넌트를 조합하여 좀 더 큰 작업을 수행할 수 있게 하는 단위 말합니다.
Nest 애플리케이션이 실행됙 위해서는 하나의 루트 모듈이 존재하고 이 루트 모듈은 다른 모듈들고 구성됩니다.
이렇게 모듈로 쪼개는 이유는 요러 모듈에 각기 맡은 바 책임을 나누고 응집도를 높이기 위함입니다.
MSA 아키텍쳐의 관점에서 모듈이 커지면 하나의 MSA로 분리 할 수도 있을겁니다.
모듈을 어찌 나눌 것인지에 대해 명확한 기준은 없습니다.
여러분이 설계를 하면서 또는 서비스가 커져가면서 유사한 기능끼리 모듈로 묶어야 하게 될 것입니다.
매우 작은 애플리케이션이라면 하나의 모듈만 있어도 충분하겠지만, 응집도를 높이는 작업을 게을리하면 의존 관계가 복잡한 코드로 변하는 것은 시간 문제입니다.
모듈은 @Module
데커레이터를 사용하빈다.
@Module
데이커레이터의 인수로 ModuleMetadata
를 받습니다.
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 | ForwardRefernce | Abstract<any> | Function>;
}
import
- 이 모듈에서 사용하기 위한 provider를 가지고 있는 다른 모듈을 가져옵니다.
- 예를 들면,
UsersModule
,OrdersModule
,ChatModule
을 가져와서 함께 빌드 되도록 합니다.
controller
/providers
- 모듈 전반에서 컨트롤러와 프로바이더를 사용할 수 있도록 Nest가 객체를 생성하고 주입할 수 있게 해줍니다.
export
- 모듈에서 제공하는 컴포넌트를 다른 모듈에서 가져오기 해서 사용하고자 한다면, export를 해야합니다.
모듈 다시 내보내기
가져온 모듈은 다시 내보내기가 가능합니다.
서비스 전반에 쓰이는 공통 기능을 모아 놓은 모듈을 CommonModule
, 공통 기능이기는 하지만 앱을 구동 시키는데 필요한 기능(로깅, 인터셉터)을 모아둔 모듈을 CoreModule 이라고 합시다.
AppMoudule
은 앱을 구동하기 위해 CoreModule
이 필요한데 CommonModule
의 기능도 필요합니다.
이런 경우 AppModule
은 둘 다를 가져오는 것이 아니라 CoreModule
만을 가져오고,
CoreModule
에서는 가져온 CoreModule
을 다시 내보내면, AppModule
에서 CommonModule
을 가져오지 않아도 사용할 수 있습니다.
CommonModule.ts
@Module({
providers: [CommonService],
exports: [CommonService]
})
export class CommonModule{ }
CommonModule
에는 CommonService
를 제공하고 있습니다.
CommonService.ts
@Injectable()
export class CommonService{
hello():string{
return ‘Hello from CommonService’;
}
}
CommonService
는 hello 라는 기능을 제공합니다.
CoreModule.ts
@Module({
imports: [CommonModule],
exports: [CommonMoudle]
})
export class CoreModule{ }
CommonService 는 hello라는 기능을 제공합니다.
CoreModule.ts
@Module({
imports: [CoreModule],
controllers: [AppController],
providers:[AppService]
})
export class AppModule{ }
AppModule은 CoreModule만 가져옵니다.
AppController.ts
@Controller()
export class AppController{
constructor(private readonly commonService: CommonService){
@Get(‘/common-hello’)
getCommonHello():string{
return this.commonService.hello();
}
}
}
이제 AppModule에 속한 AppController에서 CommonModule에 기술된 CommonService 프로바이더를 사용해봅시다.
전역 모듈
Nest는 모듈 범위 내에서 프로바이더를 캡슐화 합니다.
따라서 어떤 모듈에 있는 프로바이더를 사용하려면 모듈을 먼저 가져 와야합니다.
하지만 helper와 같은 공통 기능이나 DB연결과 같은 전역적으로 쓸수 있어야 하는 프로바이더가 필요한 경우가 있다.
이런 프로바이더를 모아 전역 모듈로 제공하면 된다.
전역 모듈을 만드는 방법은 @Global
데커레이터만 선언하면 됩니다.
Nest
프레임 워크의 강력함을 다시 한번 느낄 수 있습니다.
전역 모듈은 루트 모듈이나 코어 모듈에서 한번만 등록해야 합니다.
@Global()
@Module({
providers:[CommonService],
exports: [CommonService],
})
export class CommonModule{ }
객체 지향 언어를 많이 다뤄본 독자라면, 모든 것을 전역으로 만드는 게 SW구조상 좋지 않다는 것을 알고 있습니다.
모듈은 응집도를 높이기 위함이라고 했는데 모든 것을 전역으로 만들면 기능은 어디에나 존재하게 되며, 응집도가 떨어지게 됩니다.
꼭 필요한 기능만 모아 전역 모듈로 사용해야 합니다.
유저 서비스의 모듈 분리
- 사진 필요
우리가 만들고 있는 유저 서비스는 혀냊 루트 모듈인 AppModule
하나만 존재합니다.
우리 서비스가 발전할 경우, 다른 기능이 생길 것을 가정하고 유저 관리하는 기능을 UsersModule
로 분리하겠습니다.
또 이메일 전송 기능은 서비스 내에서 직접 구현하든 외부 이메일 서비스를 사용하든 다른 기능과 분리 되어야합니다.
MSA 개념을 적용했다면, 별개의 서비스로 분리할 수도 있겠지만, 모듈을 분리하여 유저 모듈과의 구현과 구분하겠습니다.
그림에서 마지막 진한 회색 박스는 외부 서비스를 나타냅니다.
UsersModule 분리
먼저 AppModule에서 유저 관리 기능을 UsersModule로 분리 한다.
nest g mo Users
생성된 UsersModule 클래스에 기존 UsersController와 UsersService를 추가 합니다.
또 UsersService에서 EmailService를 사용해야 하므로 함께 추가 합니다.
import { Module } from ‘@nestjs/common’
import { EmailService } from ‘src/email/email.service’;
import { UsersController } from ‘./users.controller’;
import { UsersService } from ‘./users.service’;
Module({
imports:[],
controllers: [UsersController],
providers:[UsersService, EmailService],
})
export class UserModule { }
AppModule에는 UsersModule이 Import되어 있는 것을 확인 할 수 있습니다.
이제 AppModule
에서 직접 UsersController
, UsersService
, EmailService
를 참조할 필요가 없으므로 제거 합니다.
import { Module } from ‘@nestjs/common’
import { UsersModule } from ‘./users/users.module’;
@Module({
imports:[UsersModule],
controllers:[],
providers:[]
})
export class AppModule { }
EmailModule 분리
이제 이메일 기능을 별도 모듈로 분리하여 관리 하도록 하기 위해 새로운 모듈과 서비스를 생성합니다.
nest g mo Email
EmailModule
에서 EmailService
를 제공하도록 하고 UsersService
가 속한 UsersModule
에서 사용하도록 해야 하므로 내보내기를 합니다.
import { Module } from ‘@nestjs/common’
import { EmailService } from ‘./email.service’;
@Module({
providers:[ EmailService ],
exports: [ EmailService ],
})
export class EmailModule { }
UsersModule
에는 EmailModule
을 가져오고, EmailService
를 직접 제공할 필요가 없기 때문에 삭제합니다.
import { Module } from ‘@nestjs/common’;
import { EmailModule } from ‘src/email/email.module’;
import { UsersController } from ‘./users.controller’;
import { UsersService } from ‘./users.service’;
@Module({
imports: [EmailModule],
controller: [UsersController],
providers:[UsersService],
})
export class UsersModule { }