[Nest] Nest 기초개념 및 기본구조

김택수·2022년 11월 18일
1

Nestjs를 공부하며 Nest를 구성하는 기본요소들과 그 기능들에 대해서 정리한다.
먼저 기초개념을 정리하고, 그 개념들이 적용된 기본구조들의 요소에 대해 정리할 것이다.
특히, Express를 공부하고 Nest로 넘어왔기 때문에 두가지 개념을 비교하며 정리할 것이니 참고하시면 좋겠다.

1. 데코레이터

파이썬의 데코레이터 or 자바의 어노테이션과 비슷한 기능을 하는 Nest의 기능. (파이썬과 자바는 잘 모름.)

// express router

//router/index.js
app.use('endpoint1', router.router)

//router/router.js
app.get('endpoint2', Controller.function)

// /endpoint1/endpoint2로 get요청을 하면 Controller의 function이 실행되는 순서

// nestjs decorater
@Controller('endpoint1')
class Controller {
	@Get('endpoint2')
	function() {}
}

// Controller 데코레이터의 인자가 endpoint1이 되고, 그 아래 http Method로 사용된 데코레이터의 인자가 endpoint2가 된다.
// 똑같이 /endpoint1/endpoint2로 get요청을 하면 해당 Controller class 내부의 function이 실행된다.

위의 코드와 마찬가지로 Controller라는 Class를 Nestjs안에서 Controller의 역할을 하게 하기 위해 @Controller와 같이 데코레이터로 속성을 부여한 것.
마찬가지로 get요청이 들어왔을 때, function을 Get요청을 수행할 수 있는 속성을 부여한 것이 @Get이다.

결론적으로 데코레이터란 속성을 부여해주는 기능을 가졌다고 생각하면 이해하기가 편하다.

2. Provider & DI

1. Provider

Nest는 실생활과 가까운 코드작업을 위해 Provider라는 개념을 도입했다. Provider는 한글로는 공급자로써, 어떠한 제품 또는 서비스를 제공하는 역할을 말한다.
Nest안에서는 Service, Repository, Factory, Helper 등이 Provider로 취급될 수 있다. (아직까진 Service만 생각하기로 하자)
Nest는 기본적으로 Module에 Provider를 등록해야만 다른 곳에서 사용이 가능하다. (사업자등록과 같다고 생각하면 쉽다.)
흐름으로 보자면 Provider안에 있는 함수를 다른곳에 제공한다는 뜻이 된다. (보통은 Controller)

Express에서 Layered Pattern을 사용했을 때, Service단에 있는 함수를 Controller에서 실행하는 행위과 비슷하다고 생각하면 편하다.

// 순서 중요!
// Service.ts
@Injectable() // 주입가능한 상태로 만들어주는 데코레이터
export class Service {
	function() {
    return "Hello World!"
    }
  
}

// Module.ts
@Module({
  controllers: [Controller],
  providers: [Service], // 둘 다 등록을 해줘야 사용할 수 있다.
})
export class BoardsModule {}


// Controller.ts
@Controller()
class Controller {
  constructor(private service : Service) // 밑에서 설명하는 종속성 주입
	@Get()
	function() {
    return this.service.function(); // Hello World!
    }
}

위의 순서대로 Service.ts에서 Injectable 데코레이터를 통해 Service class를 주입가능한 상태로 만들어주고, Module.ts에서 provider로써 등록해준다.
마찬가지로 소비자의 역할인 Controller도 등록해주어야 Module이 실행됐을 때 요청이 흘러들어갈 수 있다.

Controller class안에서 constructor에 Service class를 service에 담아주면 Controller class 내부에서 Service class안에 있는 함수들을 사용가능해진다.
그래서 하단의 function이라는 함수 안에서 service 안에 있는 function을 사용가능해진다.

DI(Dependency Injection) 종속성 주입

이 때 이용되는 개념이 DI(Dependency Injection)이며,종속성 주입이라고 한다.

DI는 보통 A가 B를 의존한다의 관계로 봤을 때, B의 상태가 변하면 A의 상태도 변할 수 있다는 것으로 정의할 수 있고, 상단의 코드에서 보면 Controller가 Service를 의존하게 됐기 때문에 Service가 변하면 Controller에서도 영향을 받을 수 있다는 뜻이다. (Controller에서 Service의 함수를 가져다 사용하기 때문에 의존하고 있다고 볼 수 있음)

DI를 구현하는 방법은 외부에서 의존관계를 결정하는 것이기 때문에, 의존받을 class의 constructor 내부에서 외부의 class를 사용하는 것으로 구현할 수 있다. (class의 기능을 만드는 것은 constructor에서 결정되기 때문에)

그래서 상단의 코드에서 Controller class의 constructor에 Service class를 넣어줬다.

그래서 DI를 하는데는 분명 이유가 있을 것인데 그것은 몇가지로 정리해볼 수 있다.

1. 의존성이 줄어든다.

의아하겠지만, Controller class는 여러 함수를 사용하지만 사실 Service만을 바라보고 있는 상태다. 만약 Service에 있는 모든 함수들이 Controller class 내부에서 만들어졌다면 Contoller class는 그 모든 함수들에 의존성을 가지고 있다. 그렇기 때문에 오히려 의존성은 줄어든다.

2. 재사용성이 높은 코드가 된다.

Controller class 내부에서 함수를 선언했다면 다른 class에서는 사용할 수 없게 되었을 것이다. provider로 등록된 Service에서 함수를 선언해야지만이 공급자로써의 역할을 할 수 있음을 잊지 말것.

3. 테스트하기 좋은 코드가 된다.

Controller와 Service의 테스트를 나눠서 할 수 있다. (유닛테스트에 훨씬 효과적일 것임)

4. 가독성이 높아진다.

기능들을 별도로 분리하게 되었기 때문에 자연스럽게 가독성이 높아진다.

3. 기본구조

Nest는 CLI Commend를 통해 새로운 Project를 구성할 수 있는데 기본구조를 이루고 있는 요소들에 대해 정리한다.

1. Module (모듈)

Nest에서 모듈은 @Module 데코레이터로 주석이 달린 class를 말한다. @Module 데코레이터는 Nest가 애플리케이션 구조를 구성하는데 필요한 메타 데이터를 제공하는 역할을 부여할 수 있다.
모듈은 밀접하게 관련된 기능의 집합으로 구성요소를 구성하는 효과적인 방법 중에 하나이며, Express에서 Router, Controller를 나누듯이 관련된 기능끼리 나눠 그 안에서 로직을 수행한다.

2. Contoller (컨트롤러)

Express에서의 Router와 Controller의 기능을 합쳤다고 생각하면 쉽다. 요청에 대한 분리처리를 하고, 분기처리된 요청을 처리하고, 클라이언트에 응답을 반환하는 역할.

3. Service (서비스)

주로 데이터베이스 관련된 로직을 수행한다. Express에서 Service와 Dao라고 생각하면 편할 것 같다.
Dao는 추후 Repository Pattern에서 repository에서 수행한다. 보통 Injectable 데코레이터를 통해 해당 로직들을 다른 위치에서 사용할 수 있게 만들어준다.

4. 요청에 대한 응답을 수행하는 흐름

Express를 공부할 때도 마찬가지였지만, 특정 구조를 가지고 있는 API를 개발할 때 데이터의 흐름에 따른 실행순서와 로직을 아는 것이 굉장히 중요했다. 보통 API는 클라이언트의 요청을 받아 그 요청에 맞는 응답을 해주는 것이 그 기능이라 할 수 있는데 그런 관점에서 요청에 대한 응답을 수행하는 흐름을 알지 못하는 것은 제대로 그 구조를 사용하지 않는다고 판단할 수 있다.

상단에서 설명한 Provider에도 살짝 설명한 부분이 있으니 같이 보면 훨씬 이해하기 편하다.

1. main.ts

NestCLI를 통해 새로운 Project를 만들면 많은 파일 중 main.ts가 있다. 이 파일은 Express 구조에서 index.js정도로 볼 수 있다.

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

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

위에서 부터 보면 bootstrap이라는 함수를 선언하고, 안에 내용은 NestFactory를 통해 서버를 create하는데 AppModule 을 넣어주어 요청이 들어왔을 때 AppModule을 실행하게 한다.
그리고 그렇게 만들어진 app을 가지고 3000번 포트에서 요청 대기를 할 수 있도록 만들어 주고 함수 밖에서 bootstrap을 실행시켰다.

쉽게는 AppModule을 이용해 서버를 열고, 그 서버를 3000번 포트에서 요청대기 할 수 있도록 한 것

2. App.module.ts

마찬가지로 새로운 Project를 만들면 생기는 파일로써 모든 Module의 root가 된다. 더 쉽게는, 모든 Module은 app.module에 등록되어 사용된다. 등록하지 않은 module은 실행될 수 없다.

import { Module } from '@nestjs/common';
import { BoardsModule } from './boards/boards.module';

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

@Module 데코레이터로 AppModule을 Module의 역할을 할 수 있게 속성을 부여해준다.
그 안에는 다른 module을 import할 수 있는데, 저 배열안에 module을 등록해줘야만 사용할 수 있다.
그리고 같은 역할을 하는 Controller와 Service를 등록해주어 Controller에서 요청을 받고, Service는 provider의 역할을 할 수 있게 한다.

3. app.controller.ts

app.module.ts에 controller로써 등록되었기 때문에 요청을 받을 수 있고 종속성 주입을 통해 주입된 app.service.ts의 함수들을 사용할 수 있고, 그 결과값을 return하여 클라이언트에 반환할 수 있다.

@Controller()
class Controller {
  constructor(private service : Service) // 종속성 주입
	@Get()
	function() {
    return this.service.function(); // Hello World!
    }
}
profile
개발자 키우기 Lv1

0개의 댓글