[NestJS] - 5. 모듈 의존성 주입, 다른 모듈의 서비스를 사용해보자
이전 포스팅에서 NestJS의 의존성 주입과 제어의 역전에 대해서 다루었다.
이제 그 내용을 바탕으로 서로 다른 모듈들 간에서의 의존성을 어떻게 설정해 줄 것인가에 대해서 간단한 프로젝트를 만들며 알아보려고 한다.
TODO
- 요리사 모듈과 웨이터 모듈을 만든다.
- 요리사는 직접 고기를 자르거나 잘린 고기가 있다면 스테이크를 굽는다.(없다면 굽지 못한다)
- 웨이터는 탁자를 닦거나 스테이크를 주문받아 서빙한다.
프로젝트 구조
다음과 같은 프로젝트 구조를 만들었다. 1. app.module.ts는 루트 모듈로 사용하며, 컨트롤러와 서비스는 지워주었다. 2. apis 디렉터리를 생성 후 chef와 waiter를 추가하였다. 3. chef와 waiter의 controller, module, service를 추가하여 모듈 구조를 완성해준다. 아래의 CLI 명령어를 통해 생성하거나, 직접 클래스를 만들어도 된다. - 모듈 생성 : nest generate mo chef - 컨트롤러 생성 : nest generate co chef - 서비스 생성 : nest generate s chef |
▶ app.module.ts
import { Module } from '@nestjs/common';
import { ChefModule } from './apis/chef/chef.module';
import { WaiterModule } from './apis/waiter/waiter.module';
@Module({
imports: [ChefModule, WaiterModule],
controllers: [],
providers: [],
})
export class AppModule {}
루트 모듈에는 사용할 모든 모듈을 imports 해주어야 한다. 그래야 main.ts에서 bootstrap이 실행될 때 모듈을 인식하고, 의존성을 주입할 수 있다.
Chef 구현하기
▶ chef.module.ts
import { Module } from '@nestjs/common';
import { ChefController } from './chef.controller';
import { ChefService } from './chef.service';
@Module({
controllers: [ChefController],
providers: [ChefService],
})
export class ChefModule {}
ChefModule의 작성 내용이다.
- @Module 데코레이터를 붙여주어 Nest가 싱글톤으로 생성해야 하는 모듈임을 인식시켜주었다
- controllers에 해당 모듈에서 사용할 컨트롤러인 ChefController를 넣어주었다.
- providers에 해당 모듈 안에서 의존이 필요한 모든 Provider들을 작성한다.
▶ chef.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ChefService } from './chef.service';
@Controller('chef')
export class ChefController {
constructor(private readonly chefService: ChefService) {}
@Get('ready')
readyToCook() {
return this.chefService.sliceMeet();
}
@Get('cook')
cookSteak() {
return this.chefService.cookSteak();
}
}
- @Controller 데코레이터를 붙여주어 Nest가 싱글톤으로 생성해야 하는 컨트롤러임을 인식시켜주었다
- 해당 컨트롤러는 ChefService의 비즈니스 로직을 사용해야 하므로 의존성 주입을 받아야 한다.
- constructor의 private readonly chefService를 통해 모듈의 providers에 선언된 서비스를 의존성 주입을 받는다.
- 각각의 엔드포인트의 요청에 따라 서비스에서 요리를 하는 로직이나, 재료손질을 하는 로직을 사용하여 그 결과를 응답으로 반환한다.
▶ chef.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class ChefService {
private meet;
constructor() {
this.meet = 0;
}
sliceMeet() {
console.log('고기를 자릅니다');
this.meet++;
return '고기를 잘랐습니다';
}
cookSteak() {
if (this.meet > 0) {
console.log('스테이크를 굽습니다.');
this.meet--;
return '스테이크';
} else {
return '재료소진';
}
}
}
- @Injectable 데코레이터를 붙여주어 Nest가 싱글톤으로 생성해야 하는 Provider임을 인식시켜주었다.
- 아래 두개의 비즈니스 로직을 구현하였다.
- 재료손질 : 고기를 자를 때 잔여 고기의 수를 증가시킨다.
- 요리 : 고기가 있다면 스테이크를 반환하고, 없다면 재료소진을 반환한다.
▶ 엔드포인트별 Response
/chef/ready | 고기를 잘랐습니다 | |
/chef/cook | meet > 0 | 스테이크 |
/chef/cook | else | 재료소진 |
Waiter 구현하기
Chef와 마찬가지로 모듈, 서비스, 컨트롤러를 구현한다.
이번엔 서비스 먼저 살펴보자
▶ waiter.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class WaiterService {
clean() {
return '탁자를 깨끗하게 닦았습니다.';
}
serving(food: string) {
return `${food}를 서빙하였습니다.`;
}
}
- 청소 : 청소를 한다
- 서빙 : 요리를 인자로 받아 서빙한다.
▶ waiter.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ChefService } from '../chef/chef.service';
import { WaiterService } from './waiter.service';
@Controller('waiter')
export class WaiterController {
constructor(
private readonly waiterService: WaiterService,
private readonly chefService: ChefService,
) {}
@Get('clean')
cleanTable() {
return this.waiterService.clean();
}
@Get('order')
servingSteak() {
const food = this.chefService.cookSteak();
if (food === '재료소진') {
return '재료가 소진되었습니다. 주문을 받을 수 없습니다.';
} else {
return this.waiterService.serving(food);
}
}
}
- 해당 컨트롤러의 servingSteak()를 주목하면 요리사가 요리를 하는 행위가 있어야 스테이크를 서빙할 수 있다. 따라서 WaiterService 뿐 아니라 ChefService의 의존성 주입이 필요하다.
- constructor에서 waiterService와 chefService의 의존성을 주입받아 해당 서비스들을 사용한다.
▶ waiter.module.ts
import { Module } from '@nestjs/common';
import { ChefModule } from '../chef/chef.module';
import { WaiterController } from './waiter.controller';
import { WaiterService } from './waiter.service';
@Module({
imports: [ChefModule],
controllers: [WaiterController],
providers: [WaiterService],
})
export class WaiterModule {}
- WaiterService 뿐 아니라 ChefModule 안의 ChefService를 의존성 주입을 받아야 한다고 명시해주어야 한다.
- imports에 해당 Provider가 필요한 모듈을 작성하면 ChefModule에서 exports에 명시된 Providers들의 의존성 주입이 가능해지며 사용할 수 있다.
▶ chef.module.ts
import { Module } from '@nestjs/common';
import { ChefController } from './chef.controller';
import { ChefService } from './chef.service';
@Module({
controllers: [ChefController],
providers: [ChefService],
exports: [ChefService],
})
export class ChefModule {}
- 다른 모듈(WaiterModule)에서 해당 모듈의 Provider을 의존성 주입받아 사용 가능하게 하기 위해서 exports에 외부에서 사용되게 할 Provider들을 작성해준다.
▶ 엔드포인트별 Response
/chef/ready | 고기를 잘랐습니다 | |
/chef/cook | meet > 0 | 스테이크 |
/chef/cook | else | 재료소진 |
/waiter/clean | 탁자를 깨끗하게 닦았습니다 | |
/waiter/order | Chef의 meet > 0 | 스테이크를 서빙했습니다 |
/waiter/order | else | 재료가 소진되었습니다. 주문을 받을 수 없습니다. |
싱글톤 패턴과 의존성 주입
- /chef/ready를 한 이후 /waiter/order를 하면 스테이크를 서빙
- /chef/ready를 1회 하고 /chef/cook 이후 /waiter/order를 하면 재료 소진
다음 사실을 보았을 때 서로 다른 모듈에서 ChefService를 사용하고 있어도 속성인 meet의 상태를 공유한다는 것을 알 수 있다.
즉 앞에서 말했던 싱글톤 패턴으로 생성된 인스턴스를 의존성 주입받아서 사용한다는 것이다.
또한 데코레이터로 선언된 모듈, 서비스들은 싱글톤 패턴으로 인스턴스가 생성되는 것을 확인할 수 있었다.
정리
- 모듈, 컨트롤러, 서비스 만들어 NestJS가 의존성 관리하게 만들려면 데코레이터로 선언하기
- 모듈의 providers에 의존성 주입받아야 하는 Provider들 작성하기
- 생성자를 통해서 의존성 주입받아서 사용하기
- 다른 모듈의 Provider들을 사용하려면 imports, 다른 모듈이 내 Provider를 사용하게 하려면 exports
이제 NestJS의 이론적인 부분은 여기까지 다루고 다음 포스팅부터 중고마켓을 만들기 위한 ORM을 통한 데이터베이스 사용과, REST API 생성, 인증 등을 이어서 다룰 예정이다.
해당 강의를 들으면서 학습한 내용을 바탕으로 저만의 프로젝트를 만드는 과정을 기록하여 남기는 것을 목표로 하고 있습니다.
주관적인 생각이 들어가 있을 수 있으므로 혹시 틀린 내용이 있다면 피드백 부탁드립니다.
'Backend > Node.js (NestJS)' 카테고리의 다른 글
[NestJS] - 7. TypeORM 데이터베이스 연동과 엔티티 매핑 (0) | 2023.02.22 |
---|---|
[NestJS] - 6. 상품 REST API 만들기 (0) | 2023.02.09 |
[NestJS] - 4. DI / IoC (의존성 주입/제어의 역전) (0) | 2023.02.09 |
[NestJS] - 3. 모듈 구조 - Module/Service/Controller (0) | 2023.02.09 |
[NestJS] - 2. NestJS 프로젝트 생성 (0) | 2023.02.08 |