[Node.js_4기] TIL : Nest.JS_1 (24/03/06)

2024. 3. 6. 23:09공부/내배캠 TIL

목차

 

1. 학습 내용

2. 내용 정리

3. 생각 정리

 

1. 학습 내용 

학습 목표

  • 웹 개발에서 사용되는 레이어드 아키텍처 패턴에 대해서 명확하게 이해하고 설명할 수 있습니다.
  • Nest.js가 Express.js에 비해서 어떤 장점이 있는지 명확하게 이해하고 설명할 수 있습니다.
  • Nest.js를 구성하는 구성 요소들에 대해서 명확하게 이해하고 설명할 수 있습니다.
  • Nest.js의 핵심 개념인 IoC 원칙과 그를 구현하는 방법인 DI 메커니즘에 대해서 명확하게 이해하고 설명할 수 있습니다.

2. 내용 정리 

 

w1_02. Express.js의 장단점

1) express

학습시 테스트 서버를 만드는 것에 매우 최적화 되어 있음.(코드가 별로 필요 없기 때문에...)

 

2) 복잡한 웹 서버

하나의 기능을 추가하기 위해서 하나의 미들웨어를 추가해야 한다...

body-parser를 사용한 페이로드 파싱, CORS, cookie-parser를 적용한 로그인/로그아웃 등... 경량으로 구현했던 서버가 점점 무거워지고 사용하고 싶은 미들웨어가 많아질수록 선언이 늘어나고 미들웨어에 대한 공부가 필요해진다.

 

3) 레이어드 아키텍처 패턴(  [Node.js_4기] TIL : Layered Architecture Pattern (yy/mm/dd) (tistory.com) )

의존성 : 각 계층은 가장 가까운 하위 계층의 의존성을 주입받는다.

독립성 : 각 계층은 다른 계층의 역할을 침범하지 않는다. 또한, 역할이 명확하므로 기능 구현 및 테스트에 용이하다.

 

4) express.js로 레이어드 아키텍쳐 패턴을 적용한다면?

무엇이든 할 수 있지만, 대부분 셀프-서비스

하나하나 조립하기 좋아한다면 불편하지 않겠지만, 귀찮은건 분명하다.

디렉터리 구조를 명확하게 설계해야 하며, 서비스 요구사항 변경 및 기획 추가에 따라 필요한 개념을 새롭게 추가하여야 한다.

 

w1_03. Nest.js의 등장

2) Nest.js의 인기 비결

  • 명령어 하나로 쉽고 간편하게 계층 생성
    • Nest.js는 타입스크립트 기반 웹 프레임워크이므로 JS에 비해 더 엄격한 타입체크를 통해 예외 상황을 방지할 수 있다.
    • ex) Posts 컨트롤러를 만들고 싶을 때 - nest g co posts
    • 컨트롤러, 서비스, 미들웨어, 인터셉터 등 다양한 구성요소를 커맨드로 정확하게 구현할 수 있다.
  • 보다 더 로직에 집중할 수 있는 환경
    • 이 편리성을 통해 개발자는 서버의 핵심 로직 구현만 신경쓰고, 나머지는 Nest.js에 일임하면 된다.
      • 비즈니스 로직 퀄리티 향상(maybe?)
      • 코드 생산성 향상

w2_01. Nest.js 개발환경 구축

2) Windows Node.js + TypeScript 설치

nvm 설치후... npm  설치후... 

<npm install typescript -g >를 실행하여 TypeScript를 글로벌하게 설치

3) Nest.js 개발환경 구축

nest 설치 : npm i -g @nestjs/cli 

새로운 프로젝트 생성 : nest new sparta-nest // npm 선택

 

w2_02. Nest.js코드를 통한 개념 학습

※ main.ts 파일은 절대로 파일 이름을 변경하면 안된다.

<main.ts>

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

nest 웹 어플리케이션의 진입점. create라는 정적 함수를 통해 인스턴스를 새롭게 생성할 수 있다.

 

2) 모듈 - nest.js에서 모든 모듈은 어플리케이션 관련된 컴포넌트, 서비스, 다른 모듈을 그룹화 한다.(캡슐화)

<app.modules.ts>
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

모듈을 만들게 되면 <모듈 이름> 이라는 디렉토리가 src 폴더 밑에 생기며, 해당 디렉토리 안에 <modulename.module.ts>파일이 생성됨.

- 데코레이터 : @가 붙는 키워드

  • 해당 클래스나 함수의 역할을 Nest.js에 알려주는 역할
  • 데코레이터는 각각 필요한 속성들이 다르다.(imports, controllers, provider, exports)
  • 직접 데코레이터를 만들 수도 있다!

- 모듈 속성 파헤치기

  • imports
    • 모듈이 사용하려는 다른 모듈 목록 정의
    • 주로 필요한 프로바이더(서비스)를 제공
    • HttpModule, TypeOrmModule등
  • controllers
    • 현재 모듈과 관련된 컨트롤러 목록 정의
    • 해당 모듈의 요청 처리 로직 담당
  • provider
    • 현재 모듈에서 사용하거나 제공하는 서비스, 리포지토리, 팩토리 등의 목록 정의
    • 비즈니스 로직 처리, 데이터 액세스 등의 작업 수행
  • exports
    • 현재 모듈에서 다른 모듈로 제공하려는 서비스 목록 정의
    • 다른 모듈에서 특정 서비스를 사용하기 위해선 해당 서비를 exports에 포함시켜야 한다.

3) 모듈 고찰

  • service (only) + providers
    • 서비스가 특정 모듈 내에서만 사용되고, 다른 모듈에서는 사용되지 않을 때 사용
      • ex) AuthModule이라는 모듈 내에서만 사용되는 TokenService가 있다 가정, 이 코드는 외부에서는 필요하지 않다.
      • 이 경우 TokenService를 AuthModule의 providers 배열에만 추가하면 된다.
  • module exports + imports
    • 여러 모듈에서 해당 서비스를 공통으로 사용할 경우 사용
    • 서비스를 만들기 전에 모듈부터 만들것.
      • DatabaseService라는 서비스가 있고, 이 서비스는 여러 모듈에 Db연결 및 쿼리 실행 기능을 제공
      • 이경우, DatabaseService를 별도의 DatabaseModule에 포함시키고, DatabaseModule의 exports 배열에 DatabaseService를 추가한다. 
      • 그리고 해당 서비스를 사용할 각 모듈에서 DatabaseModule을 imports 배열에 추가한다.

4) 컨트롤러

<app.controller.ts>
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
  // 인자로 AppService 객체를 넘기면 this.appService라는 변수에 객체가 주입된다.(의존성 주입)

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

@Controller라는 데코레이션을 통해 클래스의 역할을 Nest.js에 알려주고 있다.

@Get() : HTTP GET으로 요청이 들어올 시, 그 아래의 함수를 실행하라는 뜻.

-> @Post, @Put, @Patch, @Delete 데코레이션도 이미 준비되어 있다!

 

 

5) 서비스

<app.service.ts>
import { Injectable } from '@nestjs/common';

@Injectable() // <- 난 Inject(주입)될 수 있어! 라고 선언하는 것이에요.
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

@Injectable 데코레이션은 AppService를 다른 계층에 주입시킬 수 있다는 뜻.

서비스 객체는 실제 레포지토리를 의존(생성자를 통한 DI로 의존성을 주입해야)하며, 비즈니스 로직 실행을 담당한다.

 

w2_03. IoC와 DI

1) IoC(제어 역전)

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  // 1. 사용하고 싶은 서비스 타입 객체를 미리 선언합니다.
  private appService: AppService
  
  constructor() {
    // 2. 생성자에서 실제로 사용할 서비스 객체를 직접 생성합니다.
    this.appService = new AppService();
  }
  ...
}

기존에는 어떤 서비스를 사용하고 싶으면 위의 코드처럼 개발자가 생성부터 소멸까지 직접 관리했어야 했다.

직접 객체를 생성하면, AppController는 AppService와 강하게 결합된다. 이는 의존하는 서비스가 변경되면 개발자도 그에 맞춰 코드를 수정해야 한다는 뜻이며, 서비스가 자주 변경된다면 개발자는 계속 코드를 수정하는 번거로움과 여러 부작용을 떠안게 된다.

 

IoC(제어역전) : 개발자가 사용하고 싶은 객체의 생명주기 관리를 외부(Nest.js IoC 컨테이너)에 위임하는것.

모듈간 결합도를 낮추기 때문에 하나의 모듈이 변경되었을 때 다른 모듈에의 영향을 최소화 한다.

그리고 이것의 구현 방법이

 

2) DI(의존성 주입)

  constructor(private readonly appService: AppService) {}

위의 방법으로 구현한 인스턴스는 Nest.js의 DI 컨테이너에 의해 생성되고 관리된다.

따라서, 개발자는 직접 객체를 생성하거나 관리할 필요가 없어진다. 

이는 코드의 결합도를 낮추고, 유연성과 테스트 용이성을 향상시킨다.

 

3. 생각 정리 

 

가장 놀라웠던 것은, CLI를 잠깐 건드는것 만으로 여러 계층등이 생성되고, 해당 파일 안에 기본적인 토대가 되는 코드들이 작성되어 있다는 것이였습니다.

아마 계층형 아키텍쳐를 주로 사용한다면 Nest.js는 가장 좋은 웹 프레임워크가 될것 같습니다.

(다른 패턴들의 경우 nest.js에서 사용하고 싶다면 직접 수정해야 한다 들었습니다.)