본문 바로가기

Back-End/Nest.js

멋사 2주차 JS decorator, MVC 패턴, Nest.js 찍먹

1.  Decorator

- Nest는 Decorator를 적극 활용한다.

- 이를 잘 활용하면 횡단 관심사(cross-cutting concern)를 분리하여 관점 지향 프로그래밍(Aspect Oriented Programming AOP)을 적용한 코드를 작성할 수 있다.

횡단 관심사 : 모든 핵심관심사항에 공통적으로 들어가는 코드/로직 

https://choi3950.tistory.com/32

 

-우선 Decorator를 사용하기 위해 tsconfig.json파일에서 다음 옵션을 true로 지정해 주어야 한다.

tsconfig.json

{
	"compilerOptions": {
    	...
    	"experimentalDecorators":true
    	...
    }
}

- Decorator는 @expression과 같은  형식으로 사용된다.

- 여기서 expression은 decorating된 선언(decorator가 선언되는 클래스, 메서드)에 대한 정보와 함께 런타임에 호출되는 함수여야 한다.

 

- 다음 코드를 보면 test 메서드의 데코레이터 팩토리(데코레이터 함수를 감싸는 wrapper 함수)로 deco를 선언했다.

- deco 함수를 데코레이터로 사용하기 위해서는 함수의 인자를 다음과 같이 정의해야 한다. (설명은 나중에)

fucntion deco(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('데커레이터 평가됨');
}

class TestClass {
    @deco
    test() {
        console.log('함수 호출됨')
    }
}

const t = new TestClass();
t.test();

- 이제 클래스를 생성하고 test 메소드를 호출하면 다음과 같은 결과가 출력된다.

데커레이터가 평가됨
함수 호출됨

- 만약 decorator에 인수를 넘겨서 decorator의 동작을 변경하고 싶다면 decorator를 리턴하는 함수를 만들어 주면 된다.

 

fucntion deco(value: string) {
    console.log('데커레이터 평가됨');
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(value);
    }
}

class TestClass {
    @deco('HELLO')
    test() {
        console.log('함수 호출됨')
    }
}

const t = new TestClass();
t.test();
데커레이터가 평가됨
HELLO
함수 호출됨

 

Decorator 합성

- 여려 개의 Decorator를 사용한다면 수학의 함수 합성과 같이 Decorator를 합성하게 된다.

- 다음 Decorator 선언의 합성 결과는 수학적으로는 f(g(x))와 같다.

@f
@g
test()

- 여러 Decorator를 사용할 때는 다음과 같은 단계가 수행된다.

1. 각 Decorator의 표현은 위에서 아래로 평가 된다.
2. 그런 다음 결과는 아래에서 위로 함수로 호출 된다.
function first() {
    console.log("첫번째 함수가 평가되었습니다.");
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("첫번째 함수가 호출되었습니다.");
    };
}

function second() {
    console.log("두번째 함수가 평가되었습니다.");
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("두번째 함수가 호출되었습니다.");
    };
}

class TestClass {
    @first()
    @second()
    test() {
        console.log('test 함수 호출됨');
    }
}

const t = new TestClass();
t.test();
첫번째 함수가 평가되었습니다.
두번째 함수가 평가되었습니다.
두번째 함수가 호출되었습니다.
첫번째 함수가 호출되었습니다.
test 함수 호출됨

 

타입스크립트가 지원하는 5가지 Decorator 

 

1) 클래스 Decorator

- 클래스Decorator는 말 그대로 클래스 앞에 선언된다.

- 클래스 Decorator는 클래스의 생성자에 적용되어 클래스의 정의를 읽거나 수정할 수 있다.

- 선언 파일과 선언 클래스 내에서는 사용할 수 없다.

 

//클래스 Decorator 팩터리이다. 생성자 타입을 상속받는 제네릭 타입 T를 가지는 생성자를 팩터리 메서드의 인수로 전달하고 있음.
function reportableClassDecorator<T extends { new (...args: any[]) }>(constructor: T)
{
    //클래스 Decorator 생성자를 리턴하는 함수여야 함.
    return class extends constructor {
        //해당 클래스 Decorator 적용되는 클래스에 새로운 reportingURL이라는 새로운 속성을 추가한다.
        reportingURL = 'http://www.example.com';
    };
}

@reportableClassDecorator
class BugReport {
  type = 'report';
  title = 'test';
  constructor(t: string) {
    this.title = t;
  }
}

const bug = new BugReport();
console.log(bug);
{type = 'report', title = 'test', reportingURL = 'http://www.example.com'}

- 결과를 보면 BugReport 클래스에 선언되지 않았던 새로운 속성이 추가되었다.

하지만 클래스의 타입이 변경되는것은 아님!
bug.reportingURL과 같이 직접 사용할 수 없다는 의미

 

2) 메서드 Decorator

- 메서드 Decorator는 메서드 바로 앞에 선언된다.

- 메서드의 속성 설명자에 적용되고, 메서드의 정의를 읽거나 수정할 수 있다.

 

-위에서 deco 메서드 Decorator에서 봤던 것처럼 메서드 Decorator는 다음 세 개의 인수를 가진다. 

1. target: 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
2. propertykey: 멤버의 이름
3. description: 멤버의 속성, PropertyDescriptor 타입을 가진다.

3) 접근자 Decorator

- 접근자 Decorator는 말 그대로 접근자 앞에 선언한다.

- 접근자의 속성 설명자에 적용되고 접근자의 정의를 읽거나 수정할 수 있다.

속성 설명자 : 객체의 속성들은 그 자체로 객체 내부의 정보와 기능을 표현한다. 이 객체가 읽기 전용인지, 나열될 수 있는지 등의 정보를 의미한다. Js에서는 이러한 속성의 세부적인 성질을 직접 설정하거나 조회할 수 있는 방법을 제공하는데 이때 사용되는 특수한 객체가 속성 설명자 이다. 

- 다음 예시는 특정 맴버의 속성 설명자 값에 접근하는 코드이다.

function Enumerable(enumerable: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = enumerable;
  }
}

class Person {
  // name은 외부에서 접근이 불가능한 Private 멤버.
  constructor(private name: string) {}

  // 게터 getName 함수는 열거가 가능하도록 한다.
  @Enumerable(true)
  get getName() {
    return this.name
  }
  //세터 setName함수는 열거가 불가능하도록 한다.
  @Enumerable(false)
  set setName(name: string) {
    this.name = name;
  }
}

const person = new Person('KIM');
for(let key in person) {
  console.log(`${key}: ${person[key]}`);
}

- 결과를 출력하면 getName은 출력되지만 setName은 열거하지 못하게 되었기 때문에 for문에서 Key로 받을 수 없다.

name: KIM
getName: KIM

4) 속성 Decorator 

- 속성 Decorator는 클래스의 속성 바로 앞에 선언된다.

- 클래스의 속성이라 함은 클래스에 바인드 되는 모든 변수가 클래스 속성이다. 

- 앞어서 다룬 메서드, 접근 제어자와 달리 속성 데코레이터 함수는 전달받는 인자가 총 2가지 이다.

1. target : 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토 타입
2. prop : 멤버의 이름 

- 이친구는 나중에 추가적으로 좀 더 알아봐야 할것같습니다...

 

5) 매개변수 Decorator

- 매개변수 Decorator는 생성자 또는 메서드의 매개변수에 선언되어 적용된다.

1. target
2. prop
3. parameter_index : 매개변수가 함수에서 몇번째 위치에 선언되었는지를 나타내는 인덱스.

- 매개변수 Decorator는 단독으로 사용하는것 보다 Decorator와 함께 사용할 때 유용하게 쓰인다.

 

2.  MVC 패턴

- MVC 패턴이란 Model-View-Controller의 약자로 애플리케이션을 세 가지 역할로 구분한 개발 방법론이다.

출처:XESCHOOL

등장배경

- 우리가 간단한 게시판 사이트를 만드는 경우에는 한 파일 안에서 DB에 접속하고, 사용자에게 보여질 HTML 부분을 작성하면 되지만

- 네이버 사이트와 같은 대규모 사이트를 만드는 경우에는 위와 같은 방식으로 만들고 운영하기에는 힘들것이다. 

- 요소와 기능들이 많아지고, 구조가 이것저것 얽힐수록 코드도 무진장 길어지고 난해해 질것이다. 

- 프로젝트가 거대해지고 복잡해질때는 특정 기준으로 분리, 모듈화해서 접근하곤 하는데 (국가 기관을 입법, 사법, 행정으로 나누듯이)

- 웹 사이트에서는 Model, View, Controller. 즉 MVC란 접근법이 많이 사용된다. 

 

1) MODEL

- 애플리케이션의 정보, 데이터를 나타낸다. 

- DB, 처음 정의하는 상수, 초기화, 값, 변수 등을 뜻함.

- 비즈니스 로직을 처리한 후 모델의 변경사항을 컨트롤러와 뷰에 전달한다.

비즈니스 로직 : 회원가입 기능을 예시로 들었을때 중복 아이디가 있는지 없는지를 검사하기 위한 일련의 과정들비즈니스 로직, 검사 결과를 유저에게 단순히 텍스트나 다이얼 로그로 알려주는 것View 영역(Presentation)이라고 한다. 

모델은 다음과 같은 규칙을 가지고 있다.

1 . 사용자가 편집하길 원하는 모든 데이터를 가지고 있어야 한다. 
2. 뷰나 컨트롤러에 대해 어떤 정보도 알지 말아야 한다. -> 데이터 변경이 일어났을때 모델에서 화면 UI를 직접 조정해서 수정할 수 있도록 뷰를 참조하는 내부 속성 값을 가지면 안 된다. 
3. 변경이 일어나면, 변경 통지에 대한 처리 방법을 구현해야만 한다. -> 데이터 변경 발생시 누군가(뷰/데이터 변경으로 인한 화면 변경, 컨트롤러/데이터 변경으로 인한 업데이터 로직)에게 전달해야 함.

 

2) VIEW

- 사용자에게 보여지는 부분, 즉 유저 인터페이스(User interface)를 의미한다.

- 쉽게 말해 데이터의 및 객체의 입력, 그리고 출력을 담당한다. -> 데이터를 기반으로 사용자들이 볼 수 있는 화면.

 

뷰에서는 다음과 같은 규칙들이 있다.

1. 모델이 가지고 있는 정보를 따로 저장해서는 아니된다. -> 뷰의 역할은 단순히 변경된 데이터내용을 화면에 표시하는 것.
2. 모델이나 컨트롤러와 같이 다른 구성 요소들을 몰라야 한다.  
3. 변경이 일어나면 변경 통지에 대한 처리방법을 구현해야만 한다. -> 화면에서 사용자가 화면에 표시된 내용을 변경하게 될 경우 변경된 내용을 모델에게 통지할 로직이 필요하다.

 

3) CONTROLLER

- 모델과 뷰 사이를 이어주는 다리 역할을 한다.

- 위 규칙들을 보면 모델이나 뷰는 서로의 존재를 몰라야 한다.

- 변경사항을 외부로 알리고 수신하는 방법만 존재한다. 

- 컨트롤러는 이를 중재하기 위해 모델과 뷰에 대해 알고 있어야 한다.

- 모델이나 뷰로부터 변경 내용을 통지 받으면 이를 각 구성 요소에게 통지해야 한다.

- 사용자가 어플리케이션을 조작하여 발생하는 변경 이벤트들을 처리하는 역할을 수행한다.

 

컨트롤러는 다음과 같은 규칙을 가지고 있다.

1. 모델이나 뷰에 대해서 알고 있어야 한다.
2. 모델이나 뷰의 변경을 모니터링 해야 한다.

 

그래서 왜 씀? 

- 유지보수의 편리성 때문.

- 최초 설계를 꼼꼼하게 진행한 시스템이라도 유지보수가 발생하기 시작하면 각 기능간의 결합도(coupling)가 높아지는 경우가 발생한다.

- 결합도가 높아진 시스템은 유지보수 작업 시 다른 비즈니스 로직에 영향을 미치게 되므로 사소한 변경이 의도치 않은 버그를 유발할 수 있다.

-MVC 패턴을 가진 각 시스템의 각 컴포넌트는 자신이 맡은 역할만 수행 후, 다른 컴포넌트로 결과만 넘겨면 되기 때문에 결합도를 낮출 수 있다.

- 하지만 복잡한 대규모 프로그램의 경우 다수와 뷰와 모델이 컨트롤러를 통해 연결되기 때문에 컨트롤러가 불필요하게 커지는 현상이 발생한다.

- 복잡한 화면을 구성하는 경우에도 동일한 현상이 발생하는데 이를 'Massive-View-Controller'라고 한다.

https://www.infoq.com/news/2014/05/facebook-mvc-flux/

https://junhyunny.github.io/information/design-pattern/mvc-pattern/

 

MVC(Model, View, Controller) Pattern

<br /><br />

junhyunny.github.io

 

3.  Nest.js 찍먹

- NestJS는 Node.js에 기반을 둔 웹 API 프레임워크로서 Express 또는 Fastify 프레임워크를 래핑하여 동작한다. (Fastify가 속도가 빠르다는 장점이 있지만, Express가 가장 널리 사용되고 있고, 수많은 미들웨어가 NestJS와 호환되기 때문.)

 

Node.js 쓰면 되지 왜 Nest.js 씀?

- Node.js는 손쉽게 사용할 수 있고 뛰어난 확장성을 가지고 있다.

- 하지만 과도한 유연함으로 인해 소프트웨어의 품질이 일정하지 않고, 알맞은 라이브러리를 찾기 위해 사용자가 많은 시간을 할애해야 한다.

- 반면 Nest는 DB, 객체 관계 매핑(object-relational mapping, ORM), 설정, 유효성 검사 등 수많은 기능들을 기본 제공하면서 필요한 라이브러리를 쉽게 설치하여 기능을 확장할 수 있는 Node의 장점을 그대로 가지고 있다.

 

 

Express vs Nest

- Nest는 Express를 기본으로 채택하고 그 위에 여러 기능을 미리 구현해 놓은 것이다.

- Express는 가볍게 테스트용 서버를 띄우는 용도로 사용하면 좋다. 

- 아이디어를 빠르게 검증하는데 좋지만, 단순하고 자유도가 높은 만큼 자기에 맞는 라이브러리를 찾기 위해 직접 찾아야 한다.

- Nest는 미들웨어, IoC, CQRS등 이미 많은 기능을 프레임 워크 자체에 포함한다.

- 원하는 기능이 있다면 다른 라이브러리를 적용해서 사용하면 된다.

 

Nest 디핑 소스 만들기

- Nest를 설치해 봅시다. 

 

1) Node.js 설치

- Nest는 Node를 기반으로 한다. 따라서 Node도 설치해야 하는데, 

노드는 알아서 깔아옵시다.

2) Nest 프로젝트 생성

- 우선 Nest.js 서버를 구성하기 위해 @nest/cli를 설치해보자.

$ npm i -g @nestjs/cli
 i : install 의 약어
-g : 컴퓨터의 글로벌 환경에 설치하겠다는 의미. 모든 dir에서 해당 패키지를 실행할 수 있음. (글로벌 환경에서 패키지가 설치되는 경로로는 npm root -g 명렬어로 확인할 수 있다.)

- 설치가 끝났으면 프로젝트 생성을 해보자.

$ nest new proj-name

-생성이 끝난다면 보일러플레이트 코드가 생성될 것이다. 

 

3) 서버를 실행

- 서버를 실행하기 위해 프로젝트가 설치된 경로로 이동한 후 다음 명령어를 입력하여 실행시켜 보자.

$ yarn install or npm install
$ yarn run start:dev
운영이 아닌개발 단계 에서는 :dev명령어를 추가로 붙여 이용하는것이 좋다 .( 소스 코드 변경을 감지하여 코드 저장할때마다 서버를 다시 구동할 수 있다.)

 

-브라우저 로컬 서버(http://localhost:3000/)에 접속하여 잘 동작하는지 확인해 보자.

 

'Back-End > Nest.js' 카테고리의 다른 글

내장 로거  (0) 2023.02.13
파이프와 유효성 검사  (0) 2023.02.01
동적 모듈을 활용한 환경 변수 구성  (0) 2023.01.25
멋사 스터디 2주차 HTTP, RESTFUL,웹 프레임워크  (0) 2023.01.10
TypeScript  (0) 2023.01.02