๐Ÿ“‘ ๊ณต์‹ ๋ฌธ์„œ ๋ฒˆ์—ญ/Nest.js

[Nest.js] Fundamentals - Dynamic modules, Injection scopes

dev_writer 2025. 7. 16. 01:21

๋™์  ๋ชจ๋“ˆ (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 ๋‚ด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค:

  1. UsersModule์„ ์ธ์Šคํ„ด์Šคํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ UsersModule์ด ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“ˆ๋“ค๋„ ์ „์ด์ ์œผ๋กœ import ๋˜๋ฉฐ, ๋ชจ๋“  ์˜์กด์„ฑ๋„ ์ „์ด์ ์œผ๋กœ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค (์ž์„ธํ•œ ๋‚ด์šฉ์€ Custom providers ์ฐธ๊ณ ).
  2. AuthModule์„ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๊ณ , UsersModule์—์„œ export ํ•œ ํ”„๋กœ๋ฐ”์ด๋”๋“ค์„ AuthModule ๋‚ด์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ญ๋‹ˆ๋‹ค (๋งˆ์น˜ AuthModule ๋‚ด์—์„œ ์ง์ ‘ ์„ ์–ธ๋œ ๊ฒƒ์ฒ˜๋Ÿผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค).
  3. 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 ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์•ˆ์—์„œ ์†์„ฑ์„ ๊ณ ์ •ํ•ด์„œ ์„ค์ •ํ•˜๋Š” ๋Œ€์‹ , ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ์„ค์ •๊ฐ’์„ ์ง€์ •ํ•œ๋‹ค๋Š” ์ ๋งŒ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

 

์ „์ฒด ๊ฐœ๋…์„ ์™„์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡ ๊ฐ€์ง€ ์„ธ๋ถ€์‚ฌํ•ญ์„ ๋” ์•Œ์•„๋‘˜ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ์ด์ œ @Module() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ imports ์†์„ฑ์—๋Š” ๋ชจ๋“ˆ ํด๋ž˜์Šค ์ด๋ฆ„ (์˜ˆ: imports: [UsersModule]) ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋™์  ๋ชจ๋“ˆ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜ (์˜ˆ: imports: [ConfigModule.register(...)])๋„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ช…ํ™•ํžˆ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ๋™์  ๋ชจ๋“ˆ๋„ ์ž์ฒด์ ์œผ๋กœ ๋‹ค๋ฅธ ๋ชจ๋“ˆ๋“ค์„ 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