Nestjs를 배워보자 2일차 - Service와 Controller (John ahn님 강의)

0

Nestjs

목록 보기
2/9

Nestjs 2강

본 포스팅은 유튜브 'john ahn'님 강의를 정리한 것입니다.
https://www.youtube.com/watch?v=3JminDpCJNE

모두 구독하세욧

1. Nestjs 모듈이란?

모듈은 @Module() 데코레이터로 주석이 달린 클래스이다.

Nestjs의 각 응용 어플리케이션에는 하나 이상의 모듈(루트 모듈)이 있다. 루트 모듈이 Nest가 사용하는 시작점이 된다.

모듈은 밀접하게 관련된 기능 집합으로 구성 요소를 구성하는 효과적인 방법을 제공한다. 즉, 기능별로 모듈을 구성하면 된다.
ex) 유저 모듈, 주문 모듈, 챗 모듈 ...

같은 기능에 해당하는 것들은 하나의 모듈 폴더 안에 넣어서 사용한다. (UserController, UserService , UserEntity 다 같은 User에 관한 기능이기 때문에 UserModule 안에 둔다.)

모듈은 기본적으로 싱글 톤이므로 여러 모듈간에 쉽게 공급자의 동일한 인스턴스를 공유할 수 있다.

즉, 다음과 같이 하나의 공통된 모듈 하나를 생성한 다음, 해당 shared module을 다양한 모듈에서 사용할 수 있다.

Nest에서는 모듈을 자동으로 생성할 수 있는 cli를 제공한다.

가령 Board 모듈을 생성한다하면

nest g module boards

g는 generate의 약자이고, module은 내가만들고 싶은 도해(schematic), boards 는 도해의 이름이다.

2. Board Module 생성하기

파일을 다지우고 하나하나 만들어보도록 하자
src에서 app.module.tsmain.ts를 남기고는 모두 지우고, 에러나는 부분들을 모두 삭제해주도록 하자

test/ 폴더 파일도 삭제해주도록 하자

그리고 모듈을 만들어보도록 하자

nest g module boards

다음과 같은 결과가 나온다면 성공한 것이다.

다음과 같이 boards 모듈이 생성되었다.

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

@Module({})
export class BoardsModule {}

다음의 파일에 들어가면 모듈이 생성되었음을 확인할 수 있다.

이제 여기에 controller, service, entity, repository 등등 요소를 넣어주면 된다.

그럼 요소들은 무엇인가??

3. controller

nestjs의 controller는 spring의 controller를 생각하면 된다.

Controller란??

컨트롤러는 들어오는 요청을 처리하고 클라이언트 응답을 반환한다.

http 요청이 왔을 때 알맞은 url에 해당하는 controller가 요청을 받는다. 그리고, 로직을 수행한 후에 응답을 보내는 것이다.

그럼 어떻게 요청을 받을까??

@Controller('/boards')
export class BoardController {

}

다음과 같이 @Controller('/boards')라는 데코레이터 문법을 클래스에 데코레이션하여 정의한다. 참고로 데코레이터 문법이 낯설다면 디자인 패턴 중에 데코레이터 패턴을 확인해보길 바란다.

데이코레이터에 적힌 인자는 controller에 의해서 처리되는 경로를 받는 것이다.

Handler란?

핸들러는 @Get , @Post, @Delete 등과 같은 데코레이터로 장식된 컨트롤러 클래스 내의 단순한 메서드이다.

@Controller('/boards')
export class BoardController {
    @Get()
    getAllBoards(){
        return;
    }

    @Get('/:id')
    getBoardById(){
        return;
    }
    @Post()
    createBoard(){
        return;
    }
}

다음과 같이 컨트롤러 데코레이터 클래스안에 함수에 데코레이터 문법으로 장식한 함수를 핸들러라고 한다.

/board url로 요청이 올 때의 get, post, patch,delete 요청을 달리 처리하고, 또한 경로나 파라미터로 지정할 수 있다.

즉 http 요청을 핸들하는 메서드를 일컫는 말이다.

4. BoardController 만들기

모듈 만들기와 동일하게 컨트롤러도 cli를 이용하여 생성해주면 된다.

nest g controller boards --no-spec

g : 생성
controller : 컨트롤러를 만들것이다.
boards : boards라는 모듈에 생성한다.
--no-spec는 테스트를 위한 소스 코드를 생성하지 말라는 것이다. 이는 우리가 처음 배우는 것이기 때문에 생성하지 않도록 한다.

controller cli를 사용하면, src에서 boards 모듈을 찾고 controller를 만들어준다. 그 다음, 해당 controller를 boards 모듈에 등록해주기 위해 소스를 변경한다.

그래서 boards.module.ts 에 가면 다음과 같이 코드가 변경되었을 것이다.

@Module({
  controllers: [BoardsController]
})

이는 cli를 통해 자동으로 코드를 변경한 것이다.

우리는 BoardController를 통해서 http 요청을 받고, 특정 로직으로 처리해줄 것이다. 그런데 controller 내부에서 자체적으로 처리하기 보다는 로직을 처리해주는 파일을 따로 관리하는 것이 유지 보수에 더 편하고 코드 관리에 유용하다.

그래서 service라는 것을 이용해보도록 하자

5. Service 생성하기

모듈, 컨트롤러 두 개 다 만들어봤으니 대충 감이 잡힐 것이다.

nest g service boards --no-spec

해당 nest cli 명령어를 입력하면 board의 service 파일이 생기게 된다.

boards/boards.service.ts 파일이 생긴다.

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

@Injectable()
export class BoardsService {}

이 생성된 파일 안에는 Injectable데코레이터가 있으며 Nestjs는 이 데코레이터를 이용해서 다른 컴포넌트에서 해당 서비스를 사용할 수 있게(injectable)만들어준다.

@Module({
  controllers: [BoardsController],
  providers: [BoardsService]
})

또한, 자동으로 boards.module.ts에 service를 등록시켜준다.

Service를 Controller에서 이용할 수 있도록 하는 방법

Controller에서 Service를 사용하기 위해서는 Di(Dependency injection)을 이용해서 Controller에서 Service를 받아와야 한다.

DI(의존성 주입)은 아래에 자세히 서술하였다.

의존성 주입을 하기위해서는 클래스의 constructor에 넣어주어야 한다.

boardsController의 코드를 다음과 같이 바꾸어 주도록 하자

@Controller('boards')
export class BoardsController {
    boardsService : BoardsService;

    constructor(boardService : BoardsService){
        this.boardsService = boardService;
    }
}

다음과 같이 생성자를 이용해서 service를 주입해주면 된다. 즉, boardService라는 프로퍼티를 생성자 함수의 boardService 파라미터 값으로 주입하는 것이다.

이후에는 typescript의 private 접근자를 이용해서 더 간단하게 바꿀 것이다.

@Controller('boards')
export class BoardsController {
    constructor(private boardService : BoardsService){}
}

자바스크립트에서는 private와 같은 접근 제한자가 없지만, typescript의 접근 제한자를 생성자 함수의 파라미터에 선언하면 자동으로 해당 priavet 변수를 프로퍼티로 넣어주기 때문에 해당 문법이 가능하다.

즉, 파라미터 boardService는 프로퍼티로 선언이 되기 때문에 저 한줄로 처리가 가능한 것이다.

의존성 주입

참고로 DI(Dependency Injection)는 스프링의 Di와 다를 바 없다.

DI를 의존성 주입이라고 하는데, 의존성은 주로 객체라고 생각하면 된다. 주입하고 싶은 것이라고 생각하면 되고 이를, 이는 원하는 타이밍에 어떤 객체에 주입하는 것이 의존성 주입이다.

그런데 그냥 constructor로 생성하면 되지 뭣하러 DI까지 할 필요가 있는가? 즉, 그냥 쌩짜로 new 로 생성해놓고 import 하는 방식도 있는데 DI를 하는 이유는 무엇일까?

https://stackoverflow.com/questions/130794/what-is-dependency-injection

위는 stackoverflow에서 설명한 내용이다.

간단히 답변들을 설명하자면, 내가 A라는 interface가 존재하는데, 이것이 실행 환경에서는 그냥 내가 만든 object를 new나 setter로 객체를 인스턴스에 직접 주입해도 된다.

그러나, 만약 이것이 Test 환경으로 바뀌거나 나중에 사업자가 변경되거나 확장된다고 하자

Test 환경에서는 사용하지 말아야할 Object들이 있다. 또는 내부 로직보다는 내가 원하는 대로 조작되기를 바라는 객체들이 있다. 이런 경우에는 해당 함수나 객체를 Mocking 한다고 한다.

이런 경우 DI(의존성 주입)은 아주 좋은 방식을 제공한다. 즉 내가 테스트 환경에서는 나의 Mock 객체를 A라는 인터페이스에 넣어주고, 실행 환경에서는 원래 넣으려는 객체를 넣어주면 되기 때문이다.

또한, 사업자들이나 버전이 다양해진 경우에도 큰 장점을 갖는다. 가령 일본 기업 사업자를 대상으로 File Reader 객체를 만들었는데, 영어를 읽는 File Reader를 넣을 순 없기 때문이다. 사업자가 변경되었다고 이를 하나하나 바꾸는 것 또한, 유지 보수에 안좋기 때문이다.

그래서 DI(의존성)는 interface와 구현체 간의 커플링을 줄이는 데에 있어 큰 장점이 있는 것이다. 직접 new 로 객체를 주입하는 방식에 비해 유연하고 덜 위험하기 때문이다.

이를 위해 많은 프레임 워크들은 DI를 알게 모르게 많이 지원해준다. 우리는 그걸 이용해서 어떤 타이밍에 또는 어떤 버전에 맞게 객체를 주입할 건지만 설정해주면 되는 것이다.

참고로, DI와 Singleton 패턴은 다른 패턴이다.

https://enterprisecraftsmanship.com/posts/singleton-vs-dependency-injection/

여기 좋은 예제가 있다.

싱글톤 패턴의 경우, 직접 인스턴스 하나를 생성해놓고 어떻게 접근하느냐에 따라 달라지지만, 의존성 주입의 경우에는 생성자나 setter로 인터페이스에 어떤 객체를 주입 받을 지 결정하는 것이다.

의존성 주입도 하나의 인스턴스를 만들어놓고 사용한다는 점에서 싱글톤 패턴과 비슷하지만, 의존성 주입은 인터페이스와 구현체를 분리시키기 위한 구조적 패턴인 것이고, 싱글톤은 하나의 인스턴스로 관리하겠다는 디자인 패턴인 것이다.

0개의 댓글