NestJS 帶你飛! 시리즈 번역 05# Module

2023. 5. 30. 23:37개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다. 

Module은 Nest의 세상에서 무척이나 중요한 존재입니다.

Module은 주로 비슷한 역할을 하는 기능들을 한데 모아 각 모듈의 요구에 따라 줄줄이 연결 되어있고
Nest App에서 적어도 1개 이상의 루트 모듈이 필요하다고 얘기한 적이 있었습니다.

Nest는 이 루트 모듈로부터 모든것을 사용합니다.

 

비슷한 역할의 기능들을 한데 모은다」 라는건 어떤 의미일까요?

레스토랑을 예로 들어보겠습니다. 이미 대만, 일본, 미국 요리의 세 구역으로 나누었습니다. 

매 구역은 모두 자기가 책임지고있는 범위가 있을것이고 대만 요리에서 일본 돈코츠 라멘이 있지는 않을테지요.

왜냐하면 대만 요리는 대만 요리만 제공해야 하니까요. Nest에서 예를 들어보겠습니다.

똑같이 3개의 모듈로 나누어보겠습니다. TodoModule, UserModule, AuthModule입니다.

일반적인 상황이라면 UserModule안에 Todo의 정보를 얻을 수 있는 상황을 원하지는 않겠죠?

UserModule이라면 User와 가장 관련있는 자원만을 제공하는것이 최대 효율을 낼 수 있는 방법일 것입니다.

 

 

각 모듈의 요구에 따라 줄줄이 연결한다」 라는건 어떤 의미일까요?

 

사실 Module의 기능은 꼭 Controller를 포함할 필요는 없습니다.

다시말해 Module은 단순한 기능들을 모아 생성된 모듈이어도 괜찮다는 뜻입니다. (ex: MongooseModule)

자주 비유했던 식당을 예로 다시 들어보겠습니다.

'대만 요리 레스토랑' 에서는 '젓가락'을 사용하게 하고 싶고

'일본 요리 레스토랑' 에서도 젓가락을 사용하게 하고 싶다고 가정해보겠습니다.

하지만 '미국 요리 레스토랑' 에서는 '젓가락'이라는 도구는 그리 알맞는 선택이 아니어보입니다.

이 '젓가락'을 공통 모듈로 생각하면 됩니다. 대만/일본 요리에서 공용으로 사용하니까요.

 

 

 

Module 생성

모든 Module은 무조건 @Module 데코레이터로 정의를 해야합니다. NestCLI를 통해 빠르게 생성해봅시다.

 

$ nest generate module <MODULE_NAME>
주의: <MODULE_NAME>은 경로를 포함해도 됩니다. (ex: features/todo
이렇게 생성하면 src 폴더 아래에 Module과 입력했던 경로를 포함하여 생성됩니다.

todo 라는 Module을 하나 만들어봅시다.

todo.module.ts의 내용입니다. :

import { Module } from '@nestjs/common';

@Module({}) // <- 여기 빈 객체
export class TodoModule {}

 

파라미터 소개

Module을 생성하고 나면 @Module 데코레이터 안에 빈 객체 하나가 들어있는걸 발견할 수 있습니다.

이는 NestCLI가 모듈의 용도가 무엇인지 모르기 때문에 이를 개발자가 알맞게 입력하여 사용하게끔 하기 위함입니다.

그렇다면 어떤 파라미터들을 사용할 수 있을까요? 한번 알아봅시다.

 

  • controllers: Module 아래의 Controller가 이곳에 들어갑니다. 모듈이 로드될 때 인스턴스화 됩니다.
  • providers: 사용되는 Provider가 이곳에 들어갑니다. (ex: Service) 이 또한 모듈이 로드될 때 같이 인스턴스화 됩니다.
  • exports: Module 아래의 Provider가 다른 Module과 같이 사용되야 할 경우에 이곳에 Module을 내보낼 수 있습니다.
  • imports: 다른 모듈의 Providers를 불러올 때 이곳에 입력하면 됩니다.
주의: Provider는 추후에 다시 자세히 설명할 예정입니다.

 

기능 모듈 (Feature Module)

대다수의 Module은 기능 모듈에 속합니다. 먼저 Controller를 Module에 추가 해봅시다. 

아래의 명령어로 생성이 가능합니다.

$ nest generate controller <CONTROLLER_NAME>

이 포스팅에서는 <CONTROLLER_NAME>features/todo로 명명 하겠습니다.

TodoModule이 자동으로 해당 Controller를 controllers에 집어넣어줍니다.

import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';

@Module({
  controllers: [TodoController]
})
export class TodoModule {}

앞에서 얘기했던 라우팅 기능 모듈은 Controller와 Service가 담당한다고 했었습니다.

이 포스팅에서는 먼저 Service를 하나 생성하고, 나중에 다시 Service에 대해 다뤄보겠습니다.

$ nest generate service <SERVICE_NAME>

이 포스팅에서는 <CONTROLLER_NAME>features/todo로 명명 하겠습니다.

역시 TodoModule이 자동으로 해당 Service를 providers에 집어넣어줍니다.

import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';

@Module({
  controllers: [TodoController],
  providers: [TodoService]
})
export class TodoModule {}

todo.service.ts의 내용을 조금 수정해보도록 하겠습니다.

TodoService에서 getTodos 메서드를 만들어 todos의 데이터를 반환하도록 해보겠습니다.

import { Injectable } from '@nestjs/common';

@Injectable()
export class TodoService {

  private todos: { id: number, title: string, description: string }[] = [
    {
      id: 1,
      title: 'Title 1',
      description: ''
    }
  ];

  getTodos(): { id: number, title: string, description: string }[] {
    return this.todos;
  }

}

그러고 todo.controller.ts의 내용을 조금 수정해보도록 하겠습니다.

TodoControllerconstructorTodoService를 주입해줍시다.

import { Controller, Get } from '@nestjs/common';
import { TodoService } from './todo.service';

@Controller('todos')
export class TodoController {

  constructor(
    private readonly todoService: TodoService
  ) {}

  @Get()
  getAll() {
    return this.todoService.getTodos();
  }

}

동작하는 모듈 하나를 완성시켰습니다. 그럼 어떻게 이걸 사용해야 할까요?

방법은 간단합니다. 루트 모듈에 집어넣으면 됩니다. 하지만 Module을 생성할 때 이미 자동으로 입력 되었을것입니다.

수동으로 작업할 필요 없다는 뜻입니다! 자동화를 좋아하는 개발자의 입장에선 정말 행복한 일이 아닐수 없습니다.

서버를 가동시키고 웹 브라우저를 열어 http://127.0.0.1:todos에 접속해봅시다.

 

공유 모듈 (Shared Module)

Nest의 세계에서는 모든 Module은 단일 인스턴스입니다. (싱글톤) 

각 모듈간에는 같은 인스턴스를 공유한다는 얘기이기도 하고, 모든 모듈은 공유 모듈인 셈입니다.

설계 원칙만 준수해서 모듈을 설계한다면 모든 Module은 높은 재사용성을 가질 수 있습니다.

이것도 앞에서 강조했던 '각 모듈의 요구에 따라 줄줄이 연결한다'의 연장선인 셈입니다.

간단한 실험하나를 해보겠습니다. TodoServiceTodoModule로부터 내보내 보겠습니다.

 

이어서 새로운 Module과 Controller를 생성해보겠습니다. 아래는 생성 커맨드입니다.

$ nest generate module features/copy-todo
$ nest generate controller features/copy-todo

todo.service.ts의 내용을 조금 수정해보겠습니다. TodoService에서 createTodo 메서드를 하나 생성하겠습니다.

import { Injectable } from '@nestjs/common';

@Injectable()
export class TodoService {

  private todos: { id: number, title: string, description: string }[] = [
    {
      id: 1,
      title: 'Title 1',
      description: ''
    }
  ];

  getTodos(): { id: number, title: string, description: string }[] {
    return this.todos;
  }

  createTodo(item: { id: number, title: string, description: string }) {
    this.todos.push(item);
  }

}

CopyTodoModule 안에 TodoModule을 불러와보겠습니다.

import { Module } from '@nestjs/common';
import { TodoModule } from '../todo/todo.module';
import { CopyTodoController } from './copy-todo.controller';

@Module({
  controllers: [CopyTodoController],
  imports: [TodoModule]
})
export class CopyTodoModule {}

그리고 copy-todo.controller.ts를 수정해서 CopyTodoControllerconstructorTodoService를 주입하고, createTodo라는 메서드를 사용하겠습니다.

import { Body, Controller, Post } from '@nestjs/common';
import { TodoService } from '../todo/todo.service';

@Controller('copy-todos')
export class CopyTodoController {

  constructor(
    private readonly todoService: TodoService
  ) {}

  @Post()
  create(@Body() body: { id: number, title: string, description: string }) {
    this.todoService.createTodo(body);
    return body;
  }

}

 

Postman으로 테스트한 결과입니다.

http://127.0.0.1:3000/todos에 접속해보면...

다른 서비스에서 만든 자료가 들어가있는 모습을 확인할 수 있습니다.

Service같은 Provider는 Module에서 인스턴스화되어 다른 모듈이 이 인스턴스를 사용하고자 할때

내보내기를 통해 다른 Module과 공유할수 있다는 결론을 얻었습니다. 아래는 그 관계를 표현한 그림입니다.

 

전역 모듈 (Global Module)

Module이 다른 Module과 같이 사용되고자 할 때는 각 Module에서 불러오기를 통해 import 됩니다.

이때 Module은 전역 모듈이됩니다. Module에서 @Global 데코레이터를 사용하면

다른 모듈에서 불러오기를 하지 않아도 사용 가능해집니다.

TodoModule을 예로 들어보겠습니다.

import { Module, Global } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';

@Global()
@Module({
  controllers: [TodoController],
  providers: [TodoService],
  exports: [TodoService]
})
export class TodoModule {}
주의: 이렇게 편하게 전역모듈로써 불러오기의 횟수를 줄일수 있으나,
이 데코레이터는 좋은 디자인 패턴을 유지하기 위해선 최대한 지양해야 합니다.

 

일반 모듈 (Common Module)

이 모듈은 디자인의 방법 중 하나입니다.
Module을 어떠한 Controller 혹은 Provider에 포함하지 않으며, 불러오기 된 Module을 다시 내보내기만 할 수 있습니다.
이렇게 하면  일반 Module들을 하나로 묶을수 있다는 큰 장점이 있습니다.

다른 Module과 사용하려면 이 Module을 불러오기만 하면 쓸 수 있습니다. 아래는 그 예제입니다.

@Module({
  imports: [
    AModule,
    BModule
  ],
  exports: [
    AModule,
    BModule
  ],
})
export class CommonModule {}

 

마치며

Module은 Nest에서 엄청 중요한 역할을 하고 있습니다. 특히 Provider와 밀접한 관련이 있습니다.

다음 포스팅에서는 이 메커니즘을 설명해보고자 합니다. 오늘 배운것을 간단히 요약해 복습해본 후
포스팅을 마치겠습니다.

 

1. Module은 비슷한 기능을 한곳에 모아놓은 것이며  각 모듈의 요구에 따라 줄줄이 연결되어 있습니다.

2. Module은 controllers, providers, importsexport 4개의 파라미터가 존재합니다.

3. 대부분의 Module은 모두 기능 모듈입니다. '비슷한 기능을 한 곳에 모은다'를 기본 원칙으로 합니다.

4. 매 Module은 공유 모듈이기도 합니다. '각 모듈의 요구에 따라 줄줄히 연결한다' 라는 기본 원칙으로 디자인합니다.

5. 공용 모듈을 통해 다른 모듈과 같은 인스턴스를 사용할 수 있습니다.

6. 전역 모듈을 통해 불러오기의 횟수를 줄일 수 있지만 원하지 않는 모듈을 전역 모듈로써 사용할 수도 있게되며,
이는 그리 좋은 디자인 패턴은 아닙니다.

7. 일반 모듈을 사용하여 모듈 관리 방식을 통일할 수 있도록 해야합니다.