๋์ ๋ชจ๋ (Dynamic modules)
๋ชจ๋ ์ฅ์์๋ Nest ๋ชจ๋์ ๊ธฐ๋ณธ ๊ฐ๋ ๊ณผ ๋์ ๋ชจ๋์ ๋ํ ๊ฐ๋จํ ์๊ฐ๋ฅผ ๋ค๋ฃน๋๋ค. ์ด ์ฅ์์๋ ๋์ ๋ชจ๋์ ์ฃผ์ ๋ฅผ ํ์ฅํ์ฌ ์ค๋ช ํฉ๋๋ค. ์ด ์ฅ์ ๋ง์น๋ฉด ๋์ ๋ชจ๋์ด ๋ฌด์์ด๋ฉฐ, ์ด๋ฅผ ์ด๋ป๊ฒ ๊ทธ๋ฆฌ๊ณ ์ธ์ ์ฌ์ฉํด์ผ ํ๋์ง๋ฅผ ์ ์ดํดํ ์ ์๊ฒ ๋ฉ๋๋ค.
์๊ฐ
๋ฌธ์์ Overview ์น์ ์ ์๋ ๋๋ถ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋ ์์ ๋ ์ผ๋ฐ์ ์ธ, ์ฆ ์ ์ ๋ชจ๋์ ์ฌ์ฉํฉ๋๋ค. ๋ชจ๋์ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ํ ๋ ์ผ๋ถ๋ก์จ ํจ๊ป ๋์ํ๋ ํ๋ก๋ฐ์ด๋๋ ์ปจํธ๋กค๋ฌ ๊ฐ์ ์ปดํฌ๋ํธ ๊ทธ๋ฃน์ ์ ์ํฉ๋๋ค. ๋ชจ๋์ ์ด๋ฌํ ์ปดํฌ๋ํธ์ ๋ํ ์คํ ์ปจํ ์คํธ (๋๋ ์ค์ฝํ)๋ฅผ ์ ๊ณตํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๋ชจ๋ ์์ ์ ์๋ ํ๋ก๋ฐ์ด๋๋ ํด๋น ๋ชจ๋์ ๋ค๋ฅธ ๊ตฌ์ฑ์์๊ฒ๋ export ์์ด๋ ์ ๊ทผํ ์ ์์ต๋๋ค. ๋ฐ๋ฉด, ์ด๋ค ํ๋ก๋ฐ์ด๋๊ฐ ๋ชจ๋ ์ธ๋ถ์์๋ ๋ณด์ด๋๋ก ํ๋ ค๋ฉด, ๋จผ์ ๊ทธ๊ฒ์ ํด๋น ๋ชจ๋์์ export ํ ํ, ์ด๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋์์ import ํด์ผ ํฉ๋๋ค.
์ต์ํ ์์ ๋ฅผ ํตํด ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ , UsersService๋ฅผ ์ ๊ณตํ๊ณ export ํ๋ UsersModule์ ์ ์ํฉ๋๋ค. UsersModule์ UsersService์ ํธ์คํธ ๋ชจ๋์ ๋๋ค.
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
๋ค์์ผ๋ก AuthModule์ ์ ์ํฉ๋๋ค. ์ด ๋ชจ๋์ UsersModule์ import ํ์ฌ, UsersModule์์ export ํ ํ๋ก๋ฐ์ด๋๋ค์ AuthModule ๋ด์์ ์ฌ์ฉํ ์ ์๊ฒ ํฉ๋๋ค.
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
์ด๋ฌํ ๊ตฌ์ฑ ๋๋ถ์, ์๋ฅผ ๋ค์ด AuthModule์ ์ํ AuthService์์ UsersService๋ฅผ ์ฃผ์ ํ ์ ์์ต๋๋ค.
@Injectable()
export class AuthService {
constructor(private usersService: UsersService) {}
/*
this.usersService๋ฅผ ํ์ฉํ๋ ๊ตฌํ๋ถ
*/
}
์ด๋ฅผ "์ ์ ๋ชจ๋ ๋ฐ์ธ๋ฉ (static module binding)"์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค. Nest๊ฐ ๋ชจ๋๋ค์ ์ฐ๊ฒฐํ๋ ๋ฐ ํ์ํ ๋ชจ๋ ์ ๋ณด๋ ์ด๋ฏธ ํธ์คํธ ๋ชจ๋๊ณผ ์๋น ๋ชจ๋์ ์ ์ธ๋์ด ์์ต๋๋ค. ์ด ๊ณผ์ ์์ ๋ฌด์จ ์ผ์ด ์ผ์ด๋๋์ง ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค. Nest๋ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก UsersService๋ฅผ AuthModule ๋ด์์ ์ฌ์ฉํ ์ ์๊ฒ ํฉ๋๋ค:
- UsersModule์ ์ธ์คํด์คํํฉ๋๋ค. ์ด๋ UsersModule์ด ์ฌ์ฉํ๋ ๋ค๋ฅธ ๋ชจ๋๋ค๋ ์ ์ด์ ์ผ๋ก import ๋๋ฉฐ, ๋ชจ๋ ์์กด์ฑ๋ ์ ์ด์ ์ผ๋ก ํด๊ฒฐ๋ฉ๋๋ค (์์ธํ ๋ด์ฉ์ Custom providers ์ฐธ๊ณ ).
- AuthModule์ ์ธ์คํด์คํํ๊ณ , UsersModule์์ export ํ ํ๋ก๋ฐ์ด๋๋ค์ AuthModule ๋ด์ ์ปดํฌ๋ํธ๋ค์ด ์ฌ์ฉํ ์ ์๋๋ก ๋ง๋ญ๋๋ค (๋ง์น AuthModule ๋ด์์ ์ง์ ์ ์ธ๋ ๊ฒ์ฒ๋ผ ๋์ํฉ๋๋ค).
- UsersService์ ์ธ์คํด์ค๋ฅผ AuthService์ ์ฃผ์ ํฉ๋๋ค.
๋์ ๋ชจ๋ ์ฌ์ฉ ์ฌ๋ก
์ ์ ๋ชจ๋ ๋ฐ์ธ๋ฉ ๋ฐฉ์์์๋, ์๋น ๋ชจ๋์ด ํธ์คํธ ๋ชจ๋์ ํ๋ก๋ฐ์ด๋ ๊ตฌ์ฑ ๋ฐฉ์์ ์ํฅ์ ์ค ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ์ด๊ฒ์ด ์ ์ค์ํ ๊น์? ์ด๋ค ๋ชจ๋์ด ์ผ๋ฐ์ ์ธ ๋ชฉ์ ์ ๊ฐ์ง๊ณ ์๊ณ , ๋ค์ํ ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์ํด์ผ ํ๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด ๋ด ์๋ค. ์ด๋ ๋ง์ ์์คํ ์์์ "ํ๋ฌ๊ทธ์ธ" ๊ฐ๋ ๊ณผ ์ ์ฌํ๋ฐ, ์ผ๋ฐ์ ์ธ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ ์ ์๋น์๊ฐ ํน์ ๊ตฌ์ฑ์ ์ง์ ํด์ค์ผ ํ๋ค๋ ์ ์์ ๊ทธ๋ ์ต๋๋ค.
Nest์์ ๋ํ์ ์ธ ์๋ ๊ตฌ์ฑ (configuration) ๋ชจ๋์ ๋๋ค. ๋ง์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ตฌ์ฑ ์ ๋ณด๋ฅผ ์ธ๋ถํํ๊ธฐ ์ํด ๊ตฌ์ฑ ๋ชจ๋์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ ์ฉํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์๋ก ๋ค๋ฅธ ๋ฐฐํฌ ํ๊ฒฝ์์ ์ค์ ์ ์ฝ๊ฒ ๋ฐ๊ฟ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๊ฐ๋ฐ์์ฉ ๊ฐ๋ฐ DB, ์คํ ์ด์ง/ํ ์คํธ ํ๊ฒฝ์ฉ DB ๋ฑ์ ๋๋ค. ๊ตฌ์ฑ ํ๋ผ๋ฏธํฐ ๊ด๋ฆฌ๋ฅผ ๊ตฌ์ฑ ๋ชจ๋์ ์์ํ๋ฉด, ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ค ์ฝ๋๋ ๊ตฌ์ฑ ์ธ๋ถ์ฌํญ๊ณผ ๋ถ๋ฆฌ๋์ด ๋ ๋ฆฝ์ฑ์ ์ ์งํ ์ ์์ต๋๋ค.
ํ์ง๋ง ๋ฌธ์ ๋ ๊ตฌ์ฑ ๋ชจ๋ ์์ฒด๊ฐ ์ผ๋ฐ์ (generic)์ธ ๊ตฌ์กฐ์ด๊ธฐ ๋๋ฌธ์, ์ด๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋์์ ๊ตฌ์ฒด์ ์ธ ์ค์ ์ ์ง์ ํด์ค์ผ ํ๋ค๋ ์ ์ ๋๋ค. ์ด๋ ๋์ ๋ชจ๋์ด ๋ฑ์ฅํฉ๋๋ค. ๋์ ๋ชจ๋ ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด, ๊ตฌ์ฑ ๋ชจ๋์ ๋์ ์ผ๋ก ๋ง๋ค์ด ์๋น ๋ชจ๋์ด ํด๋น ๋ชจ๋์ import ํ ๋ ์ด๋ค ๋ฐฉ์์ผ๋ก ๊ตฌ์ฑํ ์ง๋ฅผ API๋ฅผ ํตํด ์ ์ดํ ์ ์์ต๋๋ค.
์ฆ, ๋์ ๋ชจ๋์ ํ ๋ชจ๋์ ๋ค๋ฅธ ๋ชจ๋์ import ํ๋ฉด์ ๊ทธ import ์์ ์ ํด๋น ๋ชจ๋์ ์์ฑ๊ณผ ๋์์ ์ปค์คํฐ๋ง์ด์ฆํ ์ ์๋ API๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ ์ง๊ธ๊น์ง ๋ด์จ ์ ์ ๋ฐ์ธ๋ฉ ๋ฐฉ์๊ณผ๋ ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ๋๋ค.
Config ๋ชจ๋ ์์
์ด ์น์ ์์๋ ๊ตฌ์ฑ ์ฑํฐ (configuration chapter)์์ ์ฌ์ฉํ ์์ ์ฝ๋์ ๊ธฐ๋ณธ ๋ฒ์ ์ ์ฌ์ฉํ ๊ฒ์ ๋๋ค. ์ด ์ฅ์ ๋ง์ง๋ง๊น์ง ์์ฑ๋ ๋ฒ์ ์ ์๋ํ๋ ์์ ๋ก ๋ณ๋๋ก ์ ๊ณต๋ฉ๋๋ค.
์ง๊ธ ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ๊ณ ์ ํ๋ ์๊ตฌ์ฌํญ์ ConfigModule์ด ์ฌ์ฉ์ ์ ์ ์ต์ ๊ฐ์ฒด (options object)๋ฅผ ๋ฐ์์ ๊ตฌ์ฑ๋ ์ ์๋๋ก ํ๋ ๊ฒ์ ๋๋ค. ํ์ฌ์ ๊ธฐ๋ณธ ์์ ๋ .env ํ์ผ์ ์์น๋ฅผ ํ๋ก์ ํธ ๋ฃจํธ ํด๋์ ๊ณ ์ ํด๋๊ณ ์์ต๋๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ ์ด ์์น๋ฅผ ์ ์ฐํ๊ฒ ์ค์ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ณ ์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ๋ฌ ๊ฐ์ .env ํ์ผ์ ํ๋ก์ ํธ ๋ฃจํธ ํ์์ config ํด๋ (์: src์ ๊ฐ์ ๋ ๋ฒจ)์ ์ ์ฅํ๊ณ ์ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ก ๋ค๋ฅธ ํ๋ก์ ํธ๋ง๋ค ConfigModule์ ์ฌ์ฉํ ๋ ๋ค๋ฅธ ํด๋๋ฅผ ์ ํํ ์ ์๋๋ก ํ๊ณ ์ถ์ต๋๋ค.
๋์ ๋ชจ๋ (dynamic modules)์ import ํ๋ ๋ชจ๋์ ๋งค๊ฐ๋ณ์๋ฅผ ์ ๋ฌํด์ ๊ทธ ๋์์ ๋ณ๊ฒฝํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ด์ ์ด๊ฒ์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ , ์๋น์ ๋ชจ๋์ ์ ์ฅ์์ ์ต์ข ์ ์ผ๋ก ์ด๋ค ์ฝ๋๊ฐ ๋ ์ง๋ฅผ ๋จผ์ ์์ํด ๋ณด๊ณ , ๊ทธ ๋ค์ ๋ด๋ถ ๊ตฌํ์ ์ญ์ผ๋ก ๋ฐ๋ผ๊ฐ๋ ๊ฒ์ด ๋์์ด ๋ฉ๋๋ค.
์ฐ์ , ์ ์ ์ผ๋ก ConfigModule์ import ํ๋ ์์ ๋ฅผ ๊ฐ๋จํ ๋ฆฌ๋ทฐํด ๋ณด๊ฒ ์ต๋๋ค. (์ฆ, ๊ฐ์ ธ์จ ๋ชจ๋์ ๋์์ ์ ์ดํ ์ ์๋ ๊ธฐ์กด ๋ฐฉ์์ ๋๋ค.) @Module() ๋ฐ์ฝ๋ ์ดํฐ์ imports ๋ฐฐ์ด์ ์ ๋ด์ผ ํฉ๋๋ค:
@Module({
imports: [DogsModule],
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
๋์ ๋ชจ๋์ ๊ธฐ์กด ์ ์ ๋ชจ๋๊ณผ ๋์ผํ ์ธํฐํ์ด์ค ํํ์ ๊ฐ์ฒด๋ฅผ ๋ฐํํด์ผ ํ๋ฉฐ, ์ฌ๊ธฐ์ module์ด๋ผ๋ ์์ฑ ํ๋๊ฐ ์ถ๊ฐ๋ก ํฌํจ๋์ด์ผ ํฉ๋๋ค. ์ด module ์์ฑ์ ๋ชจ๋์ ์ด๋ฆ ์ญํ ์ ํ๋ฉฐ, ์๋ ์์ ์์์ฒ๋ผ ๋ชจ๋ ํด๋์ค ์ด๋ฆ๊ณผ ๋์ผํด์ผ ํฉ๋๋ค.
ํํธ
๋์ ๋ชจ๋์์ ์ต์ ๊ฐ์ฒด์ ๋ชจ๋ ์์ฑ์ module์ ์ ์ธํ๊ณ ์ ํ ์ฌํญ (optional)์ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด register()๋ผ๋ ์ ์ (static) ๋ฉ์๋๋ ์ด๋ค ์ญํ ์ ํ ๊น์? ์ด์ ์ฐ๋ฆฌ๋ ์ด ๋ฉ์๋๊ฐ DynamicModule ์ธํฐํ์ด์ค๋ฅผ ๋ฐ๋ฅด๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ์ญํ ์ ํ๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ์ด ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด, ์ ์ ๋ฐฉ์์์ ๋ชจ๋ ํด๋์ค ์ด๋ฆ์ imports์ ์ง์ ๋์ดํ๋ ๊ฒ๊ณผ ์ ์ฌํ๊ฒ, ํด๋น ๋ชจ๋์ imports ๋ฐฐ์ด์ ์ ๊ณตํ๊ฒ ๋ฉ๋๋ค. ์ฆ, ๋์ ๋ชจ๋ API๋ ๋จ์ํ ๋ชจ๋์ ๋ฐํํ๋ ๊ฒ์ธ๋ฐ, @Module ๋ฐ์ฝ๋ ์ดํฐ ์์์ ์์ฑ์ ๊ณ ์ ํด์ ์ค์ ํ๋ ๋์ , ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์ค์ ๊ฐ์ ์ง์ ํ๋ค๋ ์ ๋ง ๋ค๋ฆ ๋๋ค.
์ ์ฒด ๊ฐ๋ ์ ์์ฑํ๊ธฐ ์ํด ๋ช ๊ฐ์ง ์ธ๋ถ์ฌํญ์ ๋ ์์๋ ํ์๊ฐ ์์ต๋๋ค:
- ์ด์ @Module() ๋ฐ์ฝ๋ ์ดํฐ์ imports ์์ฑ์๋ ๋ชจ๋ ํด๋์ค ์ด๋ฆ (์: imports: [UsersModule]) ๋ฟ๋ง ์๋๋ผ, ๋์ ๋ชจ๋์ ๋ฐํํ๋ ํจ์ (์: imports: [ConfigModule.register(...)])๋ ์ ๋ฌํ ์ ์๋ค๋ ์ฌ์ค์ ๋ช ํํ ์ ์ ์์ต๋๋ค.
- ๋์ ๋ชจ๋๋ ์์ฒด์ ์ผ๋ก ๋ค๋ฅธ ๋ชจ๋๋ค์ import ํ ์ ์์ต๋๋ค. ์ด ์์ ์์๋ ๊ทธ๋ ๊ฒ ํ์ง ์๊ฒ ์ง๋ง, ๋์ ๋ชจ๋์ด ๋ค๋ฅธ ๋ชจ๋์ ํ๋ก๋ฐ์ด๋์ ์์กดํ๋ ๊ฒฝ์ฐ, imports ์์ฑ์ ์ฌ์ฉํ์ฌ ํด๋น ๋ชจ๋๋ค์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. ์ด๋ ์ ์ ๋ชจ๋์์ @Module() ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ ๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ์ธํ๋ ๋ฐฉ์๊ณผ ์์ ํ ๋์ผํฉ๋๋ค.
์ด์ ์ด๋ฌํ ๊ฐ๋ ์ ๋ฐํ์ผ๋ก, ์ฐ๋ฆฌ๊ฐ ๋ง๋ค๊ณ ์ ํ๋ ๋์ ConfigModule ์ ์ธ์ด ์ด๋ค ๋ชจ์ต์ด์ด์ผ ํ ์ง ์ดํด๋ณผ ์ ์๊ฒ ๋์์ต๋๋ค. ์ง์ ๊ตฌํํด ๋ด ์๋ค.
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({})
export class ConfigModule {
static register(): DynamicModule {
return {
module: ConfigModule,
providers: [ConfigService],
exports: [ConfigService],
};
}
}
์ด์ ๊ฐ ์์๋ค์ด ์ด๋ป๊ฒ ๋ง๋ฌผ๋ ค ๋์ํ๋์ง ๋ถ๋ช ํด์ก์ ๊ฒ์ ๋๋ค. ConfigModule.register(...)๋ฅผ ํธ์ถํ๋ฉด, ์ง๊ธ๊น์ง @Module() ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ํตํด ๋ฉํ๋ฐ์ดํฐ๋ก ์ ๊ณตํ๋ ์์ฑ๋ค๊ณผ ๋ณธ์ง์ ์ผ๋ก ๋์ผํ ์์ฑ๋ค์ ๊ฐ์ง DynamicModule ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
ํํธ
DynamicModule์ @nestjs/common์์ ์ํฌํธ ํ ์ ์์ต๋๋ค.
ํ์ง๋ง ํ์ฌ ์ฐ๋ฆฌ์ ๋์ ๋ชจ๋์ ์์ง ๊ทธ๋ค์ง ํฅ๋ฏธ๋กญ์ง ์์ต๋๋ค. ์์ ๋งํ๋ฏ์ด ๊ตฌ์ฑ ๊ฐ๋ฅํ๋๋ก ๋ง๋ค๊ฒ ๋ค๋ ๋ชฉ์ ์ ์์ง ๊ตฌํํ์ง ์์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด์ ๊ทธ ๋ถ๋ถ์ ๋ค๋ค๋ณด๊ฒ ์ต๋๋ค.
๋ชจ๋ ๊ตฌ์ฑ
ConfigModule์ ๋์์ ์ปค์คํฐ๋ง์ด์ฆํ๋ ๊ฐ์ฅ ๋ช ํํ ๋ฐฉ๋ฒ์, ์์ ์์ํ๋ฏ์ด ์ ์ register() ๋ฉ์๋์ ์ต์ ๊ฐ์ฒด (options object)๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๋๋ค. ๋ค์ ํ๋ฒ, ์๋น์ ๋ชจ๋์ imports ์์ฑ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';
@Module({
imports: [ConfigModule.register({ folder: './config' })],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
์ด์ ์ต์ ๊ฐ์ฒด๋ฅผ ๋์ ๋ชจ๋์ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ ๋ง๋ จ๋์์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด ์ด ์ต์ ๊ฐ์ฒด๋ฅผ ConfigModule ์์์ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์์๊น์? ์ ์ ์๊ฐํด ๋ด ์๋ค. ์ฐ๋ฆฌ๋ ConfigModule์ด ๊ฒฐ๊ตญ ์ฃผ์ ๊ฐ๋ฅํ ์๋น์ค์ธ ConfigService๋ฅผ ์ ๊ณตํ๊ณ export ํ๋ ํธ์คํธ ์ญํ ์ ํ๋ค๋ ๊ฑธ ์๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ค์ ๋ก๋ ์ด ConfigService๊ฐ ์ ๋ฌ๋ ์ต์ ๊ฐ์ฒด๋ฅผ ์ฝ์ด ๋ค์ฌ, ์์ ์ ๋์์ ์ปค์คํฐ๋ง์ด์ฆ ํด์ผ ํฉ๋๋ค. ์ผ๋จ ์ง๊ธ์, register() ๋ฉ์๋์์ ์ ๋ฌ๋ ์ต์ ์ ์ด๋ป๊ฒ ConfigService๊น์ง ์ ๋ฌํ ์ง๋ ์์ง ๊ตฌํํ์ง ์์๋ค๊ณ ๊ฐ์ ํ๊ณ , ์ต์ ๊ฐ์ฒด์ ์์ฑ์ ๋ฐ๋ผ ๋์์ ๋ณ๊ฒฝํ๋ ์์ผ๋ก ConfigService๋ฅผ ์ผ๋ถ ์์ ํด ๋ณด๊ฒ ์ต๋๋ค. (์ฐธ๊ณ : ํ์ฌ๋ ์ต์ ์ ๋ฌ์ด ๊ตฌํ๋์ง ์์์ผ๋ฏ๋ก, ์ผ๋จ์ ํ๋์ฝ๋ฉ๋ ์ต์ ๊ฐ์ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก ์์ฑํ๊ณ , ๊ณง์ด์ด ์ด๋ฅผ ๊ณ ์ณ๋๊ฐ ๊ฒ์ ๋๋ค.)
import { Injectable } from '@nestjs/common';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
import { EnvConfig } from './interfaces';
@Injectable()
export class ConfigService {
private readonly envConfig: EnvConfig;
constructor() {
const options = { folder: './config' };
const filePath = `${process.env.NODE_ENV || 'development'}.env`;
const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
this.envConfig = dotenv.parse(fs.readFileSync(envFile));
}
get(key: string): string {
return this.envConfig[key];
}
}
์ด์ ConfigService๋ options์์ ์ง์ ํ ํด๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํด .env ํ์ผ์ ์ฐพ์ ์ ์๊ฒ ๋์์ต๋๋ค.
์ด์ ๋จ์ ๊ณผ์ ๋, register() ๋จ๊ณ์์ ์ ๋ฌ๋ options ๊ฐ์ฒด๋ฅผ ConfigService์ ์ฃผ์ ํ๋ ๊ฒ์ ๋๋ค. ๋ฌผ๋ก ์ด ์์ ์ "์์กด์ฑ ์ฃผ์ (Dependency Injection)"์ ํตํด ์ํํฉ๋๋ค. ์ด ์ ์ ๋งค์ฐ ์ค์ํ๋ฏ๋ก ํ์คํ ์ดํดํด์ผ ํฉ๋๋ค. ์ง๊ธ ์ฐ๋ฆฌ๋ ConfigModule์ ํตํด ConfigService๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ConfigService๋ ๋ฐํ์์๋ง ์ ๋ฌ๋๋ options ๊ฐ์ฒด๋ฅผ ํ์๋ก ํฉ๋๋ค. ์ฆ, ์คํ ์์ ์ options ๊ฐ์ฒด๋ฅผ ๋จผ์ Nest์ IoC ์ปจํ ์ด๋์ ๋ฐ์ธ๋ฉํ๊ณ , ๊ทธ ํ Nest๊ฐ ์ด ๊ฐ์ฒด๋ฅผ ConfigService์ ์ฃผ์ ํ๋๋ก ๋ง๋ค์ด์ผ ํฉ๋๋ค. Custom Providers ์ฅ์์ ๋ฐฐ์ด ๊ฒ์ฒ๋ผ, Nest์ provider๋ ์๋น์ค๋ฟ ์๋๋ผ ๋จ์ํ ๊ฐ๋ ํฌํจํ ์ ์์ผ๋ฏ๋ก ์ด๋ฌํ ๋ฐฉ์์ผ๋ก options ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํ๋ ๋ฐ๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
๋จผ์ options ๊ฐ์ฒด๋ฅผ IoC ์ปจํ ์ด๋์ ๋ฐ์ธ๋ฉํ๋ ๋ฐฉ๋ฒ๋ถํฐ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ด ์์ ์ ์ ์ register() ๋ฉ์๋ ์์์ ์ํํฉ๋๋ค. ์ฐ๋ฆฌ๋ ํ์ฌ ๋์ ์ผ๋ก ๋ชจ๋์ ๊ตฌ์ฑํ๊ณ ์์ผ๋ฉฐ, ๋ชจ๋์ ์ฃผ์ ๊ตฌ์ฑ ์์ ์ค ํ๋๋ providers์ ๋๋ค. ๋ฐ๋ผ์ ํด์ผ ํ ์ผ์ options ๊ฐ์ฒด๋ฅผ provider๋ก ์ ์ํ๋ ๊ฒ์ ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ดํ ๋จ๊ณ์์ ConfigService ์์ options ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํ ์ ์๊ฒ ๋ฉ๋๋ค. ์๋ ์ฝ๋์์ providers ๋ฐฐ์ด์ ์ฃผ์ ๊น๊ฒ ์ดํด๋ณด์ธ์:
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({})
export class ConfigModule {
static register(options: Record<string, any>): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
ConfigService,
],
exports: [ConfigService],
};
}
}
์ด์ 'CONFIG_OPTIONS' ํ๋ก๋ฐ์ด๋๋ฅผ ConfigService์ ์ฃผ์ ํจ์ผ๋ก์จ ์ด ๊ณผ์ ์ ์์ ํ ๋ง๋ฌด๋ฆฌํ ์ ์์ต๋๋ค. ํด๋์ค๊ฐ ์๋ ๋ฌธ์์ด ํ ํฐ์ผ๋ก ํ๋ก๋ฐ์ด๋๋ฅผ ์ ์ํ ๊ฒฝ์ฐ์๋, ์ฌ๊ธฐ์์ ์ค๋ช ํ ๊ฒ์ฒ๋ผ ๋ฐ๋์ @Inject() ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค๋ ์ ์ ๊ธฐ์ตํ์ธ์.
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
import { Injectable, Inject } from '@nestjs/common';
import { EnvConfig } from './interfaces';
@Injectable()
export class ConfigService {
private readonly envConfig: EnvConfig;
constructor(@Inject('CONFIG_OPTIONS') private options: Record<string, any>) {
const filePath = `${process.env.NODE_ENV || 'development'}.env`;
const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
this.envConfig = dotenv.parse(fs.readFileSync(envFile));
}
get(key: string): string {
return this.envConfig[key];
}
}
๋ง์ง๋ง์ผ๋ก ํ ๊ฐ์ง ๋ง๋ถ์ด์๋ฉด, ์ ์์ ์์๋ ๋จ์ํ๋ฅผ ์ํด ๋ฌธ์์ด ๊ธฐ๋ฐ์ ์ฃผ์ ํ ํฐ ('CONFIG_OPTIONS')์ ์ฌ์ฉํ์ง๋ง, ๊ถ์ฅ๋๋ ๋ฐฉ์์ ์ด ํ ํฐ์ ๋ณ๋์ ํ์ผ์์ ์์(๋๋ Symbol)๋ก ์ ์ํ๊ณ import ํด์ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';
์์
์ด ์ฅ์ ์ ์ฒด ์ฝ๋ ์์ ๋ ์ฌ๊ธฐ์์ ํ์ธํ ์ ์์ต๋๋ค.
์ปค๋ฎค๋ํฐ ๊ฐ์ด๋๋ผ์ธ
@nestjs/packages์์ forRoot, register, forFeature ๊ฐ์ ๋ฉ์๋๋ค์ ์ฌ์ฉํ๋ ์์๋ฅผ ๋ณด์ จ์ ์๋ ์์ต๋๋ค. ์ด ๋ฉ์๋๋ค์ด ์ ํํ ์ด๋ค ์ฐจ์ด๋ฅผ ๊ฐ์ง๋์ง ๊ถ๊ธํ ์ ์๋๋ฐ์, ์๊ฒฉํ ๊ท์น์ด ์๋ ๊ฒ์ ์๋์ง๋ง, Nest์ ๊ณต์ ํจํค์ง๋ค์ ๋ค์๊ณผ ๊ฐ์ ๊ด๋ก์ ์ธ ๊ฐ์ด๋๋ผ์ธ์ ๋ฐ๋ฆ ๋๋ค:
๋ชจ๋์ ์์ฑํ ๋:
- register๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, ํธ์ถํ๋ ๋ชจ๋์์๋ง ์ฌ์ฉ๋๋ ํน์ ์ค์ ์ผ๋ก ๋์ ๋ชจ๋์ ๊ตฌ์ฑํ๋ ๊ฒ์ ๊ธฐ๋ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, @nestjs/axios์์: HttpModule.register({ baseUrl: 'someUrl' }). ๋ง์ฝ ๋ค๋ฅธ ๋ชจ๋์์ HttpModule.register({ baseUrl: 'somewhere else' })๋ฅผ ์ฌ์ฉํ๋ฉด, ๋ค๋ฅธ ์ค์ ์ ๊ฐ๊ฒ ๋ฉ๋๋ค. ์ํ๋ ๋งํผ์ ๋ชจ๋์ ๋ํด ์ด ์์ ์ ์ํํ ์ ์์ต๋๋ค.
- forRoot๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, ํ๋์ ์ค์ ์ผ๋ก ๋์ ๋ชจ๋์ ๊ตฌ์ฑํ๊ณ ์ด ์ค์ ์ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฌ์ฉํ๋ ๊ฒ์ ๊ธฐ๋ํฉ๋๋ค (๋๋๋ก ์ด ์ค์ ์ ์ถ์ํ๋์ด ์์ด์ ์ธ์งํ์ง ๋ชปํ ์ฑ๋ก ์ฌ์ฌ์ฉ๋๊ธฐ๋ ํฉ๋๋ค). ์ด ๋๋ฌธ์ GraphQLModule.forRoot(), TypeOrmModule.forRoot()์ ๊ฐ์ ๋ฐฉ์์ด ์ฌ์ฉ๋ฉ๋๋ค.
- forFeature๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, ๋์ ๋ชจ๋์ forRoot ์ค์ ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋, ํธ์ถํ๋ ๋ชจ๋์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ผ๋ถ ์ค์ ์ ์์ ํ๋ ค๋ ๊ฒฝ์ฐ์ ๋๋ค (์: ์ด ๋ชจ๋์ด ์ด๋ค ๋ ํฌ์งํฐ๋ฆฌ์ ์ ๊ทผํด์ผ ํ๋์ง, ํน์ ๋ก๊ฑฐ๊ฐ ์ด๋ค ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํด์ผ ํ๋์ง ๋ฑ).
์ด ๋ฉ์๋๋ค์ ์ผ๋ฐ์ ์ผ๋ก ๋น๋๊ธฐ ๋ฒ์ ์ธ reigsterAsync, forRootAsync, forFeatureAsync ๋ ํจ๊ป ์ ๊ณตํ๋ฉฐ, ์ด๋ค์ ์ค์ ์ Nest์ ์์กด์ฑ ์ฃผ์ ์ ํตํด ์ฒ๋ฆฌํ๋ค๋ ์ ๋ง ๋ค๋ฆ ๋๋ค.
๊ตฌ์ฑ ๊ฐ๋ฅํ ๋ชจ๋ ๋น๋ (Configurable module builder)
registerAsync, forRootAsync ๋ฑ์ ๋น๋๊ธฐ ๋ฉ์๋๋ฅผ ๋ ธ์ถํ๋ ๊ณ ๋๋ก ๊ตฌ์ฑ ๊ฐ๋ฅํ ๋์ ๋ชจ๋์ ์๋์ผ๋ก ๋ง๋๋ ์ผ์, ํนํ ์ด๋ณด์์๊ฒ๋ ๊ฝค ๋ณต์กํ ์ ์์ต๋๋ค. ์ด๋ฅผ ๋จ์ํํ๊ธฐ ์ํด Nest๋ ConfigurableModuleBuilder ํด๋์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด ํด๋์ค๋ ๋ช ์ค์ ์ฝ๋๋ง์ผ๋ก ๋ชจ๋์ "์ฒญ์ฌ์ง (blueprint)"์ ๊ตฌ์ฑํ ์ ์๋๋ก ๋์์ค๋๋ค.
์๋ฅผ ๋ค์ด, ์์์ ์ฌ์ฉํ ConfigModule ์์ ๋ฅผ ConfigurableModuleBuilder๋ฅผ ์ฌ์ฉํ๋๋ก ๋ณ๊ฒฝํด ๋ด ์๋ค. ์์ํ๊ธฐ์ ์์, ๋จผ์ ConfigModule์ด ์ด๋ค ์ต์ ์ ๋ฐ๋์ง ๋ํ๋ด๋ ์ ์ฉ ์ธํฐํ์ด์ค๋ฅผ ์ ์ํด์ผ ํฉ๋๋ค.
export interface ConfigModuleOptions {
folder: string;
}
์ด์ ์ค๋น๊ฐ ๋์์ผ๋, ๊ธฐ์กด์ config.module.ts ํ์ผ๊ณผ ๋๋ํ ์๋ก์ด ํ์ผ์ ํ๋ ๋ง๋ค๊ณ ์ด๋ฆ์ config.module-definition.ts๋ก ์ง์ ํ์ธ์. ์ด ํ์ผ์์๋ ConfigurableModuleBuilder๋ฅผ ํ์ฉํ์ฌ ConfigModule์ ์ ์๋ฅผ ๊ตฌ์ฑํ ๊ฒ์ ๋๋ค.
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { ConfigModuleOptions } from './interfaces/config-module-options.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<ConfigModuleOptions>().build();
์ด์ config.module.ts ํ์ผ์ ์ด์ด, ์๋ ์์ฑ๋ ConfigurableModuleClass๋ฅผ ํ์ฉํ๋๋ก ๊ตฌํ์ ์์ ํด ๋ด ์๋ค.
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass } from './config.module-definition';
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {}
ConfigurableModuleClass๋ฅผ ํ์ฅํจ์ผ๋ก์จ, ConfigModule์ ์ด์ ๊ธฐ์กด์ register ๋ฉ์๋๋ฟ๋ง ์๋๋ผ registerAsync ๋ฉ์๋๋ ์ ๊ณตํ๊ฒ ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ์๋น์๋ ๋น๋๊ธฐ ํฉํ ๋ฆฌ ๋ฑ์ ํ์ฉํ์ฌ ํด๋น ๋ชจ๋์ ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ๊ตฌ์ฑํ ์ ์๊ฒ ๋ฉ๋๋ค.
@Module({
imports: [
ConfigModule.register({ folder: './config' }),
// ๋๋ ๋ค์๊ณผ ๊ฐ์ด ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก๋ ๊ตฌ์ฑํ ์ ์์ต๋๋ค:
// ConfigModule.registerAsync({
// useFactory: () => {
// return {
// folder: './config',
// }
// },
// inject: [...์ถ๊ฐ ์์กด์ฑ ์ฃผ์
๋์๋ค...]
// }),
],
})
export class AppModule {}
registerAsync ๋ฉ์๋๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ๋ฐ์ต๋๋ค:
{
/**
* ์ฃผ์
ํ ํฐ์ผ๋ก ์ฌ์ฉ๋ ํด๋์ค์
๋๋ค. ์ด ํด๋์ค๋ ํ๋ก๋ฐ์ด๋๋ก ์ธ์คํด์คํ๋๋ฉฐ,
* ํด๋น ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ ์์ด์ผ ํฉ๋๋ค.
*/
useClass?: Type<
ConfigurableModuleOptionsFactory<ModuleOptions, FactoryClassMethodKey>
>;
/**
* ๋ชจ๋์ ๊ตฌ์ฑํ๊ธฐ ์ํ ์ต์
๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ํฉํ ๋ฆฌ ํจ์์
๋๋ค.
* ๋๊ธฐ ๋๋ ๋น๋๊ธฐ(Promise) ๋ฐฉ์์ผ๋ก ๋์ํ ์ ์์ต๋๋ค.
*/
useFactory?: (...args: any[]) => Promise<ModuleOptions> | ModuleOptions;
/**
* ํฉํ ๋ฆฌ ํจ์์์ ์ฃผ์
๋ฐ์ ์์กด์ฑ ๋ชฉ๋ก์
๋๋ค.
*/
inject?: FactoryProvider['inject'];
/**
* ๊ธฐ์กด์ ๋ฑ๋ก๋ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฃผ์
ํ ํฐ์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
* ์ด ํ๋ก๋ฐ์ด๋๋ ์ง์ ๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ ์์ด์ผ ํฉ๋๋ค.
*/
useExisting?: Type<
ConfigurableModuleOptionsFactory<ModuleOptions, FactoryClassMethodKey>
>;
}
๋ค์์ ์ ์์ฑ๋ค์ ๋ํ ํ๋ํ๋์ ์ค๋ช ์ ๋๋ค;
- useFactory - ์ค์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ํจ์์ ๋๋ค. ๋๊ธฐ ๋๋ ๋น๋๊ธฐ ํจ์์ผ ์ ์์ต๋๋ค. ํฉํ ๋ฆฌ ํจ์์ ์์กด์ฑ์ ์ฃผ์ ํ๋ ค๋ฉด inject ์์ฑ์ ์ฌ์ฉํ์ธ์. ์ ์์ ์์ ์ด ๋ฐฉ์์ ์ฌ์ฉํ์ต๋๋ค.
- inject - ํฉํ ๋ฆฌ ํจ์์ ์ฃผ์ ๋ ์์กด์ฑ๋ค์ ๋ฐฐ์ด์ ๋๋ค. ์์กด์ฑ์ ์์๋ ํฉํ ๋ฆฌ ํจ์์ ๋งค๊ฐ๋ณ์ ์์์ ์ผ์นํด์ผ ํฉ๋๋ค.
- useClass - ํ๋ก๋ฐ์ด๋๋ก ์ธ์คํด์คํ๋ ํด๋์ค์ ๋๋ค. ํด๋น ํด๋์ค๋ ์ค์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ create() ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ ์๋์ Custom method key ์น์ ์ ์ฐธ๊ณ ํ์ธ์.
- useExisting - ์ด๋ฏธ ๋ฑ๋ก๋ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฌ์ฉํ๋ useClass์ ๋ณํ์ ๋๋ค. Nest๊ฐ ์ ์ธ์คํด์ค๋ฅผ ์์ฑํ์ง ์๊ณ ๊ธฐ์กด์ ๋ฑ๋ก๋ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฌ์ฉํ๋๋ก ํ ๋ ์ ์ฉํฉ๋๋ค. ์ด ํด๋์ค ์ญ์ useClass์์ ์ฌ์ฉํ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํ๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก create() ๋ฉ์๋๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค. (๋จ, ๊ธฐ๋ณธ ๋ฉ์๋ ์ด๋ฆ์ Custom method key ์น์ ์์ ์ฌ์ ์ํ ์ ์์ต๋๋ค.)
์ ์ต์ ๋ค (useFactory, useClass, useExisting) ์ค ํ๋๋ง ์ ํํด์ ์ฌ์ฉํด์ผ ํ๋ฉฐ, ์ด๋ค์ ์๋ก ๋ฐฐํ์ ์ ๋๋ค.
๋ง์ง๋ง์ผ๋ก, ์ง๊ธ๊น์ง ์ฌ์ฉํ๋ 'CONFIG_OPTIONS' ๋์ , ์๋ ์์ฑ๋ ๋ชจ๋ ์ต์ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฃผ์ ๋ฐ๋๋ก ConfigService ํด๋์ค๋ฅผ ์ ๋ฐ์ดํธํด ๋ด ์๋ค.
@Injectable()
export class ConfigService {
constructor(@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions) { ... }
}
์ปค์คํ ๋ฉ์๋ ํค
ConfigurableModuleClass๋ ๊ธฐ๋ณธ์ ์ผ๋ก register ๋ฐ ๊ทธ ๋์ ๋ฉ์๋์ธ registerAsync๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ค๋ฅธ ๋ฉ์๋ ์ด๋ฆ์ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด, ConfigurableModuleBuilder#setClassMethodName ๋ฉ์๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค:
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<ConfigModuleOptions>().setClassMethodName('forRoot').build();
์ด ๊ตฌ์ฑ์ ConfigurableModuleBuilder์๊ฒ forRoot์ forRootAsync ๋ฉ์๋๋ฅผ ๋ ธ์ถํ๋ ํด๋์ค๋ฅผ ์์ฑํ๋ผ๊ณ ์ง์ํฉ๋๋ค. ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
@Module({
imports: [
ConfigModule.forRoot({ folder: './config' }), // <-- "register" ๋์ "forRoot" ์ฌ์ฉ์ ์ฃผ์ํ์ธ์
// ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์๋ ์์ต๋๋ค:
// ConfigModule.forRootAsync({
// useFactory: () => {
// return {
// folder: './config',
// }
// },
// inject: [...any extra dependencies...]
// }),
],
})
export class AppModule {}
์ปค์คํ ์ต์ ํฉํ ๋ฆฌ ํด๋์ค
registerAsync ๋ฉ์๋ (๋๋ ์ค์ ์ ๋ฐ๋ผ forRootAsync๋ ๋ค๋ฅธ ์ด๋ฆ)์์๋, ์๋น์๊ฐ ๋ชจ๋ ์ค์ ์ ๋ฐํํ๋ provider ์ ์๋ฅผ ์ ๋ฌํ ์ ์๋๋ก ํด ์ค๋๋ค. ๋ฐ๋ผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์๋น์๋ ์ค์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉํ ํด๋์ค๋ฅผ ์ ๋ฌํ ์๋ ์์ต๋๋ค.
@Module({
imports: [
ConfigModule.registerAsync({
useClass: ConfigModuleOptionsFactory,
}),
],
})
export class AppModule {}
์ด ํด๋์ค๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ค์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ create() ๋ฉ์๋๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค. ํ์ง๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ค๋ฅธ ๋ค์ด๋ฐ ๊ท์น์ ๋ฐ๋ฅด๋ ๊ฒฝ์ฐ, ConfigurableModuleBuilder#setFactoryMethodName ๋ฉ์๋๋ฅผ ์ฌ์ฉ (์: createConfigOptions์ ๊ฐ์ ๋ค๋ฅธ ๋ฉ์๋)ํ๋๋ก ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<ConfigModuleOptions>().setFactoryMethodName('createConfigOptions').build();
์ด์ ConfigModuleOptionsFactory ํด๋์ค๋ create ๋ฉ์๋ ๋์ createConfigOptions ๋ฉ์๋๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค.
@Module({
imports: [
ConfigModule.registerAsync({
useClass: ConfigModuleOptionsFactory, // <-- ์ด ํด๋์ค๋ "createConfigOptions" ๋ฉ์๋๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค
}),
],
})
export class AppModule {}
์ถ๊ฐ ์ต์
๋ชจ๋์ด ํน์ ๋์ ๋ฐฉ์์ ๊ฒฐ์ ํ๊ธฐ ์ํ ์ถ๊ฐ ์ต์ ์ ๋ฐ์์ผ ํ๋ ํน์ํ ๊ฒฝ์ฐ๋ค์ด ์์ต๋๋ค. ๋ํ์ ์ธ ์๋ก๋ isGlobal (๋๋ global) ํ๋๊ทธ์ฒ๋ผ, ๋ชจ๋์ด ์ ์ญ์ผ๋ก ๋ฑ๋ก๋์ด์ผ ํ๋์ง๋ฅผ ๋ํ๋ด๋ ์ต์ ์ด ์์ต๋๋ค. ์ด๋ฌํ ์ต์ ๋ค์ ์๋น์ค๋ ํ๋ก๋ฐ์ด๋ (ConfigService ๋ฑ)์ ์ง์ ์ ์ผ๋ก ๊ด๋ จ๋์ง ์๊ธฐ ๋๋ฌธ์, MODULE_OPTIONS_TOKEN ํ๋ก๋ฐ์ด๋์ ํฌํจ๋์ด์๋ ์ ๋ฉ๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์๋ ConfigurableModuleBuilder#setExtras ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ค์ ์์ ๋ฅผ ์ฐธ๊ณ ํ์ธ์:
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<ConfigModuleOptions>()
.setExtras(
{
isGlobal: true,
},
(definition, extras) => ({
...definition,
global: extras.isGlobal,
}),
)
.build();
์ ์์ ์์ setExtras ๋ฉ์๋์ ์ ๋ฌ๋ ์ฒซ ๋ฒ์งธ ์ธ์๋ "์ถ๊ฐ (extra)" ์์ฑ๋ค์ ๊ธฐ๋ณธ๊ฐ์ ๋ด์ ๊ฐ์ฒด์ ๋๋ค. ๋ ๋ฒ์งธ ์ธ์๋ ์๋ ์์ฑ๋ ๋ชจ๋ ์ ์ ๊ฐ์ฒด (provider, exports ๋ฑ ํฌํจ)์ ์๋น์ ๋๋ ๊ธฐ๋ณธ๊ฐ์ ์ํด ์ค์ ๋ extras ๊ฐ์ฒด๋ฅผ ๋ฐ์, ์์ ๋ ๋ชจ๋ ์ ์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ํจ์์ ๋๋ค. ์ ์์ ์์๋ extras.isGlobal ์์ฑ์ ๊บผ๋ด์ด ๋ชจ๋ ์ ์์ global ์์ฑ์ ํ ๋นํ๊ณ ์์ต๋๋ค. ์ด ์ค์ ์ ํด๋น ๋ชจ๋์ด ์ ์ญ ๋ชจ๋์ธ์ง ์๋์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค (์์ธํ ๋ด์ฉ์ ์ฌ๊ธฐ์์ ํ์ธํ ์ ์์ต๋๋ค).
์ด์ ์ด ๋ชจ๋์ ์ฌ์ฉํ ๋, ์๋์ ๊ฐ์ด ์ถ๊ฐ์ ์ธ isGlobal ํ๋๊ทธ๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค:
@Module({
imports: [
ConfigModule.register({
isGlobal: true,
folder: './config',
}),
],
})
export class AppModule {}
ํ์ง๋ง isGlobal์ "extra" ์์ฑ์ผ๋ก ์ ์ธ๋์๊ธฐ ๋๋ฌธ์, ์ด ๊ฐ์ MODULE_OPTIONS_TOKEN ํ๋ก๋ฐ์ด๋์ ํฌํจ๋์ง ์์ต๋๋ค:
@Injectable()
export class ConfigService {
constructor(
@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions,
) {
// "options" ๊ฐ์ฒด์๋ "isGlobal" ์์ฑ์ด ์กด์ฌํ์ง ์์ต๋๋ค
// ...
}
}
์๋ ์์ฑ๋ ๋ฉ์๋ ํ์ฅํ๊ธฐ
register, registerAsync ๋ฑ๊ณผ ๊ฐ์ ์๋ ์์ฑ๋ ์ ์ (static) ๋ฉ์๋๋ ํ์์ ๋ฐ๋ผ ๋ค์๊ณผ ๊ฐ์ด ํ์ฅํ ์ ์์ต๋๋ค.
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {
static register(options: typeof OPTIONS_TYPE): DynamicModule {
return {
// ์ฌ๊ธฐ์ ์ฌ์ฉ์ ์ ์ ๋ก์ง์ ์ถ๊ฐํ ์ ์์ต๋๋ค
...super.register(options),
};
}
static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
return {
// ์ฌ๊ธฐ์ ์ฌ์ฉ์ ์ ์ ๋ก์ง์ ์ถ๊ฐํ ์ ์์ต๋๋ค
...super.registerAsync(options),
};
}
}
OPTIONS_TYPE๊ณผ ASYNC_OPTIONS_TYPE ํ์ ์ ๋ชจ๋ ์ ์ ํ์ผ์์ export ๋์ด์ผ ํ๋ค๋ ์ ์ ์ฃผ์ํ์ธ์.
export const {
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
OPTIONS_TYPE,
ASYNC_OPTIONS_TYPE,
} = new ConfigurableModuleBuilder<ConfigModuleOptions>().build();
์ฃผ์ ๋ฒ์ (Injection scopes)
๋ค๋ฅธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด ๋ฐฐ๊ฒฝ์ ๊ฐ์ง ์ฌ๋๋ค์๊ฒ๋ Nest์์๋ ๊ฑฐ์ ๋ชจ๋ ๊ฒ์ด ๋ค์ด์ค๋ ์์ฒญ ๊ฐ์ ๊ณต์ ๋๋ค๋ ์ ์ด ์์ ๋ฐ์ผ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ์ปค๋ฅ์ ํ, ์ ์ญ ์ํ๋ฅผ ๊ฐ์ง ์ฑ๊ธํด ์๋น์ค ๋ฑ์ ์ฌ์ฉํฉ๋๋ค. Node.js๋ ์์ฒญ/์๋ต ๊ธฐ๋ฐ์ ๋ฉํฐ์ค๋ ๋ ์ํ ๋น์ ์ฅ ๋ชจ๋ธ์ ๋ฐ๋ฅด์ง ์๊ธฐ ๋๋ฌธ์, ๊ฐ ์์ฒญ์ด ๋ณ๋์ ์ค๋ ๋์์ ์ฒ๋ฆฌ๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์ฑ๊ธํด ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ ํ ์์ ํฉ๋๋ค.
ํ์ง๋ง GraphQL ์ ํ๋ฆฌ์ผ์ด์ ์์์ ์์ฒญ ๊ธฐ๋ฐ ์บ์ฑ, ์์ฒญ ์ถ์ , ๋ฉํฐ ํ๋์์ ๊ฐ์ด ์์ฒญ ๊ธฐ๋ฐ ์๋ช ์ฃผ๊ธฐ๊ฐ ํ์ํ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ์ด๋ด ๋๋ ์ฃผ์ ์ค์ฝํ๋ฅผ ํตํด ์ํ๋ ํ๋ก๋ฐ์ด๋ ์๋ช ์ฃผ๊ธฐ ๋์์ ๊ตฌํํ ์ ์์ต๋๋ค.
ํ๋ก๋ฐ์ด๋ ์ค์ฝํ
ํ๋ก๋ฐ์ด๋๋ ๋ค์ ์ค ํ๋์ ์ค์ฝํ๋ฅผ ๊ฐ์ง ์ ์์ต๋๋ค.
DEFAULT | ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ํ๋ก๋ฐ์ด๋์ ๋จ์ผ ์ธ์คํด์ค๊ฐ ๊ณต์ ๋ฉ๋๋ค. ์ธ์คํด์ค์ ์๋ช ์ฃผ๊ธฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ช ์ฃผ๊ธฐ์ ์ง์ ์ฐ๊ฒฐ๋์ด ์์ผ๋ฉฐ, ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ถํธ์คํธ๋ฉ๋๋ฉด ๋ชจ๋ ์ฑ๊ธํด ํ๋ก๋ฐ์ด๋๊ฐ ์ธ์คํด์คํ๋ฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ์ฑ๊ธํด ์ค์ฝํ๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. |
REQUEST | ๋ค์ด์ค๋ ๊ฐ ์์ฒญ๋ง๋ค ํ๋ก๋ฐ์ด๋์ ์๋ก์ด ์ธ์คํด์ค๊ฐ ์์ฑ๋ฑ๋๋ค. ํด๋น ์์ฒญ์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ฉด ์ธ์คํด์ค๋ ๊ฐ๋น์ง ์ปฌ๋ ์ ๋ฉ๋๋ค. |
TRANSIENT | ํธ๋์ง์ธํธ ํ๋ก๋ฐ์ด๋๋ ์๋น์ ๊ฐ์ ๊ณต์ ๋์ง ์์ต๋๋ค. ํธ๋์ง์ธํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฃผ์ ํ๋ ๊ฐ ์๋น์๋ ์๋ก์ด ์ ์ฉ ์ธ์คํด์ค๋ฅผ ๋ฐ๊ฒ ๋ฉ๋๋ค. |
ํํธ
๋๋ถ๋ถ์ ๊ฒฝ์ฐ์๋ ์ฑ๊ธํด ์ค์ฝํ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค. ์๋น์์ ์์ฒญ ๊ฐ์ ํ๋ก๋ฐ์ด๋๋ฅผ ๊ณต์ ํ๋ฉด ์ธ์คํด์ค๋ฅผ ์บ์ ํ ์ ์๊ณ , ์ด๊ธฐํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ํ ๋ฒ๋ง ์ํ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ฌ์ฉ
@Injectable() ๋ฐ์ฝ๋ ์ดํฐ์ ์ต์ ๊ฐ์ฒด์ scope ์์ฑ์ ์ ๋ฌํ์ฌ ์ฃผ์ ์ค์ฝํ๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค:
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {}
๋ง์ฐฌ๊ฐ์ง๋ก, ์ปค์คํ ํ๋ก๋ฐ์ด๋์ ๊ฒฝ์ฐ ํ๋ก๋ฐ์ด๋ ๋ฑ๋ก ์ ๋กฑํธ๋ (long-hand, ๋ช ์์ ์ธ ์ ์ฒด ๊ฐ์ฒด ํํ์ ๋ฑ๋ก ๋ฐฉ์) ํ์์์ scope ์์ฑ์ ์ค์ ํ ์ ์์ต๋๋ค:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
ํํธ
Scope ์ด๊ฑฐํ์ @nestjs/common์์ ์ํฌํธ ํ ์ ์์ต๋๋ค.
์ฑ๊ธํด ์ค์ฝํ๋ ๊ธฐ๋ณธ๊ฐ์ด๋ฏ๋ก ๋ช ์์ ์ผ๋ก ์ ์ธํ ํ์๋ ์์ต๋๋ค. ๊ทธ๋๋ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฑ๊ธํด ์ค์ฝํ๋ก ์ ์ธํ๊ณ ์ ํ๋ค๋ฉด, scope ์์ฑ์ Scope.DEFAULT ๊ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋ฆผ
WebSocket ๊ฒ์ดํธ์จ์ด๋ ๋ฐ๋์ ์ฑ๊ธํด์ผ๋ก ๋์ํด์ผ ํ๋ฏ๋ก ์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฌ์ฉํด์๋ ์ ๋ฉ๋๋ค. ๊ฐ ๊ฒ์ดํธ์จ์ด๋ ์ค์ ์์ผ์ ์บก์ํํ๊ณ ์์ผ๋ฉฐ ์ฌ๋ฌ ๋ฒ ์ธ์คํด์คํ๋ ์ ์์ต๋๋ค. ์ด ์ ํ ์ฌํญ์ Passport ์ ๋ต์ด๋ Cron ์ปจํธ๋กค๋ฌ์ ๊ฐ์ ์ผ๋ถ ๋ค๋ฅธ ํ๋ก๋ฐ์ด๋๋ ๋์ผํ๊ฒ ์ ์ฉ๋ฉ๋๋ค.
์ปจํธ๋กค๋ฌ ์ค์ฝํ
์ปจํธ๋กค๋ฌ๋ ์ค์ฝํ๋ฅผ ๊ฐ์ง ์ ์์ผ๋ฉฐ, ์ด๋ ํด๋น ์ปจํธ๋กค๋ฌ์ ์ ์ธ๋ ๋ชจ๋ ์์ฒญ ๋ฉ์๋ ํธ๋ค๋ฌ์ ์ ์ฉ๋ฉ๋๋ค. ํ๋ก๋ฐ์ด๋ ์ค์ฝํ์ ๋ง์ฐฌ๊ฐ์ง๋ก, ์ปจํธ๋กค๋ฌ์ ์ค์ฝํ๋ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ ์ํฉ๋๋ค. ์์ฒญ ์ค์ฝํ (Request-scoped) ์ปจํธ๋กค๋ฌ์ ๊ฒฝ์ฐ, ๋ค์ด์ค๋ ๊ฐ ์์ฒญ๋ง๋ค ์๋ก์ด ์ธ์คํด์ค๊ฐ ์์ฑ๋๊ณ ์์ฒญ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ฉด ๊ฐ๋น์ง ์ปฌ๋ ์ ๋ฉ๋๋ค.
์ปจํธ๋กค๋ฌ ์ค์ฝํ๋ ControllerOptions ๊ฐ์ฒด์ scope ์์ฑ์ผ๋ก ์ ์ธ๋ ์ ์์ต๋๋ค:
@Controller({
path: 'cats',
scope: Scope.REQUEST,
})
export class CatsController {}
์ค์ฝํ ๊ณ์ธต ๊ตฌ์กฐ (Scope hierarchy)
REQUEST ์ค์ฝํ๋ ์ฃผ์ ์ฒด์ธ์ ๋ฐ๋ผ ์์๋ก ์ ํ๋ฉ๋๋ค. ์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋์ ์์กดํ๋ ์ปจํธ๋กค๋ฌ๋ ์๋์ผ๋ก ์์ฒญ ์ค์ฝํ๊ฐ ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ์์กด์ฑ ๊ทธ๋ํ๊ฐ ์๋ค๊ณ ๊ฐ์ ํด ๋ด ์๋ค: CatsController <- CatsService <- CatsRepository. ์ฌ๊ธฐ์ CatsService๊ฐ ์์ฒญ ์ค์ฝํ์ด๊ณ ๋๋จธ์ง๋ ๊ธฐ๋ณธ ์ฑ๊ธํด์ด๋ผ๋ฉด, CatsController๋ CatsService์ ์์กดํ๊ณ ์์ผ๋ฏ๋ก ์์ฒญ ์ค์ฝํ๋ก ์ ํ๋ฉ๋๋ค. ๋ฐ๋ฉด, CatsRepository๋ ์์ฒญ ์ค์ฝํ ๊ฐ์ฒด์ ์์กดํ์ง ์๊ธฐ ๋๋ฌธ์ ๊ณ์ํด์ ์ฑ๊ธํด ์ค์ฝํ๋ฅผ ์ ์งํฉ๋๋ค.
๋ฐ๋ฉด, TRANSIENT ์ค์ฝํ๋ ์ด์ ๊ฐ์ ์ ํ ํจํด์ ๋ฐ๋ฅด์ง ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ฑ๊ธํด ์ค์ฝํ์ DogsService๊ฐ ํธ๋์ง์ธํธ LoggerService๋ฅผ ์ฃผ์ ๋ฐ๋๋ค๋ฉด, LoggerService๋ ๋งค๋ฒ ์๋ก์ด ์ธ์คํด์ค๋ก ์ฃผ์ ๋์ง๋ง DogsService ์์ฒด๋ ์ฌ์ ํ ์ฑ๊ธํด ์ค์ฝํ๋ฅผ ์ ์งํฉ๋๋ค. ์ฆ, ์ด๋์๋ DogsService๋ฅผ ์ฃผ์ ํ๋ฉด ๋์ผํ ์ธ์คํด์ค๋ฅผ ๋ฐ์ต๋๋ค. ๋ง์ฝ DogsService๋ ๋งค๋ฒ ์ ์ธ์คํด์ค๋ก ์์ฑ๋๊ธฐ๋ฅผ ์ํ๋ค๋ฉด, DogsService ์ญ์ ๋ช ์์ ์ผ๋ก TRANSIENT ์ค์ฝํ๋ก ์ ์ธํด์ผ ํฉ๋๋ค.
์์ฒญ ํ๋ก๋ฐ์ด๋
HTTP ์๋ฒ ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ (@nestjs/platform-express ๋๋ @nestjs/platform-fastify ๋ฑ)์์ ์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฌ์ฉํ ๋, ์๋ณธ ์์ฒญ ๊ฐ์ฒด์ ์ ๊ทผํ๊ณ ์ถ์ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ REQUEST ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํ์ฌ ์ฐธ์กฐํ ์ ์์ต๋๋ค.
REQUEST ํ๋ก๋ฐ์ด๋๋ ๋ณธ์ง์ ์ผ๋ก ์์ฒญ ์ค์ฝํ์ด๊ธฐ ๋๋ฌธ์, ์ฌ์ฉํ ๋ ๋ช ์์ ์ผ๋ก ์ค์ฝํ๋ฅผ ์ง์ ํ ํ์๊ฐ ์์ต๋๋ค. ๋ํ ์ค์ฝํ๋ฅผ ์ง์ ํ๋ ค ํด๋ ๋ฌด์๋ฉ๋๋ค. ์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋์ ์์กดํ๋ ๋ชจ๋ ํ๋ก๋ฐ์ด๋๋ ์๋์ผ๋ก ์์ฒญ ์ค์ฝํ๊ฐ ๋๋ฉฐ, ์ด ๋์์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(REQUEST) private request: Request) {}
}
๊ธฐ์ ์ ์๋ ํ๋ซํผ/ํ๋กํ ์ฝ์ ์ฐจ์ด๋ก ์ธํด, ๋ง์ดํฌ๋ก์๋น์ค๋ GraphQL ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์์ฒญ ๊ฐ์ฒด์ ์ ๊ทผํ๋ ๋ฐฉ์์ด ์กฐ๊ธ ๋ค๋ฆ ๋๋ค. GraphQL ์ ํ๋ฆฌ์ผ์ด์ ์์๋ REQUEST ๋์ CONTEXT๋ฅผ ์ฃผ์ ํฉ๋๋ค:
import { Injectable, Scope, Inject } from '@nestjs/common';
import { CONTEXT } from '@nestjs/graphql';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(CONTEXT) private context) {}
}
๊ทธ๋ฐ ๋ค์ GraphQLModule์์ context ๊ฐ์ ์ค์ ํ ๋, request๋ฅผ ๊ทธ ์์ฑ์ผ๋ก ํฌํจํ๋๋ก ๊ตฌ์ฑํด์ผ ํฉ๋๋ค.
Inquirer ํ๋ก๋ฐ์ด๋
๋ก๊น ์ด๋ ๋ฉํธ๋ฆญ ํ๋ก๋ฐ์ด๋์ฒ๋ผ, ํน์ ํ๋ก๋ฐ์ด๋๊ฐ ์ด๋ค ํด๋์ค์ ์ํด ์์ฑ๋์๋์ง ์๊ณ ์ถ์ ๋๋ INQUIRER ํ ํฐ์ ์ฃผ์ ํ์ฌ ํด๋น ํด๋์ค๋ฅผ ์ฐธ์กฐํ ์ ์์ต๋๋ค.
import { Inject, Injectable, Scope } from '@nestjs/common';
import { INQUIRER } from '@nestjs/core';
@Injectable({ scope: Scope.TRANSIENT })
export class HelloService {
constructor(@Inject(INQUIRER) private parentClass: object) {}
sayHello(message: string) {
console.log(`${this.parentClass?.constructor?.name}: ${message}`);
}
}
๊ทธ๋ฆฌ๊ณ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
import { Injectable } from '@nestjs/common';
import { HelloService } from './hello.service';
@Injectable()
export class AppService {
constructor(private helloService: HelloService) {}
getRoot(): string {
this.helloService.sayHello('My name is getRoot');
return 'Hello world!';
}
}
์ ์์ ์์ AppService#getRoot๊ฐ ํธ์ถ๋๋ฉด, "AppService: My name is getRoot"๊ฐ ์ฝ์์ ์ถ๋ ฅ๋ฉ๋๋ค.
์ฑ๋ฅ
์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. Nest๋ ๊ฐ๋ฅํ ๋ง์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์บ์ ํ๋ ค๊ณ ํ์ง๋ง, ์ฌ์ ํ ๋งค ์์ฒญ๋ง๋ค ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค. ๋ฐ๋ผ์ ํ๊ท ์๋ต ์๊ฐ๊ณผ ์ ์ฒด ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ๊ฐ ๋๋ ค์ง ์ ์์ต๋๋ค. ๋ฐ๋์ ์์ฒญ ์ค์ฝํ๊ฐ ํ์ํ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด, ๊ธฐ๋ณธ ์ฑ๊ธํด ์ค์ฝํ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฐ๋ ฅํ ๊ถ์ฅ๋ฉ๋๋ค.
ํํธ
๊ฒ๋ณด๊ธฐ์๋ ๊ฝค ์ํ์ ์ผ๋ก ๋๊ปด์ง ์ ์์ง๋ง, ์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋๋ฅผ ์ ์ ํ ํ์ฉํ ์ ์ค๊ณ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ผ๋ฉด ์ง์ฐ ์๊ฐ (latency) ๊ธฐ์ค์ผ๋ก ์ฑ๋ฅ ์ ํ๋ ์ฝ 5% ์ด๋ด์ ๋ถ๊ณผํฉ๋๋ค.
๋ด๊ตฌ์ฑ ํ๋ก๋ฐ์ด๋
์์ ์ธ๊ธํ ์์ฒญ ํ๋ก๋ฐ์ด๋๋ ์ง์ฐ ์๊ฐ์ด ์ฆ๊ฐํ ์ ์์ต๋๋ค. ์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋๊ฐ ์ปจํธ๋กค๋ฌ ์ธ์คํด์ค์ ์ฃผ์ ๋๊ฑฐ๋, ๋ ๊น์ด ์ฃผ์ ๋์ด ์๋ค๋ฉด ํด๋น ์ปจํธ๋กค๋ฌ ์์ฒด๋ ์์ฒญ ์ค์ฝํ๊ฐ ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ ๊ฐ ์์ฒญ๋ง๋ค ์ปจํธ๋กค๋ฌ (๋ฐ ๊ทธ ์์ฒญ ์ค์ฝํ ํ๋ก๋ฐ์ด๋)๊ฐ ์๋ก ์์ฑ๋๊ณ , ์ดํ ๊ฐ๋น์ง ์ปฌ๋ ์ ๋๋ค๋ ์๋ฏธ์ ๋๋ค. ์๋ฅผ ๋ค์ด 3๋ง ๊ฐ์ ์์ฒญ์ด ๋์์ ๋ฐ์ํ๋ฉด, 3๋ง ๊ฐ์ ์ผ์์ ์ธ ์ปจํธ๋กค๋ฌ ์ธ์คํด์ค๊ฐ ์์ฑ๋ฉ๋๋ค.
๋๋ถ๋ถ์ ํ๋ก๋ฐ์ด๋๊ฐ ๊ณตํต์ ์ผ๋ก ์์กดํ๋ ํ๋ก๋ฐ์ด๋ (์: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ , ๋ก๊ฑฐ ์๋น์ค ๋ฑ)๊ฐ ์์ฒญ ์ค์ฝํ์ผ ๊ฒฝ์ฐ, ์ด๋ก ์ธํด ํด๋น ํ๋ก๋ฐ์ด๋๋ฅผ ์์กดํ๋ ๋ชจ๋ ํ๋ก๋ฐ์ด๋๊ฐ ์๋์ผ๋ก ์์ฒญ ์ค์ฝํ๊ฐ ๋ฉ๋๋ค. ์ด๋ ํนํ ๋ฉํฐ ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฌธ์ ๊ฐ ๋ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์์ฒญ ๊ฐ์ฒด์์ ํค๋๋ ํ ํฐ์ ๊ฐ์ ธ์ ํ ๋ํธ๋ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ด๋ ์คํค๋ง๋ฅผ ์ ํํ๋ ์ค์ ์ง์คํ ์์ฒญ ์ค์ฝํ "๋ฐ์ดํฐ ์์ค" ํ๋ก๋ฐ์ด๋๊ฐ ์์ ๊ฒฝ์ฐ ๋์ฑ ๊ทธ๋ ์ต๋๋ค.
์๋ฅผ ๋ค์ด, 10๋ช ์ ์๋ก ๋ค๋ฅธ ๊ณ ๊ฐ์ด ๋ฒ๊ฐ์ ์ฌ์ฉํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์๋ค๊ณ ๊ฐ์ ํด ๋ด ์๋ค. ๊ฐ ๊ณ ๊ฐ์ ์ ์ฉ ๋ฐ์ดํฐ ์์ค๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ๊ณ ๊ฐ A๊ฐ ์ ๋๋ก ๊ณ ๊ฐ B์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ์ง ๋ชปํ๋๋ก ๋ณด์ฅํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ๋ฌ์ฑํ๋ ํ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์์ฒญ ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ ๊ณ ๊ฐ์ ์๋ณํ๊ณ ํด๋น ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐํ๋ ์์ฒญ ์ค์ฝํ์ "๋ฐ์ดํฐ ์์ค" ํ๋ก๋ฐ์ด๋๋ฅผ ์ ์ธํ๋ ๊ฒ์ ๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ๋ช ๋ถ ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฉํฐ ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์ ํํ ์ ์์ต๋๋ค.
ํ์ง๋ง ์ด ๋ฐฉ์์ ํฐ ๋จ์ ์, ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง์ ์ปดํฌ๋ํธ๊ฐ ์ด "๋ฐ์ดํฐ ์์ค" ํ๋ก๋ฐ์ด๋์ ์์กดํ๊ฒ ๋๋ฉด์, ์ด๋ค ๋ํ ์๋ฌต์ ์ผ๋ก "์์ฒญ ์ค์ฝํ"๊ฐ ๋๊ณ , ๊ฒฐ๊ตญ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ์ํฅ์ ์ฃผ๊ฒ ๋๋ค๋ ์ ์ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ๋ ๋์ ํด๊ฒฐ์ฑ ์ ์์๊น์? ๊ณ ๊ฐ์ด 10๋ช ๋ฟ์ด๋ผ๋ฉด, ์์ฒญ๋ง๋ค DI ํธ๋ฆฌ๋ฅผ ๋งค๋ฒ ์ฌ์์ฑํ๋ ๋์ , ๊ณ ๊ฐ๋ณ๋ก 10๊ฐ์ ๊ฐ๋ณ DI ์๋ธ ํธ๋ฆฌ๋ฅผ ๋ง๋ค ์๋ ์์๊น์? ๋ง์ฝ ๊ฐ ์์ฒญ๋ง๋ค ๊ณ ์ ํ ์์ฑ (์: ์์ฒญ UUID)์ ์์กดํ์ง ์๊ณ , ๊ณตํต๋ ํน์ ์์ฑ์ผ๋ก ์์ฒญ์ ๋ถ๋ฅํ ์ ์๋ค๋ฉด, ๋งค ์์ฒญ๋ง๋ค DI ํธ๋ฆฌ๋ฅผ ๋ค์ ๋ง๋ค ์ด์ ๋ ์์ต๋๋ค.
๋ฐ๋ก ์ด๋ด ๋ ๋ด๊ตฌ์ฑ ํ๋ก๋ฐ์ด๋ (Durable Provider)๊ฐ ์ ์ฉํฉ๋๋ค.
๋ด๊ตฌ์ฑ ํ๋ก๋ฐ์ด๋๋ฅผ ์ ์ฉํ๊ธฐ ์ ์, ๋จผ์ Nest์ "๊ณตํต ์์ฒญ ์์ฑ์ด ๋ฌด์์ธ์ง"๋ฅผ ์๋ ค์ฃผ๋ ์ ๋ต์ ๋ฑ๋กํด์ผ ํ๋ฉฐ, ์์ฒญ์ ๊ทธ๋ฃนํํ๊ณ ํด๋น DI ์๋ธ ํธ๋ฆฌ์ ์ฐ๊ด์ํค๋ ๋ก์ง์ ์ ๊ณตํด์ผ ํฉ๋๋ค.
import {
HostComponentInfo,
ContextId,
ContextIdFactory,
ContextIdStrategy,
} from '@nestjs/core';
import { Request } from 'express';
const tenants = new Map<string, ContextId>();
export class AggregateByTenantContextIdStrategy implements ContextIdStrategy {
attach(contextId: ContextId, request: Request) {
const tenantId = request.headers['x-tenant-id'] as string;
let tenantSubTreeId: ContextId;
if (tenants.has(tenantId)) {
tenantSubTreeId = tenants.get(tenantId);
} else {
tenantSubTreeId = ContextIdFactory.create();
tenants.set(tenantId, tenantSubTreeId);
}
// ํธ๋ฆฌ๊ฐ Durableํ์ง ์๋ค๋ฉด ๊ธฐ๋ณธ contextId ๋ฐํ
return (info: HostComponentInfo) =>
info.isTreeDurable ? tenantSubTreeId : contextId;
}
}
ํํธ
์์ฒญ ์ค์ฝํ์ ๋ง์ฐฌ๊ฐ์ง๋ก, ๋ด๊ตฌ์ฑ (durability)์ ์ฃผ์ ์ฒด์ธ์ ๋ฐ๋ผ ์ ํ๋ฉ๋๋ค. ์ฆ, A๊ฐ B๋ฅผ ์์กดํ๊ณ B๊ฐ durABLE๋ก ์ง์ ๋์ด ์๋ค๋ฉด, A๋ ์๋ฌต์ ์ผ๋ก durable์ด ๋ฉ๋๋ค (๋จ, A ํ๋ก๋ฐ์ด๋์ ๋ํด ๋ช ์์ ์ผ๋ก durable์ false๋ก ์ค์ ํ์ง ์์ ๊ฒฝ์ฐ์ ํํด).
๊ฒฝ๊ณ
์ด ์ ๋ต์ ๋ง์ ์์ ํ๋ํธ์ ํจ๊ป ๋์ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์๋ ์ด์์ ์ด์ง ์๋ค๋ ์ ์ ์ ์ํ์ธ์.
attach ๋ฉ์๋์์ ๋ฐํ๋ ๊ฐ์ ํน์ ํธ์คํธ์ ๋ํด ์ด๋ค ์ปจํ ์คํธ ์๋ณ์๋ฅผ ์ฌ์ฉํ ์ง Nest์ ์ง์ํฉ๋๋ค. ์์ ์์์์๋, ํธ์คํธ ์ปดํฌ๋ํธ (์: ์์ฒญ ์ค์ฝํ ์ปจํธ๋กค๋ฌ)๊ฐ durable๋ก ํ์๋ ๊ฒฝ์ฐ, ์๋ ์๋ ์์ฑ๋ contextId ๋์ tenantSubTreeId๋ฅผ ์ฌ์ฉํ๋๋ก ์ง์ ํ์ต๋๋ค (ํ๋ก๋ฐ์ด๋๋ฅผ ์ด๋ป๊ฒ durable๋ก ์ง์ ํ๋์ง๋ ์๋์์ ์ค๋ช ๋จ). ๋ํ ์ ์์ ์์๋ ํ์ด๋ก๋ (= ์๋ธ ํธ๋ฆฌ์ "๋ฃจํธ"๋ฅผ ๋ํ๋ด๋ REQUEST/CONTEXT ํ๋ก๋ฐ์ด๋)๊ฐ ๋ฑ๋ก๋์ง ์์ต๋๋ค.
durable ํธ๋ฆฌ์ ๋ํด ํ์ด๋ก๋๋ฅผ ๋ฑ๋กํ๊ณ ์ถ๋ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ ๊ตฌ๋ฌธ์ ์ฌ์ฉํ์ธ์:
// `AggregateByTenantContextIdStrategy#attach` ๋ฉ์๋์ ๋ฐํ ๊ฐ:
return {
resolve: (info: HostComponentInfo) =>
info.isTreeDurable ? tenantSubTreeId : contextId,
payload: { tenantId },
};
์ด์ @Inject(REQUEST) ๋๋ @Inject(CONTEXT)๋ฅผ ์ฌ์ฉํด REQUEST ํ๋ก๋ฐ์ด๋ (๋๋ GraphQL ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ CONTEXT)๋ฅผ ์ฃผ์ ํ ๋, ํ์ด๋ก๋ ๊ฐ์ฒด๊ฐ ์ฃผ์ ๋ฉ๋๋ค (์ด ๊ฒฝ์ฐ ๋จ์ผ ์์ฑ์ธ tenantId๋ฅผ ํฌํจ).
์ด ์ ๋ต์ ์ ์ญ์ ์ผ๋ก ์ ์ฉ๋๋ฏ๋ก, ์ ํ๋ฆฌ์ผ์ด์ ์ด๋์๋ ๋ฑ๋กํ ์ ์์ผ๋ฉฐ, ์๋ฅผ ๋ค์ด main.ts ํ์ผ์ ๋ฑ๋กํ ์ ์์ต๋๋ค:
ContextIdFactory.apply(new AggregateByTenantContextIdStrategy());
ํํธ
ContextIdFactory ํด๋์ค๋ @nestjs/core ํจํค์ง์์ ์ํฌํธ ๋ฉ๋๋ค.
์์ฒญ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ฌํ๊ธฐ ์ ์ ํด๋น ์ ๋ต์ด ๋ฑ๋ก๋๊ธฐ๋ง ํ๋ฉด, ๋ชจ๋ ๊ฒ์ด ์๋ํ ๋๋ก ์๋ํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก, ์ผ๋ฐ ํ๋ก๋ฐ์ด๋๋ฅผ durable ํ๋ก๋ฐ์ด๋๋ก ์ ํํ๋ ค๋ฉด, durable ํ๋๊ทธ๋ฅผ true๋ก ์ค์ ํ๊ณ ์ค์ฝํ๋ฅผ Scope.REQUEST๋ก ์ง์ ํ๋ฉด ๋ฉ๋๋ค (์ด๋ฏธ ์์ฒญ ์ค์ฝํ ์ฒด์ธ ๋ด์ ์๋ค๋ฉด ์ค์ฝํ ์ค์ ์ ์๋ต ๊ฐ๋ฅ).
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST, durable: true })
export class CatsService {}
๋ง์ฐฌ๊ฐ์ง๋ก, ์ปค์คํ ํ๋ก๋ฐ์ด๋์ ๊ฒฝ์ฐ ํ๋ก๋ฐ์ด๋ ๋ฑ๋ก ์ ๋กฑํธ๋ ํ์์์ durable ์์ฑ์ ์ค์ ํ ์ ์์ต๋๋ค:
{
provide: 'foobar',
useFactory: () => { ... },
scope: Scope.REQUEST,
durable: true,
}
Reference
'๐ ๊ณต์ ๋ฌธ์ ๋ฒ์ญ > Nest.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Nest.js] Fundamentals | Lazy-loading modules, Execution context (3) | 2025.07.29 |
---|---|
[Nest.js] Fundamentals - Circular dependency, Module reference (0) | 2025.07.19 |
[Nest.js] Fundamentals - Custom providers, Asynchronous providers (0) | 2025.07.15 |
[Nest.js] Overview - Interceptors, Custom decorators (0) | 2025.07.13 |
[Nest.js] Overview - Pipes, Guards (0) | 2025.07.08 |