๐Ÿ’ก 03. ์„œ๋น„์Šค์™€ ํ”„๋กœ๋ฐ”์ด๋” โ€” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ์ œ์–ด์˜ ์—ญ์ „(IoC)

2026๋…„ 2์›” 26์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ํ•ต์‹ฌ์ธ ์„œ๋น„์Šค์™€, ์ด๋ฅผ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ด ์ฃผ๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ์‹œ์Šคํ…œ์ธ 'ํ”„๋กœ๋ฐ”์ด๋”(Provider)์™€ ์˜์กด์„ฑ ์ฃผ์ž…(DI)'์„ ๋งˆ์Šคํ„ฐํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 20๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 10๋ถ„

๐Ÿงณ ์ „์ œ ์ง€์‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • 02์žฅ ์ปจํŠธ๋กค๋Ÿฌ์˜ ์—ญํ• (์š”์ฒญ ์ˆ˜์‹  ๋ฐ ์‘๋‹ต ๋ฐ˜ํ™˜)์„ ์™„์ „ํžˆ ์ดํ•ดํ–ˆ๋‹ค.
  • TypeScript์˜ constructor(private readonly ...) ๋ฌธ๋ฒ•์ด ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ์•ˆ๋‹ค.
  • (์„ ํƒ) ๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ '์ธํ„ฐํŽ˜์ด์Šค(Interface)' ๊ฐœ๋…์„ ๋“ค์–ด๋ณธ ์ ์ด ์žˆ๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
์„œ๋น„์Šค์˜ ํ•„์š”์„ฑ โ†’ DI(์˜์กด์„ฑ ์ฃผ์ž…)์˜ ๊ฐ•๋ ฅํ•จ โ†’ ์‹ค์ „ DB ์—ฐ๊ฒฐ ์ฝ”๋“œ ์ž‘์„ฑ โ†’ ์ปค์Šคํ…€ ํ”„๋กœ๋ฐ”์ด๋” โ†’ ์ˆœํ™˜ ์ฐธ์กฐ ์—๋Ÿฌ ํ•ด๊ฒฐ๋ฒ•

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜์—ฌ ์„œ๋น„์Šค๋กœ ์˜ฎ๊ธธ ์ˆ˜ ์žˆ๋‹ค.
  • "์˜์กด์„ฑ ์ฃผ์ž…์„ ์™œ ์“ฐ๋‚˜์š”?" ๋ผ๋Š” ์งˆ๋ฌธ์— ๋‹น๋‹นํ•˜๊ฒŒ ๋Œ€๋‹ตํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™œ์šฉํ•ด ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์šด(Mocking ๊ฐ€๋Šฅํ•œ) ์ฝ”๋“œ๋ฅผ ์งค ์ˆ˜ ์žˆ๋‹ค.

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์ดˆ๋ณด ๊ฐœ๋ฐœ์ž๋“ค์€ ํ”ํžˆ "๋ผ์šฐํ„ฐ ํŒŒ๋†“๊ณ  ๊ทธ ์•ˆ์—์„œ DB๋„ ๋ถ€๋ฅด๊ณ , ๊ณ„์‚ฐ๋„ ํ•˜๊ณ , ์—๋Ÿฌ๋„ ๋ฑ‰์œผ๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹ˆ์•ผ?" ๋ผ๊ณ  ์ฐฉ๊ฐํ•ด.
ํ•˜์ง€๋งŒ ๋กœ์ง์ด ๋ณต์žกํ•ด์งˆ์ˆ˜๋ก ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋šฑ๋šฑํ•ด์ง€๊ณ , ๋‚˜์ค‘์— "๊ฒฐ์ œ ๋กœ์ง"๋งŒ ๋˜‘ ๋–ผ์–ด์„œ "์Šค์ผ€์ค„๋Ÿฌ"์—์„œ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์–ด๋„ ๋–ผ์–ด๋‚ผ ์ˆ˜๊ฐ€ ์—†์–ด.
์„œ๋น„์Šค๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  DI๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋ ˆ๊ณ  ๋ธ”๋ก์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž์œ ์ž์žฌ๋กœ ๋—๋‹ค ๋ถ™์˜€๋‹ค(์žฌ์‚ฌ์šฉ) ํ•  ์ˆ˜ ์žˆ๊ณ , ๋ฌด์—‡๋ณด๋‹ค ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์ด 100๋ฐฐ ์‰ฌ์›Œ์ ธ.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

์ปจํŠธ๋กค๋Ÿฌ์— ๋กœ์ง์„ ๋‹ค ์งœ๋Š” ๊ฒƒ (โŒ)
= ์›จ์ดํ„ฐ๊ฐ€ ์†๋‹˜ ์ฃผ๋ฌธ์„ ๋ฐ›์ž๋งˆ์ž, ๋ณธ์ธ์ด ์ฃผ๋ฐฉ์œผ๋กœ ๋‹ฌ๋ ค๊ฐ€์„œ ํ”„๋ผ์ดํŒฌ์„ ์žก๊ณ  ๊ณ ๊ธฐ๋ฅผ ๊ตฝ๋Š” ๊ฑฐ์•ผ. ๊ทธ๋Ÿฌ๋‹ค ์†๋‹˜ ์˜ค๋ฉด ๋˜ ๋›ฐ์–ด๋‚˜๊ฐ€๊ณ ! ์‹๋‹น์ด ์—‰๋ง์ง„์ฐฝ์ด ๋˜๊ฒ ์ง€?
์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•˜๋Š” ๊ฒƒ (โœ…)
= ์›จ์ดํ„ฐ๋Š” ์ฃผ๋ฌธ๋งŒ ๋ฐ›๊ณ  ์ฃผ๋ฐฉ์— ํ† ์Šคํ•ด. ์ฃผ๋ฐฉ์—๋Š” '์ดํƒˆ๋ฆฌ์•ˆ ์…ฐํ”„', 'ํ•œ์‹ ์…ฐํ”„(์„œ๋น„์Šค๋“ค)'๊ฐ€ ๊ฐ์ž์˜ ๋ฉ”๋‰ดํ‘œ๋ฅผ ๋“ค๊ณ  ๋Œ€๊ธฐํ•˜๊ณ  ์žˆ์–ด.
์›จ์ดํ„ฐ๊ฐ€ ์ถœ๊ทผํ•  ๋•Œ "์ € ์˜ค๋Š˜ ์ดํƒˆ๋ฆฌ์•ˆ ์…ฐํ”„๋ž‘ ์ผํ• ๊ฒŒ์š”!" ๋ผ๊ณ  ํ•˜๋ฉด, ์‹๋‹น ์ง€๋ฐฐ์ธ(NestJS DI ์ปจํ…Œ์ด๋„ˆ) ์ด ์•Œ์•„์„œ ๊ทธ๋‚  ์ปจ๋””์…˜์ด ์ œ์ผ ์ข‹์€ ์ดํƒˆ๋ฆฌ์•ˆ ์…ฐํ”„๋ฅผ ์›จ์ดํ„ฐ ํŒŒํŠธ๋„ˆ๋กœ ๋”ฑ! ๋ฐฐ์ •ํ•ด ์ฃผ๋Š” ๊ฑฐ์•ผ.


๐Ÿงฉ ์„œ๋น„์Šค (Service) โ€” ์š”๋ฆฌ๋ฅผ ์ „๋‹ดํ•˜๋Š” ์…ฐํ”„ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • @Injectable()์˜ ์˜๋ฏธ๋ฅผ ์•Œ๊ณ , ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ์œ„์น˜๋ฅผ ํ™•์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์„œ๋น„์Šค๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์„ ๋‹ด๋‹นํ•˜๋Š” ํ‰๋ฒ”ํ•œ TypeScript ํด๋ž˜์Šค์•ผ. ๋‹จ ํ•˜๋‚˜์˜ ์ฐจ์ด์ ์€ ๋จธ๋ฆฌ ์œ„์— @Injectable() ์ด๋ผ๋Š” ์™•๊ด€์„ ์“ฐ๊ณ  ์žˆ๋‹ค๋Š” ์ ์ด์ง€.

import { Injectable } from '@nestjs/common';
 
// "์ด ํด๋ž˜์Šค๋Š” NestJS ๋ฉ”๋‰ด์–ผ์— ๋“ฑ๋ก๋œ ์ •์‹ ์…ฐํ”„์ž…๋‹ˆ๋‹ค!"
@Injectable()
export class UsersService {
  
  // ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
  calculateTax(amount: number) {
    return amount * 0.1;
  }
}

๐Ÿ“– ์šฉ์–ด: Provider (ํ”„๋กœ๋ฐ”์ด๋”)
NestJS์—์„œ DI(์˜์กด์„ฑ ์ฃผ์ž…) ์‹œ์Šคํ…œ์ด ๊ด€๋ฆฌํ•˜๋Š” ๋ชจ๋“  ํด๋ž˜์Šค๋ฅผ ํ†ตํ‹€์–ด 'ํ”„๋กœ๋ฐ”์ด๋”'๋ผ๊ณ  ๋ถˆ๋Ÿฌ. ์„œ๋น„์Šค๋Š” ํ”„๋กœ๋ฐ”์ด๋”์˜ ๊ฐ€์žฅ ํ”ํ•œ 90%์งœ๋ฆฌ ํ˜•ํƒœ์ผ ๋ฟ์ด์•ผ. ๋‚˜์ค‘์—๋Š” ํŒฉํ† ๋ฆฌ๋„ ํ”„๋กœ๋ฐ”์ด๋”๊ณ , ์„ค์ •๊ฐ’ ๋ฌธ์ž์—ด๋„ ํ”„๋กœ๋ฐ”์ด๋”๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.


๐Ÿงฉ ์˜์กด์„ฑ ์ฃผ์ž… (DI) โ€” ๋˜‘๋˜‘ํ•œ ์ธ์‚ฌ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๐ŸŸข

์ด ํŒŒํŠธ๊ฐ€ NestJS์˜ ์‹ฌ์žฅ์ด์•ผ.

โŒ ์˜์กด์„ฑ์„ ์ง์ ‘ ๋งŒ๋“ค๋ฉด ์ƒ๊ธฐ๋Š” ๋น„๊ทน:

class UsersController {
  // ์›จ์ดํ„ฐ๊ฐ€ ์ถœ๊ทผํ•  ๋•Œ๋งˆ๋‹ค ์ž๊ธฐ ๋ˆ์œผ๋กœ ์…ฐํ”„๋ฅผ ์ง์ ‘ ๊ณ ์šฉํ•จ
  private usersService = new UsersService(new DatabaseService());
}

์ด๋Ÿฌ๋ฉด DatabaseService๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ปจํŠธ๋กค๋Ÿฌ์˜ ์ฝ”๋“œ๊นŒ์ง€ ํ•จ๊ป˜ ๋œฏ์–ด๊ณ ์ณ์•ผ ํ•ด. ๊ฐ•ํ•œ ๊ฒฐํ•ฉ(Tight Coupling)์˜ ์ „ํ˜•์ด์ง€.

โœ… NestJS์˜ ๊ฐ•๋ ฅํ•œ DI ํ•ด๊ฒฐ์ฑ…:

@Controller('users')
export class UsersController {
  // "์ง€๋ฐฐ์ธ๋‹˜, ์ € UsersService ํ•  ์ค„ ์•„๋Š” ์‚ฌ๋žŒ ํ•œ ๋ช… ๋ฐฐ์ •ํ•ด์ฃผ์„ธ์š”"
  constructor(private readonly usersService: UsersService) {}
}

์šฐ๋ฆฌ๋Š” ๋‹จ์ง€ constructor์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…ํ‘œ์‹œ(: UsersService)๋งŒ ์ ์–ด์ฃผ๋ฉด ๋์ด์•ผ.
NestJS๊ฐ€ ์‹คํ–‰๋  ๋•Œ, ๋˜‘๋˜‘ํ•œ ์ง€๋ฐฐ์ธ(IoC Container)์ด ์ด ํƒ€์ž…์„ ๋ณด๊ณ  ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘” UsersService ์ธ์Šคํ„ด์Šค๋ฅผ ์•Œ์•„์„œ ์™ ๋„ฃ์–ด์ค˜.

์ด๊ฑธ ์ œ์–ด์˜ ์—ญ์ „(IoC, Inversion of Control) ์ด๋ผ๊ณ  ๋ถˆ๋Ÿฌ! ๊ฐœ๋ฐœ์ž(๋‚˜)๊ฐ€ new๋ฅผ ์•ˆ ํ•˜๊ณ  ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์•Œ์•„์„œ ํ•ด์ค€๋‹ค๋Š” ๋œป์ด์•ผ.

ํ”„๋กœ๋ฐ”์ด๋”์˜ ์ƒ๋ช…์ฃผ๊ธฐ (Scope)

๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€๋ฐฐ์ธ์€ ์…ฐํ”„๋ฅผ ๋”ฑ 1๋ช…๋งŒ ๊ณ ์šฉํ•ด์„œ, ๋ชจ๋“  ์›จ์ดํ„ฐ๊ฐ€ ๋Œ๋ ค์“ฐ๊ฒŒ ํ•ด (** ์‹ฑ๊ธ€ํ†ค, Singleton**). ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์•„๋ผ๊ธฐ ์œ„ํ•ด์„œ์ง€.
ํ•˜์ง€๋งŒ ํŠน๋ณ„ํ•œ ์˜ต์…˜์„ ์ค„ ์ˆ˜๋„ ์žˆ์–ด.

์Šค์ฝ”ํ”„์†์„ฑ์–ธ์ œ ์“ฐ๋‚˜?๋‹จ์ 
๊ธฐ๋ณธ(DEFAULT)์‹ฑ๊ธ€ํ†ค. ์•ฑ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ ์ƒ์„ฑ99%์˜ ์„œ๋น„์Šค, ๋ฌด์ƒํƒœ ๋กœ์ง์—†์Œ (๊ฐ€์žฅ ๋น ๋ฆ„)
์š”์ฒญ(REQUEST)ํด๋ผ์ด์–ธํŠธ์˜ 1์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ ์ƒ์„ฑ์œ ์ €๋ณ„ ํ† ํฐ์ด๋‚˜ ํ…Œ๋„ŒํŠธ ์‹๋ณ„์ด ํ•„์š”ํ•  ๋•Œ์„ฑ๋Šฅ ์ €ํ•˜ (๋งค๋ฒˆ ์ƒ์„ฑ/ํŒŒ๊ดด)
์ž„์‹œ(TRANSIENT)์ฃผ์ž…๋ฐ›์„ ๊ณณ์„ ๋งŒ๋‚  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ ์ƒ์„ฑ๊ฐ๊ฐ ์™„์ „ํžˆ ๊ฒฉ๋ฆฌ๋œ ์ƒํƒœ(State)๋ฅผ ๊ฐ€์ ธ์•ผ ํ•  ๋•Œ๋ฉ”๋ชจ๋ฆฌ ํญ๋ฐœ ์ฃผ์˜

์‚ฌ์šฉ๋ฒ•: @Injectable({ scope: Scope.REQUEST })


๐Ÿงช ๋”ฐ๋ผํ•ด๋ณด๊ธฐ: ์‹ค์ „ CRUD ์„œ๋น„์Šค ๊นŽ์•„๋ณด๊ธฐ

๋ง๋ณด๋‹จ ์ฝ”๋“œ์ง€. ์‹ค๋ฌด์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์งœ๊ฒŒ ๋ , Drizzle ORM์„ ๋ถ™์ธ ์œ ์ €(์ฃผ์‹) ๊ฒŒ์‹œํŒ CRUD ๋ผˆ๋Œ€์•ผ.

import { Injectable, NotFoundException } from '@nestjs/common';
import { eq } from 'drizzle-orm';
import { stocks, Stock, NewStock } from '@repo/schema';
 
@Injectable()
export class StocksService {
  // 1. DB ์„œ๋น„์Šค ์ฃผ์ž…๋ฐ›๊ธฐ
  constructor(private readonly db: DatabaseService) {}
 
  // 2. ์ƒ์„ฑ (Create)
  async create(data: NewStock): Promise<Stock> {
    const [stock] = await this.db.insert(stocks).values(data).returning();
    return stock;
  }
 
  // 3. ๋‹จ์ผ ์กฐํšŒ (Read)
  async findBySymbol(symbol: string): Promise<Stock> {
    const stock = await this.db.query.stocks.findFirst({
      where: eq(stocks.symbol, symbol),
    });
    
    // ๐Ÿ”ฅ ์ค‘์š”: ์ปจํŠธ๋กค๋Ÿฌ ๋Œ€์‹  ์„œ๋น„์Šค์—์„œ ์ผ์ฐ ์—๋Ÿฌ๋ฅผ ๋˜์ง„๋‹ค!
    if (!stock) {
      throw new NotFoundException(`์ฆ๊ถŒ ์‹ฌ๋ณผ '${symbol}'์„(๋ฅผ) ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.`);
    }
    return stock;
  }
 
  // 4. ์‚ญ์ œ (Delete)
  async remove(symbol: string): Promise<void> {
    await this.findBySymbol(symbol); // ์žˆ๋‚˜ ํ™•์ธ๋ถ€ํ„ฐ ํ•˜๊ณ  (์—†์œผ๋ฉด ์œ„์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ)
    await this.db.delete(stocks).where(eq(stocks.symbol, symbol));
  }
}

๐Ÿค” ๋Šฅ๋™์  ํšŒ์ƒ ํŠธ๋ฆฌ๊ฑฐ: ์—ฌ๊ธฐ์„œ ๋งŒ์•ฝ NotFoundException์„ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜๋ฉด, ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋นˆ ๊ฐ’์„ ๋ฐ›์•˜์„ ๋•Œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์–ด๋–ค ์‘๋‹ต์ด ๋‚ด๋ ค๊ฐˆ๊นŒ? (์ •๋‹ต: ๋นˆ ๊ฐ์ฒด์ด๊ฑฐ๋‚˜, 200 OK์™€ ํ•จ๊ป˜ ๋‚ด์šฉ์ด ์—†๋Š” ์‘๋‹ต์ด ๋‚ด๋ ค๊ฐ€์„œ ๋ฒ„๊ทธ๋ฅผ ์œ ๋ฐœํ•ด! ์„œ๋น„์Šค์—์„œ ์ปท ์น˜๋Š” ๊ฒŒ ๋งž์•„.)


๐Ÿ’ผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค์™€ ์‹ค๋ฌด ํŒ ๐ŸŸก

1. ๊ฑฐ๋Œ€ํ•œ ์„œ๋น„์Šค๋ฅผ ๋” ์ž‘๊ฒŒ ์ชผ๊ฐœ๊ธฐ

UsersService ํ•˜๋‚˜๊ฐ€ 2,000์ค„์ด ๋„˜์–ด๊ฐ€๋ฉด ์œ„ํ—˜ ์‹ ํ˜ธ์•ผ. ๊ด€๋ จ๋œ ์„œ๋น„์Šค๋“ค์„ ๋” ์ชผ๊ฐœ๊ณ , ๊ทธ ์„œ๋น„์Šค๋ผ๋ฆฌ ์„œ๋กœ ์ฃผ์ž…๋ฐ›๊ฒŒ ํ•ด.
์˜ˆ: UserAuthService, UserProfileService, UserPointsService

2. ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ (์ปค์Šคํ…€ ํ”„๋กœ๋ฐ”์ด๋” ๊ฟ€ํŒ)

์™ธ๋ถ€ ๋ฉ”์ผ ๋ฐœ์†ก ์„œ๋น„์Šค(์˜ˆ: SendGrid)๋ฅผ ์ง์ ‘ ์ฃผ์ž…๋ฐ›์œผ๋ฉด, ๋‚˜์ค‘์— AWS SES๋กœ ๊ฐˆ์•„ํƒˆ ๋•Œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋‹ค ์ˆ˜์ •ํ•ด์•ผ ํ•ด.

// 1. ๊ทœ๊ฒฉ์„ ์ •ํ•จ
export interface IEmailService {
  send(to: string, content: string): Promise<void>;
}
 
// 2. ๊ตฌํ˜„์ฒด
@Injectable()
export class SendGridEmailService implements IEmailService { ... }
 
// 3. ๋ชจ๋“ˆ์—์„œ '๋ฐ”๊ฟ”์น˜๊ธฐ' ์„ค์ • (์‚ฌ์šฉํ•˜๋Š” ์ชฝ ์ฝ”๋“œ ๋ณ€๊ฒฝ X)
@Module({
  providers: [
    {
      provide: 'EMAIL_SERVICE',        // ์ด๋ฆ„ํ‘œ
      useClass: SendGridEmailService,  // ์‹ค์ œ ์“ธ ํด๋ž˜์Šค (์ถ”ํ›„ ์ด๊ฑธ AwsEmailService๋กœ ํ•œ ์ค„๋งŒ ๋ฐ”๊พธ๋ฉด ๋!)
    }
  ]
})

์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @Inject('EMAIL_SERVICE') private emailService: IEmailService ๋กœ ๋ฐ›์œผ๋ฉด ๋ผ. ์ด๊ฑธ ์™„๋ฒฝํžˆ ์ดํ•ดํ•˜๋ฉด ๋„Œ ๋” ์ด์ƒ ์ดˆ๋ณด๊ฐ€ ์•„๋‹ˆ์•ผ.

3. ์ˆœํ™˜ ์˜์กด์„ฑ ํ•ด๊ฒฐ (Circular Dependency)

๊ฐ€์žฅ ์งœ์ฆ ๋‚˜๋Š” ์ƒํ™ฉ: A ์„œ๋น„์Šค๊ฐ€ B๋ฅผ ์ฃผ์ž…๋ฐ›๊ณ , B๊ฐ€ A๋ฅผ ๋‹ค์‹œ ์ฃผ์ž…๋ฐ›๋Š” ์ƒํ™ฉ. ๊ผฌ๋ฆฌ๋ฅผ ๋ฌด๋Š” ๋ฑ€์ด์ง€.

  • **ํ•ด๋ฒ• 1 (๊ถŒ์žฅ):**A์™€ B๊ฐ€ ๊ณตํ†ต์œผ๋กœ ์˜์กดํ•˜๋Š” C ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ถ„๋ฆฌํ•œ๋‹ค.
  • ํ•ด๋ฒ• 2 (์ฝ”๋“œ ์ˆ˜์ •): ๋ถ€๋“์ดํ•˜๋‹ค๋ฉด forwardRef๋ฅผ ์จ์„œ ํ”„๋ ˆ์ž„์›Œํฌ์— "์กฐ๊ธˆ๋งŒ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ์ฃผ์ž…ํ•ด์ค˜" ๋ผ๊ณ  ๋ถ€ํƒํ•œ๋‹ค.
@Injectable()
export class ServiceA {
  constructor(
    @Inject(forwardRef(() => ServiceB)) 
    private serviceB: ServiceB,
  ) {}
}

๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

โŒ Nest can't resolve dependencies of the UsersController (?).

์–ธ์ œ ๋‚˜์˜ค๋Š”๊ฐ€?
์•ฑ์„ ์ผฐ๋Š”๋ฐ ํ„ฐ๋ฏธ๋„์— ์‹œ๋ป˜๊ฑด ๊ธ€์”จ๋กœ ์œ„ ๋‚ด์šฉ์ด ์ถœ๋ ฅ๋˜๋ฉฐ ์„œ๋ฒ„๊ฐ€ ์ฃฝ์Œ.

์›์ธ:
์ปจํŠธ๋กค๋Ÿฌ(์›จ์ดํ„ฐ)๊ฐ€ 1๋ฒˆ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ(0๋ฒˆ ์ธ๋ฑ์Šค) ๋ฌด์–ธ๊ฐ€๋ฅผ ์ฃผ์ž…ํ•ด๋‹ฌ๋ผ๊ณ  ํ–ˆ๋Š”๋ฐ, ํ•ด๋‹น ๋ชจ๋“ˆ์˜ providers: [] ๋ฐฐ์—ด์— ๊ทธ ํด๋ž˜์Šค๊ฐ€ ๋น ์ ธ์žˆ์Œ.

ํ•ด๊ฒฐ์ฑ…:

  • app.module.ts (๋˜๋Š” ํ•ด๋‹น ๊ธฐ๋Šฅ ๋ชจ๋“ˆ) ์—ด๊ธฐ
  • providers: [UsersService] ์ฒ˜๋Ÿผ ๋‚ด๊ฐ€ ์“ธ ์„œ๋น„์Šค๋ฅผ ์ œ๋Œ€๋กœ ๋“ฑ๋กํ–ˆ๋Š”์ง€ ์ฒดํฌ!

โŒ Maximum call stack size exceeded

์–ธ์ œ ๋‚˜์˜ค๋Š”๊ฐ€?
์„œ๋ฒ„๋ฅผ ์ผœ์ž๋งˆ์ž ๋ฌดํ•œ ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ์น˜๋ช…์ ์ธ ์—๋Ÿฌ๋ฅผ ๋ฑ‰์Œ.

์›์ธ:
์œ„์—์„œ ๋งํ•œ ์ˆœํ™˜ ์ฐธ์กฐ ๋•Œ๋ฌธ์ด์•ผ. Module A -> Module B -> Module A ๋ฌดํ•œ ๋ฃจํ”„!

ํ•ด๊ฒฐ์ฑ…:

  1. ์–ด๋А ๋ชจ๋“ˆ๋ผ๋ฆฌ ๊ผฌ์˜€๋Š”์ง€ ํŒŒ์•… (forwardRef ์ ์šฉํ•˜๊ธฐ).
  2. ๊ฐ€์žฅ ์ข‹์€ ๊ฑด ์„ค๊ณ„๋ฅผ ๊ณ ์ณ์„œ ์–‘๋ฐฉํ–ฅ ์˜์กด์„ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์ชผ๊ฐœ๋Š” ๊ฒƒ.

๐Ÿ—‚๏ธ ์น˜ํŠธ์‹œํŠธ

  • ๋‹จ์ˆœ ์„œ๋น„์Šค ์„ ์–ธ: @Injectable() export class MyService {}
  • ์˜์กด์„ฑ ์ฃผ์ž… ๋ฐ›๊ธฐ: constructor(private readonly appService: AppService) {}
  • ๋ชจ๋“ˆ์— ๋“ฑ๋กํ•˜๊ธฐ: @Module({ providers: [MyService] })
  • ์‹ฑ๊ธ€ํ†ค ๋„๊ธฐ: @Injectable({ scope: Scope.REQUEST })

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ๋‹น์‹ ์ด ๊ฐœ๋ฐœ ์ค‘์ธ OrderService์—์„œ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•ด CartService๋ฅผ ๊ฐ€์ ธ๋‹ค ์“ฐ๊ณ  ์‹ถ๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์€?

  • A) cartService = new CartService(); ๋ผ๊ณ  ์ง์ ‘ ํ• ๋‹นํ•œ๋‹ค.
  • B) @Injectable() ์•„๋ž˜์— ์†์„ฑ์œผ๋กœ ์„ ์–ธํ•ด๋‘”๋‹ค.
  • C) constructor(private readonly cartService: CartService) {} ๋ฅผ ํ†ตํ•ด ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›๋Š”๋‹ค.

โœ… ์ •๋‹ต: C

๐Ÿ’ก **์„ค๋ช…:**NestJS์˜ ํ•ต์‹ฌ ์ฒ ํ•™์€ ์ œ์–ด์˜ ์—ญ์ „(IoC)! new ํ‚ค์›Œ๋“œ๋Š” ์ ˆ๋Œ€ ์“ฐ์ง€ ๋ง๊ณ  ์ง€๋ฐฐ์ธ์—๊ฒŒ ๋‹น๋‹นํžˆ ์š”๊ตฌํ•  ๊ฒƒ.

Q2. ๋‹ค์Œ ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์›์ธ์€ ๋ฌด์—‡์ธ๊ฐ€?

@Injectable()
export class UserService {
  constructor(private readonly authService: AuthService) {}
}
 
@Injectable()
export class AuthService {
  constructor(private readonly userService: UserService) {}
}

โœ… ์ •๋‹ต: ์ˆœํ™˜ ์˜์กด์„ฑ (Circular Dependency)
์„ค๋ช…: ๋‹ญ๊ณผ ๋‹ฌ๊ฑ€์˜ ๋ฌธ์ œ์ง€. ์œ ์ € ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๋ ค๋‹ค ๋ณด๋‹ˆ ์ธ์ฆ์ด ํ•„์š”ํ•˜๊ณ , ์ธ์ฆ์„ ๋งŒ๋“ค๋ ค๋‹ˆ ์œ ์ €๊ฐ€ ํ•„์š”ํ•ด์„œ ๋ฌดํ•œ ๋Œ€๊ธฐ์— ๋น ์ ธ. forwardRef๋ฅผ ์“ฐ๊ฑฐ๋‚˜ ๊ณตํ†ต ์„œ๋น„์Šค๋กœ ์ฐข์–ด๋‚ด์•ผ ํ•ด!

Q3. UserService ํŒŒ์ผ์—๋Š” @Injectable()์„ ๋‹ฌ์•˜๊ณ , ์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋„ constructor๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฃผ์ž…๋ฐ›์•˜๋‹ค. ๊ทธ๋Ÿฐ๋ฐ๋„ Nest can't resolve dependencies ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค๋ฉด, ์–ด๋–ค ํŒŒ์ผ์˜ ๋ฌด์Šจ ์ฝ”๋“œ๋ฅผ ๋นผ๋จน์€ ๊ฒƒ์ผ๊นŒ?

โœ… ์ •๋‹ต: user.module.ts ํŒŒ์ผ(ํ•ด๋‹น ๋ชจ๋“ˆ)์˜ @Module ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์•ˆ providers: [UserService] ๋ฐฐ์—ด์— ๋“ฑ๋กํ•˜๋Š” ๊ฑธ ๊นŒ๋จน์—ˆ์Œ!


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ