본문 바로가기
Project

SpaceCloud 백엔드 개발

by yongmin.Lee 2020. 10. 25.

Description  

  • 스페이스클라우드(SpaceCloud)는 스터디룸, 회의실, 연습실, 워크샵, 코워킹스페이스등 사용자에게 최적화된 공간에 대해 예약, 결제, 검색, 마케팅을 종합적으로 지원하는 서비스입니다.
  • 팀원: 프론트개발 4명, 백엔드개발 2명
  • 개발기간: 2020.08.31 ~ 2020.09.11
  • Tech stack : Node.js, Express, Mysql, Sequelize, AWS EC2, AWS RDS
  • Github : github.com/eymin1259/11-PrettyCloud-backend

Demo Video

What I did

  • 정규화를 통한 데이터 모델링
  • 프로젝트 구조 설계
  • Sequelize ORM을 통한 API 구현
  • Pagination을 적용하여 공간 리스트 조회 API 구현
  • 공간 검색 API 구현
  • 공간상세정보 페이지 API 구현
  • AWS를 이용한 클라우드 환경의 서버 구현

Modeling

What I learned

1. Project Architecture 설계

 

프로젝트를 생성하면 디렉토리 구조가 정해여 있는 Django와 달리 Node.js를 이용하여 백엔드 서버를 구현하는 경우 프로젝트의 아키텍쳐 설계부터 시작하여야 했다. 따라서 적용 가능한 아키텍쳐 패턴들에 대해 공부하게 되었고 하나의 애플리케이션을 Model , View , Controller 세 가지의 역할로 구분하여 구현하는 방식인 MVC 패턴을 적용하여 SpaceCloud 백엔드 프로젝트의 구조를 잡기로 결정하여 다음과 같이 설계를 하였다.

 

디렉토리 구조

 

이렇게 애플리케이션을 역활별로 모듈화를 하면 사용자 인터페이스로부터 비즈니스 로직을 분리하고, 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 구현 및 유지보수할 수 있다는 장점이 있다.

 

(좌) models/Users.js (중) models/index.js (우) models/index.js

 

Models에서 각각의 schema 마다 column과 옵션설정을 정의하는 js파일을 작성한다. 그리고 index.js에서 해당 schema 정의 파일들을 sequelize를 이용하여 데이터베이스에 반영하고 schema들 간의 관계를 설정해준다.

 

controllers/spaceDetail.js (상세정보조회)

 

User가 접근 한 URL에 따라서 사용자의 reqeust를 파악한 후에 그 요청에 맞는 데이터를 앞서 정의한 Models로부터 주고 받는 Controller를 정의한다. 예를들어 User가 특정 id값을 가지는 어떤 공간에 대해 상세정보를 조회하고 싶은 경우 상세정보조회 endpoint로 id값을 url parameter와 함께 request를 보내면, models/Space.js에 접근하여 해당 id값을 가지는 공간이 있는 조회 하여 상세정보를 response로 보내 주거나 없는 경우 에러메세지를 전달한다.

 

2. Node.js Asynchrony

 

CPU bound 보다 I/O bound가 높은 수 많은 client request가 발생하는 경우, 서버에서는 bottleneck 현상이 발생한다. 이 경우 다른 request들이 처리되지 못하고 계속 대기하는 문제가 발생하는데 기존 중앙 서버에서는 이런 문제를 해결하기 위해 Multi-thread를 이용하여 병렬처리를 가능하도록 구현하였다. 하지만 multi-thread의 경우 request가 많아질수록 thread가 더욱 많이 생성되므로 제한된 자원을 가지고 있는 서버 환경에서 자원을 더 많이 소모한다는 단점이 있다. 또한 각 thread가 공유자원 접근 시 동기화를 제대로 구현하지 않으면 원하지 않는 결과를 얻을 수 있다는 위험도 존재한다.

 

Node.js는 bottleneck 문제를 Asynchrony로 해결한다.

 

 

Event-driven

클라이언트의 요청을 비동기로 처리하기 위하여 이벤트가 발생하며 서버 내부에 메시지 형태로 Event Loop에게 전달된다. Event Loop가 클라이언트 요청을 처리하는 동안 제어권은 다음 요청으로 넘어가고 처리가 완료되면 Callback을 호출하여 처리완료를 호출측에 알린다.

Non-Blocking

Node.js의 비동기 방식에서는 하나의 요청 처리가 완료되기 전에 제어권을 다음 요청으로 넘긴다. 따라서 IO 처리인 경우 Blocking 되지 않고 다음 요청을 처리한다. 이러한 특성 덕분에 이벤트에 의해 처리해야 할 단위 작업이 아주 짧은 시간 안에 처리되는 경우, Node.js의 고성능의 병렬처리 기능을 극대화 할 수 있다.

Single-Thread

이벤트를 처리하는 Event Loop는 Single-Thread 로 이루어져 있어서, 호출 측에는 client 요청이 비동기로 처리되지만 Event Loop에서는 모든 client 요청 처리가 하나의 Thread 안에서 처리된다. 따라서 처리작업 자체가 오래 걸린다면 전체 서버 처리에 영향을 준다.

 

한편 Node 애플리케이션에서 로직을 비동기로 하지 않고 동기방식으로 구성한다면 예상치 못한 결과를 얻을 수 있다.

 

서버에서 받은 response

 

위와 같이 클라이언트는 서버로 원하는 데이터를 받지 못하고 pending 상태의 Promise 객체를 response로 받게 된다. 이런 문제가 발생하는 이유는 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 비동기의 특성을 이해하지 못해 callback function이 호출되기 전에 해당 코드가 실행완료 되었다고 착각하여 완료되지 않은 비동기 처리 로직의 결과값을 클라이언트에게 response로 전달했기 때문이다.

 

따라서 Node.js로 개발을 할때는 비동기적 프로그래밍 사고를 연습하는 것이 중요하며 동기적으로 로직을 처리해야 하는 경우, 다음과 같은 방법으로 비동기를 순차처리해야 한다.

1. Callback function 

2. await/async

 

callback

Callback function을 중첩하여 이용하여 순서가 보장되게함으로써 동기적으로 처리할 수 있다. 하지만 이 경우 중첩의 깊이가 깊어지면 콜백 안에 콜백을 계속 물어 가독성이 떨어지고 로직을 변경하기도 어려운 Callbeck hell 문제가 발생한다. 이 문제를 해결하기 위해 탄생한 것이 Promise와 async/await 이다.

 

Promise

Promise는 자바스크립트 비동기 처리에 사용되는 객체로, Pending (대기 : 비동기 처리 로직이 아직 완료되지 않은 상태), Fulfilled (이행 : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태),  Rejected (실패 : 비동기 처리가 실패하거나 오류가 발생한 상태)의 3가지 상태를 가진다.

프로미스는 new Promise로 생성하여 resolve와 reject를 매개변수로 갖는 callback function을 받는데 이때 프로미스는 Pending 상태가 된다. 여기서 콜백 함수의 인자 resolve를 실행하면 프로밋는 이행(Fulfilled) 상태가 되고 then()을 이용하여 처리 결과 값을 받을 수 있다. 만약 콜백 함수의 인자 reject를 호출하면 실패(Rejected) 상태가 되어 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있습니다.

 

async/await을 이용한 로직

프로미스로 callbeck hell을 해결하여 코드의 깊이가 깊어지지는 않지만 .then()과 .catch()가 계속 반복되므로 여전히 코드가 길다. 이 문제는 async/await으로 해결할 수 있다. 함수 선언부를 일반 함수 대신 async 키워드를 붙인 function으로 바꿔주고 프로미스 앞에 await을 붙이면 해당 함수는 프로미스가 resolve 될 때까지 기다리고 다음 로직으로 넘어간다. 프로미스가 reject되어 에러를 처리해야 하는 경우는 try/catch로 로직을 감싸 cathc문에서 에러를 처리할 수 있다.

 

Node.js 의 올바른 사용

Node.js 는 Google Chrome V8 엔진 기반으로 동작하며 내부의 Event Loop는 Single-Thread 기반에서 비동기 메시지를 처리하고. 이러한 Event Loop는 고성능의 병렬처리를 보장하도록 설계되어 있다. 따라서 짧은 단위의 잦은 I/O 작업이나 실시간 데이터 처리 작업에서 면 Node.js의 고성능의 장점을 극대화 할 수 있다. 하지만, 이미지처리, 대용량 파일을 처리 같이 CPU를 많이 소모하는 작업이라면 Node.js 는 적합하지 않다.