Nest의 아키텍처에 대해서

·2022년 6월 26일
1

SKILL

목록 보기
6/16
post-thumbnail

Nest에 관련된 글을 쓰다보니 아키텍처 그 자체에 대해서 설명을 한번 해놓는게 좋다고 생각이 들었다.

한번 적어놓으면 계속 링크를 걸어놓을 수도 있고, 좀 정확한 이해를 하기 위하여? 적어본다.

Nest's Abstraction Layer(추상화계층)


이미지 출처 NestJS독학 - 소개

조금 더 확실하게 보기 위하여 팀프로젝트에 썼던 폴더구조를 가져와봤다.

이 구조는 MVC 패턴과도 비슷한 구조로 되어있는데, 실제로 Nest에서는 MVC 패턴도 지원한다.

여기서도 로직이 딱 분리가 되어있는데 바로 3가지로 구분을 할 수 있다.

  • Controller & Resolver
    • 백엔드에서 흔히 생각하는 그 컨트롤러가 맞으며 비즈니스 로직을 가기 위한 관문이라고 생각할 수 있다.
    • Resolver 같은 경우에는 GraphQL을 사용하기 위한 Controller라고 생각하면 된다.
  • Service
    • 실질적인 비즈니스 로직들이 분리되어있는 공간이다.
  • Module
    • 만들어진 Controller, Resolver, Service등을 한개로 합치는 역할을 한다.(캡슐화)
    • 또한 추가적으로 필요한 것을 import 할 수 있다.

즉 Nest는 상단의 사진처럼 최상단에는 main.ts가 존재하고, 그 아래에는 app.module.ts이 존재하는데
app.module 속에 수많은 Module을 장착하는 것으로 아키텍처가 짜여져있다.

그리고 Nest 공식 문서에서 장점을 모은 아키텍처를 만들기 위해서 적용된 다양한 패턴들이 존재한다.

  • Nest에서 제공하는 데코레이터를 사용할 경우 자체적으로 인스턴스를 생성해준다.
    • Nest에서는 불필요한 메모리 사용을 방지하기 위하여 싱글톤 패턴을 적용시킬 수 있는 데코레이터를 제공한다.
    • @Resovler() @Controller() @Injectable()
    • 해당하는 것을 모듈의 프로바이더에 넣는 것으로 싱글톤이 적용된다.
  • IoC(제어의 역전)을 Nest에서는 providers에 클래스를 넣는 것으로 사용할 수 있다.
    • 싱글톤 패턴의 제일 큰 문제는 강한 결합이였다. 하지만 이것을 해결하기 위하여 Nest가 IoC를 지원한다.
    • Nest가 bootstrap을 할 때 Module에 등록된 의존성들을 검사하여 프로바이더 인스턴스를 생성한다.
    • 이것이 애플리케이션 전역에 단일 인스턴스로 공유가 되서 빠른 속도로 처리가 가능하다.
  • 생성자를 활용하여 의존성을 부여하는 것(DI)으로 손쉽게 느슨한 결합을 사용할 수 있다.
    • 사용하고 싶은 클래스에 생성자로 호출하기만 하면 바로 사용을 할 수 있게 되어있다.
  • 비슷한 로직이 모여있는 것을 한개의 모듈에 담아 캡슐화를 시킬 수 있도록 지원한다.
    • 한개의 모듈을 만들어서 서로 import가 가능하도록 구현이 되어있다.
    • 이것으로 기능간의 명확한 경계를 설정하는 것으로 복잡성과 확장성, 테스트 가능성을 제공한다.

어떤게 특징인지 잘 알 것 같다, 하지만 한번 더 생각을 해봐야하는 것이 있다.

저것들은 어떻게 적용이 되는지 알고싶은데요?

이제부터 위에 적혀있는 것들이 어떠한 원리로 적용이 되고 있는 것인지

Nest라는 프레임워크는 어떻게 작동을 하는지 알아보려고 한다.

데코레이터 패턴

데코레이터 패턴은 사실상 Nest의 근간이라고 생각할 수 있다.
정말 모조리 다 달려있기 때문이다(....)

데코레이터기존 객체의 동작을 동적으로 증대시킬 수 있는 디자인 패턴으로
동작이 클래스의 모든 객체에 적용되지 않고, 명시적으로 데코레이팅된 인스턴스에만 적용된다는 특징이 있다.

다시 한번 강조하면 데코레이터 패턴에서 중요한 것은 기존 객체를 변화시키는 것이 아니라,
확장을 할 수 있도록 해주는 것이다.

Nest에서는 reflect-metadata라이브러리를 사용하여 메타데이터를 생성하는 것으로 데코레이터를 생성하여 사용한다.


새로운 메타데이터를 생성하는 Reflect.defineMetadata를 볼 수 있다.

NestJS 전략 & 데코레이터 패턴에 대해서

NestJS 데코레이터 공식 문서

싱글톤 패턴

싱글톤 패턴은 객체 지향 프로그래밍에서 무조건 보이는 디자인 패턴이다.

해당 패턴의 핵심은 바로 클래스의 인스턴스는 1개만 존재한다. 라는 것이다.

그리고 사용하는 이유는 아래와 같다.

  • 상태 정보의 공유
    • 한개의 객체를 사용하는 것으로 변경점이 있을 경우 한번에 적용된다.
  • 리소스 사용의 최적화
    • 싱글톤 패턴을 사용하지 않을 경우 new를 선언해야하는데, 이 경우에는 선언할 때 마다 메모리가 소모된다.
    • 1개만을 사용하기 때문에 메모리의 사용을 최소화할 수 있다.
  • 리소스에 대한 접근 동기화
    • 어디에서 접근을 하더라도, 같은 값을 확인할 수 있다.

Nest의 IoC 구현 방법

자바 스프링같은 경우에는 빈 컨테이너라는 것을 사용하여 IoC를 구현한다.

그렇다면 Nest는 어떠한 구조로 구현을 시키는지 알아보자.

Nest에서는 먼저 공급자라는 것을 정의를 해줘야한다.

  • @Resovler()
  • @Controller()
  • @Injectable()
  • @Inject()

이렇게 선언을 하여 클래스를 생성할 경우 Nest IoC 컨테이너에서 관리를 할 수 있게 된다.
커스텀으로 생성을 할 순 있지만, 가급적 권장하지 않는다. 라고 나와있다.

이렇게 만든 클래스를 관련이 되어있는 모듈에 providers에 넣는 것으로 손쉽게 적용을 할 수 있다.

ModuleMetadata interface의 설명

그렇다면 싱글톤 패턴의 적용은?

공식문서에는 이렇게 나와있다.

공급자의 단일 인스턴스는 전체 애플리케이션에서 공유됩니다. 인스턴스 수명은 애플리케이션 수명 주기에 직접 연결됩니다. 애플리케이션이 부트스트랩되면 모든 싱글톤 공급자가 인스턴스화됩니다. 기본적으로 싱글톤 범위가 사용됩니다.

애플리케이션이 부트스트랩이 될 경우 싱글톤 공급자가 인스턴스화가 된다......
이것도 돌아가는 로직을 봐야 좀 이해가 될 것 같아서 조금 찾아봤다.


nest-factory.js의 일부

전반적인 코드 그 자체를 이해할 능력까진 없어서 코드의 흐름만 한번 훑어보면

  1. 컨테이너에 존재하는 인스턴스를 불러온다.
  2. 메타데이터를 읽어온다.
  3. 종속성을 읽어온다.
  4. 컨테이너를 httpServer에 연결시킨다.
  5. 문제가 없으면 httpServer를 초기화한다. (?)
  6. 로그 한번 찍어준다.
  7. 비동기로 모듈을 다 읽어서 새로운 인스턴스와 종속성을 만든다.
  8. 다시 한번 종속성을 읽어서 애플리케이션 공급자들로 등록한다. (여기가 싱글톤 패턴 적용인 것 같다.)

이런식으로 진행이 되는 것 같다.

그래서 Nest를 실행시켰을 경우에 아래와 같은 로그를 볼 수 있다고 생각한다.


xxxModule dependencies initialized이 적혀있는 것을 볼 수 있다.

DI(의존성주입)을 하는 방법은?

이건 정말 간단하다. 생성자를 만들어서 현재 프로바이더에 등록되어있는 것을 넣는 것으로 적용된다.

어떻게 모듈에 담는 것으로 전부를 사용할 수 있을까? (캡슐화)

이것도 좀 궁금했었다, 프로젝트를 할 때는 일단 작업! 이다보니 세부적인 것을 못봤는데
이제야 살펴볼 수 있는 느낌이랄까?

그래서 한번 찾아봤는데, 워낙 많이 봐서 그런지 이번에는 바로 이해를 할 수 있었다.

Module.decorator.js

for in 구문을 볼 수 있는데 모듈에 들어가있는 것들이 객체의 형태여서 그렇다.

그런데 여기 코드만 보면 캡슐화가 진행된다라고 볼 수 없을 것 같다.
그저 반복문을 통하여 각기 다른 새로운 메타데이터를 만드는 것처럼 보이는데...

공식문서에는 프로바이더를 캡슐화한다고 적혀있다.

공식문서 모듈

상단의 nest-factory.js일부를 보면 dependenciesScanner.applyApplicationProviders(); 라는 것이 보인다.

이것을 따라서 들어가보면, 아래와 같은 코드가 존재한다.

프로바이더에 들어오는 것들을 각각 모델의 이름에 맞게 인스턴스화가 이루어지는 것을 볼 수 있다.

즉 이런식으로 각 모델별로 프로바이더를 캡슐화시킨다는 것을 확인할 수 있었다.


실제 상황으로 보는 Nest의 IoC와 DI

팀프로젝트에서 사용했던 코드를 보면서 프로바이더에 넣지 않을 경우 어떤 에러가 나는지 알아보겠다.

Auth 폴더구조

auth.Controller에는 유저의 IP가 다를 경우
API를 호출하는 것으로 화이트리스트가 업데이트되는 로직이 들어가있다.
그것을 사용하기 위하여 모듈의 컨트롤러에 넣는 것으로, 해당 로직을 사용할 수 있었다.


controllers에 AuthController가 있는 모습을 볼 수 있다.

그런데 어떠한 이유때문에 컨트롤러에 있는 메소드를 사용하고 싶어서 Resolver에 생성자로 넣게 되었다.


AuthResolver에 AuthController가 생성자에 들어가있다.

하지만 아래와 같은 에러가 뜨는 것을 확인할 수 있었다.

AuthResolver의 1번 인덱스에 있는 것의 종속성을 사용할 수 없다. 라는 빨간 에러가 보인다.

왜냐하면 현재 컨트롤러는 모델에 1:1로 연결이 되어있기 때문이다.
이것을 해결하기 위하여 AuthController를 Mudule의 providers에 넣어보자.

종속 에러를 이야기하는 빨간색 에러가 사라지고, 정상적으로 실행이 되는 모습을 볼 수 있다.


이렇게 Nest의 아키텍처는 어떤식으로 구현이 되어있고, 어떻게 적용이 되는지 살짝만 확인을 해봤다.
조금 더 정확한 내용에 대해서는 패키지파일을 조금 더 세밀하게 읽어봐야할 것 같은데

분량이 너무 많고 어떤 포인트를 읽어나가야 맞을지 감이 잘 안잡혀서(....) 시간이 정말 많이 걸리다보니
이정도로 마무리를 짓고 나중에 활용해가면서 해당 글을 수정해봐야할 것 같다.

아, 그리고 정말 공식문서는 모든 것을 가지고 있다.
노드모듈보다가 머리에 쥐나서 고민하고 있었는데 공식문서에 설명이 잘 되어있던 것도 있어서...
참 잘만들어놓은 것 같다.

=> https://docs.nestjs.com/

profile
물류 서비스 Backend Software Developer

0개의 댓글