[정원사 프로젝트] 2. 개발 환경 구성과 백엔드 개발하기 (1)

정원사 프로젝트 회고, 개발 환경 구성과 백엔드 개발하기 : mongoose + github API

Posted by yoogomja on June 20, 2020

프로젝트 회고 글타래

1. 설계하기

본격적으로 프로젝트를 시작하기 전에 대략적인 서비스의 흐름을 결정할 필요가 있었다. 최대한 번거로운 기획 단계는 이번에는 건너뛰어 볼 요량으로 이런 저런 단계를 건너뛰었다. 난 항상 이 단계가 어려워서 그런지 저런 다짐에도 불구하고 꽤 오랜시간이 걸렸다.

우선 이 프로젝트의 기본 기능은 다음과 같았다.

특정 기간동안 등록된 사용자들의 깃허브 push 내역을 크롤링한 후 매일 출석 여부를 확인할 수 있으며, 그 출석 현황에 대한 통계 자료를 제공할 것

이 밖에도 몇가지 기능이 더 있었으나, 기본적인 골자는 위와 같았다. 저 기본 기능에 더해 내가 보완하려고 했던 내용은 다음과 같았다.

  1. 사용자가 손쉽게 프로젝트에 등록할 수 있을 것
  2. 프로젝트를 여러번 수행하고 그 수행 내역을 기록할 것
  3. 사용자의 행동들을 통해 다양한 통계를 작성할 수 있을 것

1.1. 주요 로직 설계

먼저 첫번째 항목을 이루기 위해서는 github REST API가 어떻게 동작하는 지 알아야 했다. 사용자의 푸시 정보를 어떻게 불러올 수 있는지, 그리고 그 푸시 정보가 어떻게 이루어져 있는지를 알아보는게 가장 핵심이었기 때문이다.

확인해보니, 저런 푸시 정보는 event라는 이름으로 관리되고 있었으며 그 종류가 PushEvent, CreateEvent등 다양한 종류를 가지고 있다는 걸 알게되었다. (이벤트에 대해서는 뒤에 다시 이야기한다.) 무엇보다 제일 중요한 점은 저 이벤트를 가져오기 위한 주소 형태가 /users/USER_NAME/events 형태, 즉, github 계정명만 있으면 된다는 것을 알게되었다. 그래서 첫번째 구상은 다음과 같았다.

  1. github 유저 이름을 홈페이지에서 등록
  2. 등록된 이름의 이벤트를 크롤링
  3. 크롤링한 정보를 분석해 통계내기

이런 흐름으로 작업을 할 예정이었다. 그런데 github REST API측에서 사용자 정보에 대해서도 꽤많은 정보들(아바타 이미지 주소, profile 정보 등)을 포함하고 있었기에 등록된 유저 이름을 기준으로 유저 정보 또한 크롤링하기로 하였다. 그 과정에서 등록된 유저 이름이 회원 정보 그자체로 사용되는 것이 더 활용도가 높다고 판단되어, 유저 이름을 홈페이지에서 등록하면 해당 유저의 정보를 크롤링해오고, 그 다음 유저의 이벤트를 크롤링해오는 방식으로 가닥을 잡았다.

첫 기획 당시에는 놓친 부분이 있었는데, 등록의 편의성에만 집중하다보니 생각보다 유저 고유의 권한이 필요한 기능이 많이 생길 수 있다는 걸 예측하지 못했다는 점이다. 이 부분을 놓쳐서 추후에 실제 사용할 동아리원들에게 사용성 테스트를 진행한 후 2주정도 추가 작업을 하게되었었다.

유저 등록과정을 가닥을 잡은 이후에는 이벤트를 좀 더 면밀히 살펴봤다. 이벤트는 위에 언급했듯이 PushEvent만이 존재하는 것이 아니었다. 그리고 해당 API에서 제공하는 정보는 약 90일 간의 정보를 한번에 30개씩 10페이지 정도의 분량으로 제공한다는 점이었다. 그 이벤트 내용들은 모두 PushEvent가 아닐 수도 있으며, 한번의 이벤트에 예상보다 적은 정보가 돌아온 다는 점이 유의사항이었다. PushEvent만 가져오는 것이 가능한지 알아봤으나 REST API에서는 불가한 것으로 내부적으로 결론을 내렸다. 그래서 이벤트 요청을 최대한 효율적으로 진행해야 했다.

(이 당시에는)도전 과제가 몇개나 얼마나 실행되고 있을지 모를것으로 예상되었기 때문에, 우선은 첫 요청에는 10페이지 분량의 이벤트를 사용자마다 모두 요청하기로 했다. 그리고 PushEvent타입의 이벤트만 데이터베이스에 등록하기로 하였으며, 이 과정에서 이벤트는 고유한 일련번호를 가지고 있으므로 데이터 베이스에서도 그 일련번호를 고유한 키로 사용하도록 그대로 유지했다. 그렇기에 새로운 이벤트를 크롤링한 후 데이터 베이스에 이미 해당 키를 가진 이벤트가 발견될 경우 이벤트 크롤링을 종료하는 방향으로 1차 목표를 정했다.

크롤링의 가닥을 잡고난 후, 이벤트의 내용을 살펴보니 이벤트 자체에도 꽤 많은 정보가 들어있었다. 이벤트의 타입 뿐만아니라, 누가 이벤트를 수행했는지, 언제 했는지, 어떤 저장소에 이벤트가 발생했는지, 어떤 커밋들이 포함되었는지등이 그것이었다. 여기서 이벤트가 사용자 정보 뿐만 아니라 저장소 정보, 커밋정보까지도 저장하고 있는다는 것을 알게됐다.

지금 이렇게 구구절절 github API이야기를 하는 과정에서 눈치 챘겠지만, 첫 설계 과정에서 데이터베이스 설계를 염두에 두지 않고 먼저 설계를 하게됐다. 내가 내부적으로 어떤 데이터를 갖고있는지보다, github에서 제공하는 정보의 형태가 더 중요하다고 생각했던 탓이다. 그렇기에 최대한 github REST API측의 데이터 형태를 따르기로 했고, 실제 데이터베이스 모델도 그 형태를 따라서 작업하게 되었다.

어찌되었든, 살펴보고 알게된 점은 다음과 같았다.

  1. 사용자마다 일어난 이벤트를 최대 300개(30개 * 10페이지)까지 조회할 수 있다.
  2. 기본적으로 누가 발생시킨 이벤트인지, 언제 발생했는지에 대한 정보들을 포함한다.
  3. 그외에도 타겟 저장소 정보, 포함된 커밋 정보가 포함된다.

이런 이유로 데이터 베이스는 events, users, repositories, commits정도가 필요하겠다고 가닥을 잡았고, 실제 워크 플로우는 다음과 같을 것으로 예상했다.

  1. 사용자가 홈페이지에 접속해 본인의 github username을 등록
  2. 등록 시 username을 기준으로 사용자의 정보, 이벤트를 크롤링
  3. 크롤링된 정보에서 저장소 정보, 커밋 정보를 추출해 따로 저장
  4. 특정 시간 간격으로 등록된 모든 사용자의 정보를 새로 크롤링 (단, 중복된 이벤트가 발견 시 해당 사용자 정보 크롤링은 종료)

위에 적은 데이터베이스와 워크플로우를 기준으로 프로젝트를 제작하기로 결정하고, 본격적으로 환경을 구성하기 시작했다.

1.2. 개발 순서 정하기

방향을 정한 뒤에는, 개발 순서를 정할 필요가 있었다. 당초에 생각한 개발순서는 다음과 같았다.

  1. 필요한 화면들과 레이아웃, 포함될 정보들을 개략적으로 기획할 것
  2. 그 후 필요한 정보에 맞는 API 목록을 정리할 것
  3. API 목록 대로 백엔드 개발을 진행할 것
  4. 완성된 API를 토대로 프론트엔드 개발을 진행할 것

먼저 화면 기획을 한 후, 백엔드 개발을 완전히 마치고 프론트엔드 개발을 진행하기로 결정했었다. 당시에는 프로젝트 자체가 엄청 많은 기능을 포함하는 것은 아니라고 생각했기 때문에 문제가 없다고 생각하고 진행했다. 비교적 짧은(?) 시간 동안 작업할 요량이었다보니, 다행히 모든 기능의 흐름을 놓치지 않아 백엔드 개발과 프론트 엔드 개발을 완전히 분리해 작업할 수 있었다.

다만, 나중에 이 과정에서 느낀 점은, 화면에서 보이는 것과 서버에서 진행되는 과정에서 생길 수 있는 괴리를 최대한 고려했다고 하더라도, 어찌되었든 간극이 생길 수 있다는 것을 놓친다면 생각보다 많은 시간을 버리게 된다는 점이었다. 실제로 화면 작업이 시작되고 몇가지 부분이 변경되어 수정하게 되었었다. 백엔드 전체/ 프론트엔드 전체 보다 기능과 페이지 단위로 묶어 작업하는 것이 효율적일 수 있겠다는 생각을 했다.

1.3. 필요한 화면과 기능 정리하기

레이아웃을 구성하기 위해 처음에는 펜을 집어들어서 필요한 화면을 적어봤었다.

  1. 현재 진행중인 프로젝트의 모든 현황을 볼 수 있는 대시보드
  2. 등록된 사용자의 목록을 볼 수 있는 사용자 목록
  3. 진행중인 프로젝트에 대한 사용자별 참여 현황
  4. 프로젝트의 관리 페이지

위의 네가지 정도의 페이지가 주요 페이지로 작업이 될 것으로 보였고, 정말 모자란 디자인 감각으로 Adobe XD를 이용해 대강의 화면을 구성해보았었다. 구성을 해보고나니 필요한 정보들이 눈에 보이기 시작했고, 필요한 정보들을 하나씩 API 목록으로 정리하기 시작했다. 이제 정리된 API대로 개발할 순서만 남았다고 생각했다.

2. 개발 환경 설정하기

기획이 어느정도 러프하게 완료되었다고 생각했기 때문에, 개발 환경 구성에 들어갔다. 이때에도 고려해봐야할 것이 몇가지 있었다.

  1. nodejs 서버는 어떻게 실행할 것인가
  2. mongodb 서버는 어떻게 실행할 것인가
  3. 배포시에는 서버를 어떻게 다룰 것인가
  4. react와 nodejs는 개발시 어떻게 코드를 분리할 것인가
  5. 배포시에는 두 코드를 어떻게 결합할 것인가

맨 처음 기술 조사를 하면서 위에 대한 물음은 어느정도 마무리 되어 있었다. 1,2번 항목은 docker로 해결하기로 했다. docker 공간안에 linux를 설치하고, 그 linux안에 alpine-nodejsalpine-mongo서버를 설치해 실행하기로 했다.

이 과정에서 alpine이라는 접두어가 붙은 것들은 굉장히 가볍고 기본적인 기능으로 압축되어있는 서비스라는 것을 알게되었다. 이름이 생소했던 탓에 처음에 받아들이는데 약간 어려움이 있었다.

이것은 3번의 항목도 자동적으로 해결해주었는데, 그냥 docker 설정을 그대로 서버에 업로드 하기로 한 것이다. 이때 docker-compose up --build 커맨드를 사용해 작업하기로 결정하고 그대로 설정을 했는데, 실제 서비스가 배포될 때 이 옵션을 사용하는지는 잘모르는 터라, 나중에 따로 조사를 하기로 하고, 우선은 개발이 빠르니 그대로 진행해보기로 했다.

4번과 5번은 생각보다 어렵지 않았다. create-react-app으로 개발할 예정이었던 프론트엔드는 개발 모드로 실행 시 hot-reload기능을 제공하는 개발 서버를 자동으로 생성해 실행하게 된다. 배포 시에는 서버를 실행하지 않고 build를 거쳐, 몇개의 js파일과 html, css로 합쳐져서 번들을 서버측에서 실행하는 식으로 작동하게된다.

자세한 작업 내역은 링크에서 확인할 수 있다.

그렇기 때문에, 개발 당시에는 백엔드 서버와 프론트엔드 개발 서버를 동시에 기동해 작업하고, 실제 배포 시에는 빌드 후 백엔드 코드 쪽에 client라는 이름의 폴더에 번들링된 내용을 자동으로 이동시켜 서버 코드를 배포시키는 방향으로 가닥을 잡았다.

그리고 백엔드 개발을 1차적으로 완성하고 난 뒤에 프론트 개발을 할 예정이었기 때문에, 화면을 대체할 요청 클라이언트가 필요했고, Postman을 사용하기로 결정했다. 전에는 insomnia를 사용했었으나, 생각보다 Postman쪽에서 인증 같은 기능을 더 많이 제공하고 있었기 때문에 Postman으로 결정했다.

데이터베이스도 생성한 뒤에는 직접 접속해 raw 데이터를 살펴볼 필요가 있었기때문에, mysqlworkbench같은 기능을 하는 mongodb compass를 사용하게되었다. 맥 OS에서도 제공했기 떄문에 굉장히 쾌적하게 작업 할 수 있었다.

3. 백엔드 개발하기 (1)

백엔드 개발에 앞서, 폴더 구성을 정하는 것이 먼저였다. express에서 기본제공하는 항목 중 views폴더는 사실 상 사용하지 않으니, 지워도 상관없었으나 일단 남겨두었었다. 구성을 하고 나서 남은 폴더 구성은 다음과 같았다.

1
2
3
4
5
6
7
server
    ㄴbin - express 실행 파일 포함
    ㄴdb - 데이터베이스 관련 
    ㄴlib - 추가 기능성 코드들 
    ㄴroutes - 라우팅 
    ㄴpublic - 안씀
    ㄴviews - 안씀

코드 구성을 마치고 난 후, docker 설정을 진행했었다. 각각 다른 컨테이너에서 서버를 실행하고, 그 서버끼리 통신해야 했기 때문에, 네트워크 연결을 하는 부분이 중요했다. 처음 이해하기 까지 이 과정에서 굉장히 많은 시간을 들였던 것으로 기억한다. 그 후 최신 표준을 사용하기 위해 babel 설정을 이어갔다.

기본적인 설정을 모두 마친 후 앞으로 배포하거나, 개발하는 중에 계속 노드 프로세스를 자동으로 재시작해주는 hot-reload 기능을 제공해주는 무언가가 필요했다. pm2nodemon을 대상으로 두고 있었는데 nodemon측이 사용하기에 압도적으로 편했기 때문에, nodemon을 사용하는 것으로 결정했다. 실제 배포하고 나서도 nodemon을 사용하는 것으로 결정했었다. 맞는 결정인지는 모르겠지만..

3.1. mongoose

그 후에는 nodejs에서 좀 더 mongodb를 쉽게 다루기 위한 mongoose를 세팅해 두었다. 모델 방식으로 데이터를 관리할 수 있게 도와주었는데, react에서도 모델 형식으로 데이터를 관리했기 때문에 이 구조 덕분에 굉장히 개발 시간을 단축 시킬 수 있었다고 생각한다. mongoose 모델은 기존에 기획해두었던 항목들 위주로 빠르게 설정했고 필드들은 최대한 github에서 제공하는 형태와 이름을 따르게 되었다.

mongoose는 커넥션 관리까지 한번에 하게 된다. 생각보다 어려운 항목들을 mongoose측에서 쉽게 해결해 주었기 때문에, 복잡하게 nodejs측에서 연결을 제어하는 등의 과정을 거치지 않아도 되어 다행이라고 생각했다. 모델을 생성하고 mongoose설정을 마친 뒤 서버를 실행하게 되면, 커넥션을 맺으며 자동으로 데이터베이스를 생성하고 모델에 해당하는 컬렉션들을 생성해준다. 굉장히 편리한 기능이라고 생각한다. mysql에서는 테이블을 생성하는 것도 일일히 작성해야했던 일들이 생각났다.

mongodb에서는 참조 구조도 실제 객체 안에서 결정된다. 이걸 좀 더 strict하게 관리하기 위해서 mongoose에서는 모델 형태로 작성하는 것으로 보였다. 실제 모델을 작성하면 다음과 같은 형태를 띄었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import mongoose from 'mongoose';

const EventScheme = mongoose.Schema({
    id: {
        type: String,
        unique: true,
    },
    type: String,
    actor: {
        // 다른 요소를 참조하기 
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
    },
    // ... 
});

const Event = mongoose.model("Event" , EventScheme);

나중에 typescript로 클라이언트 코드를 작성하면서 interface를 작성해보니 위와 굉장히 유사하다고 생각했다. 이런 유사점이 나중에 작업할 때 큰 도움이 되었었다.

이렇게 작성된 mongoose 모델들은 db/models라는 폴더에 모두 모아놨고, index.js를 만들어 모든 모델을 포함하게 해서 가져오기 쉽게 묶어서 사용했었다. 아래 같은 식이었다.

1
2
3
4
// 디비 불러오기 
import * as Models from '../db/models';

const allEvents = Models.Events.find();

이 방식은 이후에도 다양한 함수 묶음들을 정의해둘 때 유용하게 사용했었다. 폴더 아래 index.js파일을 만들어두면 import * as _name from '/folder'식으로 불러올 때 자동으로 index.js파일이 로드된다는 것을 우연히 다른 사람의 react코드를 보면서 배우게 되었는데, 이후에 프로젝트 구조에 정말 많은 영향을 끼치게 되었다.

3.2. github API

github REST API는 이미 문서에서 다양한 정보를 제공해주고 있었기 때문에, 처음에는 axios만 가지고 작업을 할까 했다. 하지만, 인가된 요청을 하기 위해서는 매번 인증키를 전달해줄 필요가 있었고, 그외에도 요청을 위해서 매번 문서를 열어야 하는 번거로움이 생겼었다. 그래서 찾아보니 github측에서 다양한 환경에서 사용시 편의성을 위해 제공하는 몇 가지 패키지 들이있었는데, 그중에서도 nodejs를 위해 제작된 octonode라는 패키지가 있었다.

callback형태로 제작 되어있어 Promise를 기본적으로 제공하지 않는 등의 약간의 불편함은 있었으나, 간단히 사용할 수 있었기 떄문에 바로 설치해서 테스트 해볼 수 있었다.

실제 적용기는 링크에서 확인할 수 있다.

이 과정에서 모델을 적용해보고, 미리 기획단계에서 크롤링하기로 결정했던 요소들에 대해 크롤링함수를 미리 만들어 두었다. 그리고 route측에 테스트 코드를 만들어 간략한 사용자 정보를 불러와서 사용자 컬렉션에 추가하는 로직을 시작으로 본격적인 백엔드 개발에 돌입했다. 생각보다 과정이 손에 잡혀 기본적인 뼈대를 잡는데 그리 오랜 시간이 걸리지는 않았다.

정리하며

생각보다 기획단계를 정리하는 글이 길어지고 정작 개발에 들어간 내용은 많지 않아서 당황스럽다. 기획단계과 환경설정 과정은 항상 내가 어려워하는 부분이고 가장 많은 시간을 투자하는 부분이라 생각도 많이 정리할 필요가 있었다고 생각한다. 다음 글에서는 남은 백엔드 개발 과정에서 어떤 일이있었는지 작성할 예정이다. 생각보다 글이 짧아질지도 모르겠다.