Search

5. Nest 아키텍처: 모듈로 코드 정리하기

NestJS에서 의존성 주입(Dependency Injection, DI)을 제대로 이해하기 위해, 작은 데모 프로젝트를 만들어보자. 이 프로젝트를 통해 모듈과 서비스가 어떻게 연결되는지, 그리고 의존성 주입이 어떻게 작동하는지를 배우게 된다.

프로젝트 개요

이번 프로젝트는 간단한 컴퓨터 시스템을 모델링하는 것이 목표이다. 컴퓨터는 여러 하드웨어 구성 요소로 이루어져 있지만, 이번 프로젝트에서는 다음 3가지 핵심 요소만 구현한다.
1.
Power Module (전원 공급)
2.
CPU Module (연산 장치)
3.
Disk Module (저장 장치)
각 모듈을 개별적으로 동작하며, NestJS의 의존성 주입(DI)를 활용하여 서로 연결된다.

프로젝트 구조

Power Module
전원을 공급하는 역할
CPU Module
PowerService에 의존하며, 전원이 공급될 때 동작
Disk Module
PowerService에 의존하며, 전원이 공급될 때 동작
Computer Module
CPU와 Disk를 하나의 컴퓨터로 묶는 역할 수행

프로젝트 생성

1. NestJS 프로젝트 생성

터미널에서 다음 명령어를 실행하여 새 NestJS 프로젝트를 생성한다.
nest new di
Shell
복사
패키지 관리자로 npm을 선택한 후, 설치가 완료될 때까지 기다린다.

2. 프로젝트 디렉터리로 이동 및 정리

cd di
Shell
복사
src 디렉터리 내부의 모든 파일을 삭제하고, main.ts 파일만 남겨둔다.

3. 모듈 및 서비스 생성

이제 프로젝트의 핵심 구조를 만들기 위해 모듈과 서비스를 생성한다.
nest g module computer nest g module cpu nest g module disk nest g module power
Shell
복사
nest g service cpu nest g service disk nest g service power nest g controller computer
Shell
복사

4. 메인 모듈 설정

main.ts 파일을 수정해서 ComputerMoudle을 메인 모듈로 설정한다.
import { NestFactory } from '@nestjs/core'; import { ComputerModule } from './computer/computer.module'; async function bootstrap() { const app = await NestFactory.create(ComputerModule); await app.listen(3000); } bootstrap();
TypeScript
복사

5. 파워 서비스 구현

가장 먼저 power 모듈의 power.service.ts를 수정해서 전력을 공급하는 메서드를 추가한다.
import { Injectable } from '@nestjs/common'; @Injectable() export class PowerService { supplyPower(watts: number) { console.log(`Supplying ${watts} worth of power.`); } }
TypeScript
복사
이제 supplyPower 메서드는 호출될 때마다 요청된 전력량을 로그로 출력한다.

6. 여러 모듈 간에 서비스 공유하기

이제 Power 모듈과 CPU 모듈 사이에서 PowerService를 공유하는 방법을 살펴보자.

6.1. Power 모듈에서 PowerService를 export하기

PowerService는 기본적으로 프라이빗이므로 다른 모듈이 액세스할 수 없다. 이를 변경하기 위해 exports 속성을 추가해야 한다.
power.module.ts
import { Module } from '@nestjs/common'; import { PowerService } from './power.service'; @Module({ providers: [PowerService], exports: [PowerService], }) export class PowerModule {}
TypeScript
복사

6.2. CPU 모듈에서 Power 모듈을 가져오기

CPU 모듈에서 Power 모듈을 가져와야 CPU 서비스가 Power 서비스를 사용할 수 있다.
cpu.module.ts
import { Module } from '@nestjs/common'; import { CpuService } from './cpu.service'; import { PowerModule } from 'src/power/power.module'; @Module({ imports: [PowerModule], providers: [CpuService], }) export class CpuModule {}
TypeScript
복사

6.3. CPU 서비스에서 Power 서비스 주입하기

이제 CpuService에서 PowerService를 주입받아 사용할 수 있다.
cpu.service.ts
import { Injectable } from '@nestjs/common'; import { PowerService } from 'src/power/power.service'; @Injectable() export class CpuService { constructor(private powerService: PowerService) {} }
TypeScript
복사
CPU 서비스는 PowerService를 주입받아 전력을 소비하면서 연산을 수행한다.
따라서, 연산을 담당하는 compute 메소드를 작성한다.
cpu.service.ts
import { Injectable } from '@nestjs/common'; import { PowerService } from 'src/power/power.service'; @Injectable() export class CpuService { constructor(private powerService: PowerService) {} compute(a: number, b: number) { console.log('Drawing 10 watts of power from Power Service'); this.powerService.supplyPower(10); return a + b; } }
TypeScript
복사

6.4. 디스크 모듈과 서비스 구현

디스크 서비스 또한 PowerService를 주입받아 전력을 소비하며 데이터를 제공하는 역할을 한다.
disk.service.ts
import { Injectable } from '@nestjs/common'; import { PowerService } from 'src/power/power.service'; @Injectable() export class DiskService { constructor(private powerService: PowerService) {} getData() { console.log('Drawing 20 watts of power from PowerService'); this.powerService.supplyPower(20); return 'data!'; } }
TypeScript
복사
디스크 모듈도 PowerModule을 임포트하여 디스크 서비스가 전력 서비스를 사용할 수 있도록 한다.
disk.module.ts
import { Module } from '@nestjs/common'; import { DiskService } from './disk.service'; import { PowerModule } from 'src/power/power.module'; @Module({ imports: [PowerModule], providers: [DiskService], }) export class DiskModule {}
TypeScript
복사

6.5. CPU 및 디스크 모듈을 컴퓨터 모듈에 가져오기

이제 컴퓨터 모듈에서 CPU 및 디스크 모듈을 불러와야한다.
CPU 모듈과 디스크 모듈의 서비스들을 외부 모듈에서 사용할 수 있도록 설정한다.
cpu.module.ts
import { Module } from '@nestjs/common'; import { CpuService } from './cpu.service'; import { PowerModule } from 'src/power/power.module'; @Module({ imports: [PowerModule], providers: [CpuService], exports: [CpuService], }) export class CpuModule {}
TypeScript
복사
disk.module.ts
import { Module } from '@nestjs/common'; import { DiskService } from './disk.service'; import { PowerModule } from 'src/power/power.module'; @Module({ imports: [PowerModule], providers: [DiskService], exports: [DiskService], }) export class DiskModule {}
TypeScript
복사
그런 다음 컴퓨터 모듈에서 CPU 및 디스크 모듈을 불러온다.
computer.module.ts
import { Module } from '@nestjs/common'; import { ComputerController } from './computer.controller'; import { CpuModule } from 'src/cpu/cpu.module'; import { DiskModule } from 'src/disk/disk.module'; @Module({ imports: [CpuModule, DiskModule], controllers: [ComputerController], }) export class ComputerModule {}
TypeScript
복사

6.6. 컴퓨터 컨트롤러에서 CPU 및 디스크 서비스 사용하기

컴퓨터 컨트롤러에서 CpuServiceDiskService를 주입받아 사용하도록 설정한다.
그리고, 테스트를 위해 간단하게 run 메소드를 구현해보았다.
computer.controller.ts
import { Controller, Get } from '@nestjs/common'; import { CpuService } from 'src/cpu/cpu.service'; import { DiskService } from 'src/disk/disk.service'; @Controller('computer') export class ComputerController { constructor( private cpuService: CpuService, private diskService: DiskService, ) {} @Get() run() { return [this.cpuService.compute(1, 2), this.diskService.getData()]; } }
TypeScript
복사
이제 프로젝트를 실행한 뒤 올바르게 동작하는지 확인해보자.
npm run start:dev
TypeScript
복사
브라우저에서 다음 URL에 접속하면 다음과 같은 결과를 확인할 수 있다.
http://localhost:3000/computer
TypeScript
복사
[3, "data!"]
TypeScript
복사

마무리

이제 의존성 주입이 어떻게 작동하는지 파워 모듈과 CPU 모듈을 중심으로 이 둘이 서로 어떻게 작용하는지 마지막으로 정리해보자.

DI 컨테이너의 역할

모듈이 생성되면, NestJS는 이를 기반으로 DI 컨테이너를 만든다.
컨테이너 내부에는 모든 모듈과 서비스가 정리되고, 각 서비스의 의존성이 확인된다.
만약 의존성이 필요한 서비스라면, 컨테이너에서 자동으로 찾아 주입한다.

파워 모듈과 CPU 모듈의 연결

파워 모듈은 독립적인 서비스이므로, 자체적으로 생성될 수 있다.
그런데 CPU 모듈에서 파워 서비스를 사용해야 한다면?
이때 exports를 활용하여 다른 모듈에서도 사용할 수 있도록 공유할 수 있다.
즉, 파워 모듈이 CPU 모듈로 import될 때, 내부의 서비스도 함께 전달된다.

DI 컨테이너는 단 하나!

실제로 DI 컨테이너는 여러 개 생기는 것이 아니다.
NestJS는 하나의 컨테이너 안에서 모든 모듈과 서비스의 관계를 정리하고 주입한다.
하지만 개념적으로는 각 모듈이 독립적인 컨테이너처럼 연결되는 모습으로 이해하면 도움이 된다.
즉, 임포트(imports)엑스포트(exports)의 관계를 잘 이해하면, NestJS의 모듈 간 데이터 흐름을 쉽게 파악할 수 있다.