[Retrievo] 팀을 위한 프로젝트 협업 관리 툴 | React & Apollo & GraphQL 4주 프로젝트 후기 (feat. 코드스테이츠)

Hailey Song·2020년 12월 24일
5

PROJECT

목록 보기
4/4

0. Intro.

2주 프로젝트로는 우리가 좋아하는 게임 분야에 도전을 했다면(What's Dat) 4주 프로젝트에서는 우리에게 도전적인 영역을 준비하기로 했다. 좀 더 정석적이고 CRUD 구현이 분명한 분야가 무엇이 있을까 고민하다가 한 팀원이 우리의 2주 프로젝트를 레딧에 올리며 개발자들에게 조언을 구하셨다.

우리는 개발을 배운지 3개월이 된 학생들이고, 2주 프로젝트로는 이런 걸 만들었다. 앞으로 4주 동안 무엇을 만드는 것이 우리가 가장 많은 것을 배울 수 있을지 추천을 해달라.

그렇게 해서 받은 답변이 Jira와 쇼핑몰이었고, 프론트엔드 중심의 쇼핑몰이냐, 아니면 백엔드 중심의 프로젝트 관리 툴이냐에 대해서 팀원끼리 많은 의견을 나누었다. 일단 4명 모두 프론트엔드 개발자를 희망했기 때문에 쇼핑몰이 매력적이긴 했는데, 진정한 프론트엔드가 되기 위해서는 백엔드에 대한 로직을 어느 정도 경험해야한다는 의견에 모두가 공감하면서 Jira와 같은 애자일 기반 프로젝트 협업 관리 툴을 만들어보기로 했다.
(그리고 그 과정이 이렇게 피말릴 줄은 그 누구도 예상하지 못했다)

1. Retrievo?

간단하게 Retrievo 프로젝트를 소개하자면, 애자일 방식으로 프로젝트를 관리하는 협업 관리 툴이다. 스프린트 단위로 테스크를 관리할 수 있고, 그렇게 나눈 테스크들을 칸반보드에서 시각적으로 관리할 수 있다.

팀원 중에 어디서 자꾸 에러를 물어오는 리트리버 같은 분이 있는데(리트리버가 retrieve(가져오다)에서 유래한 단어라는 것도 처음 알았다!) 그 분의 이름을 따서 프로젝트의 이름을 Retrievo라고 이름을 지었다. 귀여운 강아지가 할 일을 물어다 준다는 의미에서..

기획 단계에서는 Jira와 Asana, Monday를 참고했고 Miro에서 개략적인 뼈대를 짜고 Figma로 디자인했다.

1) 랜딩페이지


랜딩페이지는 원래 기획 단계에서는 없었던 페이지인데, 사이트에 들어왔을 때 이게 어떤 서비스인지 알려주지 않고 대뜸 회원가입부터 요구하면 어느 사용자가 신뢰를 하면서 들어오겠냐는 피드백을 받고 반성하면서 만든 페이지이다. 여기에서는 프로젝트의 주요 기능에 대해 간략하게 소개를 했고, 회원가입을 하지 않아도 게스트 버튼(Take Tour)을 통해 간접적으로 사이트를 체험해볼 수 있게 만들었다.

이 페이지는 디자인에서부터 구현까지 팀장님과 함께 작업을 진행했다. 스크롤할 때 나타나는 애니메이션은 React awesome reveal이라는 라이브러리를 사용했고, 비디오 삽입은 React Player 라이브러리를 사용했다. 로컬 이미지와 비디오가 많이 들어가는 페이지였는데, 개발 단계에서는 아무 이상이 없었지만 빌드하고 배포하는 단계에서 동영상과 이미지 파일이 손상되는 경우가 생겼다. 결국 비디오는 S3 버켓에 올린 후 링크를 걸어 불러오는 형식을 택하게 되었다. 사실 그게 맞는 것 같다.

Position Absolute

여기서는 CSS의 position 속성에 대해 많은 고민을 했던 것 같다. 다꾸 스티커처럼 배경에 착 달라붙은 이미지가 많은데 이 친구들을 어떻게 배치하거나 어떻게 고정시켜야 하는지, 포지션 absolute를 준다면 어느 단계를 부모로 잡아야 하는지, 어디까지를 그룹으로 묶어야 하는지에 대한 고민을 많이 했다. 이번 프로젝트는 모바일 반응형을 지원하지 않고 데스크탑과 테블릿까지만 고려하기로 했기 때문에 그렇게 엄격하게 absolute를 주지는 않았지만 만약 모바일까지 지원하게 되었다면 이 이미지들을 어떻게 배치하는 것이 좋았는지에 대해서는 추후 더 생각해볼 주제인 것 같다.

Z-index

이미지들이 서로 겹쳐있다보니 Z-index를 사용하게 되었는데, 아무리 노력해도.. z-index를 999까지 잡아도 밑에 있는 친구들이 위로 올라오지 않는 것이다! 계속 끙끙대면서 위치도 바꿔보고 숫자도 -1로 잡아보고 별 짓을 다 하다가 검색을 해보니 position이 잡히지 않으면 기준점이 없기 때문에 z-index가 적용되지 않는다는 것을 알게 되었다. '기준점'이라는 말에 띵 했다. 생각해보니 상대적 위치를 정하기 위해서는 기준점이 필요하다는 당연한 사실을 모르고 진행했던 것들이 꽤 많았던 것 같았다. 해당 이미지에 position으로 기준점을 주기 위해서는 태그 트리구조를 한 번 뒤엎어야 했다. 이번을 교훈으로 다음번에는 큰 그림에서부터 구조를 짜는 연습을 해야겠다는 생각을 했다.

2) 로그인/회원가입


로그인과 회원가입은 하나의 페이지 안에서 애니메이션 전환으로 구분된다. 디자인은 요기를 참고했다. 꼭 해보고 싶었던 디자인이라 팀원분들께 강력하게(?ㅋㅋㅋ) 어필해봤고 다행히 모두가 마음에 들어해주셨다.
이 페이지 역시 팀장님과 함께 작업했는데, Formik을 담당하셨던 팀장님이 로그인, 레지스터 컴포넌트를 담당해주셨고, 나는 이미지 카드 작업을 진행했다. SVG 파일을 리액트로 import해본 적이 처음이라 어떻게 하는지 고민했었는데 앞서 다른 팀원분이 작업해놓으신 게 있어서 참고했다. 나중에는 SVG의 색깔을 바꾸어 적용하는 방법을 찾아보고 싶다.

로그인 페이지 다음에 나오는 새 프로젝트 생성 페이지는 내가 담당했는데, 팝업창 바 상단에 있는 빨간색 버튼을 누르면 다시 랜딩 페이지로 라우트되는 이스터에그를 숨겨놓았다ㅎ (팀원들도 모르겠징..)
다만 디자인 계획 단계에서 미처 생각하지 못한 것이 바로 페이지 간 이동이었는데, 페이지에 잘못 들어간 경우나 이전 페이지로 돌아가고 싶은 경우 누를 수 있는 뒤로가기 버튼이 없어서 페이지간 테스트를 할 때 여간 불편한 게 아니었다. 다음에는 유저의 동선을 배려하는 기획을 하고 싶다.

Authentication

Authentication은 팀원이 모두 공동작업한 부분이다. 모두가 Apollo server, GraphQL을 처음 적용해봤기 때문에 다같이 작업하며 어느 정도 감을 익히자고 합의한 부분이 Authentication이었기 때문이었다. 처음엔 GraphQL + JWT + React 강의를 들으면서 진행하자고 했었는데 20분짜리 강의를 해독하는데 5시간이 걸리면서.. 모든 팀원이 고통에 빠졌고, 깔끔하게 포기하고 다른 예제를 검색한 끝에 GraphQL과 Passport로 Authentication을 구현하는 친절한 블로그 하나를 찾아내서 옹기종기 모여 구현했다. 다만 우리같은 경우에는 Express 뿐 아니라 Apollo Server도 사용해야 해서 그걸 어떻게 접목해야하는지는 더 찾아봐야했고, 유저 정보를 email로 구분하는데, github에서 email을 받아오는 것이 선택사항이어서 거기에서도 조금 헤맸던 기억이 난다.

3) 스프린트 / 테스크


스프린트 페이지는 다른 팀원분들이 맡았다. 여기서는 스프린트의 CRUD가 모두 가능하고 스프린트 내 테스크의 CRUD도 모두 가능하다. 특히 테스크를 클릭하면 해당 테스크가 사이드바로 나오게 되는데, 여기서 구체적으로 테스크에 대한 설정이 가능하다. 스프린트와 테스크는 모두 드래그앤드롭으로 순서를 바꿀 수 있는데, 내가 맡은 칸반보드에서도 드래그앤드롭 기능이 필요했기 때문에 스프린트를 맡은 팀원분들과 (같은 고통을 겪는 동료끼리) 서로 많이 대화를 했다.

Task Modeling

스프린트 페이지의 구성요소 중 클라이언트 쪽에서 작업한 것은 없지만 여기에 들어가는 테스크나 라벨, 코멘트에 대한 서버쪽 엔티티와 리졸버를 작업했다. 테스크는 getTask, getTasks, createTask, updateTask, deleteTask와 같은 기본적인 CRUD를 구현했는데, 가장 어려웠던 건 updateTask였다. updateTask는 크게 세 가지 경우로 나뉘었는데, 테스크의 제목이나 설명을 변경하는 일반 업데이트, 해당 테스크에 댓글이 달리거나 유저가 할당되거나 라벨을 설정할 때 발생하는 관계성 업데이트, 그리고 드래그앤드롭이 일어나거나 테스크를 삭제할 때 일어나는 (업데이트의 꽃..) 인덱스 업데이트였다.
난이도는 일반 업데이트 <<< 관계성 업데이트 <<<<<<<<<<<<<<<<<<<<<<< 인덱스 업데이트 정도..? 인덱스 업데이트 로직에만 아침 9시부터 밤 12시까지의 모든 시간을 바쳤다. 함께 싸워주신 팀장님께 큰절 올린다.. 흑흑..

(급한대로 개인 발표 영상에서 이미지를 가져왔다)
테스크 엔티티는 보드 내 해당 테스크의 인덱스를 나타내는 boardRowIndex라는 column을 가지고 있다. 이 인덱스를 수정할 때 해당 테스크의 인덱스만 변하는 것이 아니라 테스크 이동으로 영향을 받는 모든 테스크의 인덱스를 이동시켜주어야 했다.

문제는 보드 간 이동일 때 더욱 복잡해졌다.. 이동 전 보드 안의 테스크 뿐만 아니라 이동 후의 테스크의 인덱스도 바꾸어주어야 했기 때문에 영향받는 테스크들을 모두 필터로 걸러내어 조건에 따라 각자 +1을 해주거나 -1을 해주는 방식을 선택했다. 여기에 또 스프린트 시작 전의 테스크이거나 마지막 보드로 이동하는 테스크이거나, 보드 자체를 삭제해서 테스크가 다른 보드로 이동하는 경우 등 테스크 업데이트에 걸려있는 경우의 수가 너무 많아서 여기서 엄청나게 머리를 싸맸던 것 같다. (사실 어딘가에 구멍이 있는 것도 같다...) 그 때는 이렇게 경우의 수가 많을 줄 몰라서 무작정 코드를 쓰기 시작한 건데 어느새 코드들이 무럭무럭 자라나서 감당이 어려울 정도가 되었다. 그제서야 왜 사람들이 함수를 작은 단위로 나누라고 하는지 뼈저리게 이해할 수 있었다.

4) 칸반보드


이 곳은 외롭고 고독한 싸움을 치룬 또다른 혈전지이다.. 범인은 드래그앤드로..ㅂ...o<-< ..
일단 클라이언트는 크게 TaskCard, Board, (맨 우측에 있는)SkeletonBoard, BoardList라는 컴포넌트로 나누어서 작업했다.

TaskCard Design

TaskCard에서 했던 고민은 테스크 상단의 X버튼이나 테스크 하단의 아바타의 위치를 잡을 때 position absolute를 써야할지와, 테스크안 요소 간의 간격 설정을 flex로 주는 것이 좋을지, margin으로 설정하는 것이 좋을지였다. 처음에는 absolute를 사용했었는데 이게 상대적인 위치가 아니다보니 테스크의 제목이 길어질 때 서로 겹치는 문제가 발생했다. 사실 처음 만들 때는 이런 경우에 대해서는 생각지도 못했는데, 팀장님이 테스크바를 디자인하실 때 모든 글자수를 최대치까지 넣어 실험하시는 것을 보고 모든 경우의 수를 따져야한다는 것을 알게 되었다. 유저는 항상 예상치 못한 행동을 한다는 말을 가슴에 새겨야겠다. 또 CSS는 모두 눈속임이라는 말을 들은 적이 있는데 그래도 실무에서는 이럴 경우 어떻게 처리하는지 궁금했다.

Drag & Drop

드래그앤드롭 기능은 React Beautiful DnD 라이브러리를 사용했다. 수직적인 드래그드롭이 들어가는 스프린트와 달리 칸반보드에서는 보드의 수평적 이동과 보드 내 테스크의 수직적 이동이 중첩되어야 하기 때문에 걱정이 많았는데 다행히도 이 라이브러리가 제공하는 스토리북에 똑같은 예시가 있었다. 공식 사이트에서 제공하는 튜토리얼 영상을 따라하면서 하나하나 성공할 때마다 너무너무 신기했다. 그렇게 클라이언트 로직을 모두 구현하고 나서 서버와 연결하는 작업을 시작했는데.. 여기서부터가 지옥이었다.

일단 아무리 해도... 아무리 해도.. 리랜더링이 안됐다. 보드를 수정하거나 업데이트하거나 지울 때 실시간 반영이 되지 않아서.. 여기에만 꼬박 5일을 썼다. Apollo Client 역시 처음 적용해봤기 때문에 cache에 대한 이해가 부족해서 그런줄 알고 cache.update, refetchQuery, cache.writeQuery, cache.readQuery, cache.evict 등등 스텍오버플로우와 공식문서를 이잡듯 뒤져 모든 경우의 수를 쳐봤는데도 해결이 안되는 것이다.. 옆동네 스프린트 리랜더링은 아주 잘 되던데! 왜 우리집 내새끼는 잘 못하는 것인가!
막힌지 4일차가 되던 날에는 apollo cache 사용의 문제가 아니라 내가 짠 코드의 어디선가 문제가 나고 있겠다 싶어서 모든 페이지 모든 단계에다 콘솔을 찍고 여러번 돌려봤다. 그러고 나서 발견한 것..


중간에 보드 데이터를 스테이트로 저장해주고 있었던 것이었다.. 그러니 아무리 cache를 업데이트하더라도 받아오지 못했지...
뷰티풀디앤디 공식문서에 나타난 예시가 스테이트 저장방식이라 아무 생각없이 그대로 구현하고, 또 아폴로 클라이언트 방식은 공식문서에 나온 대로 하다보니 둘 다 같이 쓰는 경우에 대해서는 전혀 생각하지 못한 것이 이 문제의 원인이었다. 지금은 임시방편으로 useEffect를 사용해서 cache가 바뀔 때마다 state를 업데이트시켜주도록 만들었지만 기회가 된다면 cache only로 리팩토링을 해보고 싶다.

5) 대시보드


대시보드 페이지는 여태까지 만든 테스크의 정보를 토대로 나 자신에게 할당된 테스크를 분류하거나 프로젝트 전체의 진행상황을 차트로 확인할 수 있는 페이지이다. 이 페이지 역시 스프린트를 담당했던 팀원분들이 구현하셨다. 페이지네이션, 차트 등의 기능이 들어갔는데 둘 다 나중에 꼭 구현해보고 싶은 분야라 구현을 뚝딱 해내신 팀원분들이 좀 멋있어보였다ㅎㅎ

6) 프로젝트 세팅

프로젝트 세팅 페이지에서는 프로젝트에 참여한 팀원들의 목록을 볼 수 있고, 프로젝트의 이름을 수정하거나 프로젝트 자체를 삭제할 수 있다. 이 페이지의 핵심 기능은 멤버를 초대할 수 있는 기능인데, nodeMailer를 사용하여 구현하였다. 멤버를 초대하면 해당 멤버에게 초대 메일이 전송되는데

메일 템플릿은 내가 만들었다!ㅎ 리트리보가 초대장을 물어오는 형식으로.. woof woof!
HTML으로 작업하고 flex를 주어서 direction-column으로 설정했는데 막상 메일을 전송하고 보니 다 디자인이 깨져있어서 좌절했던 기억이 있다. 다행히 flex 항목을 지워주니 잘 나왔다. 이메일과 관련된 css 설정은 또 다른걸까..? 궁금하다.

2. Stack

기술 스택은 다음과 같다.. (심호흡)

서버는 일단 GraphQL을 사용하기로 했기 때문에 Express 서버와 Apollo Server를 함께 사용했다. 데이터베이스는 Postgres를 사용했고, TypeORM과 TypeGraphQL로 데이터베이스의 엔티티와 그래프큐엘 쿼리를 한꺼번에 작성했다. 세션이나 email invitation 같은 휘발성 데이터는 레디스에 저장을 했고, authentication은 passport 라이브러리를 사용해서 구글, 깃헙, 일반 로그인 기능을 구현했다.

서버에서 가장 힘들었던 건 단연코 Typescript, TypeORM, TypeGraphQL의 TYPE 3형제였는데, TypeScript를 처음 사용해보는데다 각 언어마다 타입을 지정해주는 문법이 미묘하게.. 정말 미묘하게 달라서 초반에 좀 많은 시행착오를 겪었다.


클라이언트는 View적으로는 기본적으로 타입스크립트를 적용한 리액트로 구현했고, 빠른 개발을 위해 chakra-ui와 emotion을 사용했다. 이번 프로젝트도 기본적인 아톰 및 레이아웃 단계까지는 스토리북으로 프로토타입을 미리 만들어봤고, 그리고 대망의 뷰티풀드래그앤드롭..을 사용하여 드래그앤드롭 기능을 구현했다. 서버와의 통신은 Apollo Client를 이용해 GraphQL 쿼리를 보냈는데, 빠른 타입체킹을 위해 코드젠을 사용했다.(커스텀이 불가능해 나중엔 좀 후회했다.)


클라이언트와 서버의 대략적인 플로우는 위와 같다. 아키텍쳐에 관심이 많으신 팀원분이 설계하셨다.

배포 역시 내가 건드린 영역은 아니라서..ㅎ.. S3와 EC2, RDS, 엘라스틱캐시, 로드밸런서 등을 사용한 것으로 알고 있다. AWS는 꼭 넘어야할 큰 산 같다. 개인 프로젝트를 하면서 경험을 꼭 해보고 싶은 분야 중 하나.


Typescript(살려줘!)와 AirBnB eslint의 양대산맥은 우리를 고통에 빠지게 했고 Prettier는 우리를 구원받게 했다. Typescript는 팀장님이 any를 사용하면 커밋을 못하게 막아놓으셨기 때문에 화를 내는 코드들을 하나하나 달래가며 진행을 해야했다. 덕분에 이론은 몰라도 대충 눈치로 때려맞추는 잔술수가 늘었다. 깃훅인 허스키도 처음 사용해봤는데 허스키를 처음 설정할 때 쉘 스크립트의 늪에 빠져 자바스크립트로는 1분만에 끝날 간단한 조건문에도 두 세시간이 걸렸다. 터미널을 사용하는 이상 쉘스크립트는 꼭 넘어야 할 산인 것 같아 프로젝트가 끝나고 천천히 공부해보기로 했다. 암튼 허스키로는 master, dev 브랜치에 직접 커밋하거나 푸시하지 못하게 막아두고 eslint 체크를 하는 기능을 넣었다. 프로젝트를 진행하면서 허스키에게 세 번 정도 물렸다. 웡웡!

이 외에도 프로젝트 관리에 Jira를 도입했다. Jira를 선택한 이유는 첫째, 지난번 깃헙 기반으로 이슈를 관리할 때 너무너무 UX적으로 불편했기 때문에 후반에 들어서 테스크 관리를 하지 않게 되었기 때문이고, 둘째, 우리가 Jira와 같은 서비스를 구현하기 때문에 직접 이슈를 관리하면서 플로우와 기능을 경험해보는 것이 좋을 것 같다는 생각 때문이었다. 예전에 직장 다닐 때 Jira를 써본 적이 있어서 친숙한 툴이기도 했고. 그런데 Github과 Jira를 연동할 수 있다는 것은 이번에 처음 알아서 연동까지 시켰다!(근데 지라-깃헙 연동의 UX가 너무너무 좋지 않았다.. 엄청 헤맴...) 이게 좋았던게, 지라에 깃헙 레포지토리를 연결한 다음 이슈카드 넘버를 브랜치나 커밋메세지에 넣으면 자동으로 해당 이슈에 브랜치나 커밋이 연동되었다. 개인적으로는 이렇게 커밋이 문서화되니까 버그트래킹에 너무너무 편했다. 보드 드래그앤드롭에서 많이 헤매면서 예전 기록들을 찾아봐야할 때가 많았는데 지라로 한번에 딱딱 찾을 수 있어서 꿀기능!

3. Review

사실 느낀점을 쓰는게 애매한게.. "불태웠어..." 밖에 할 말이 없다. 개운하다던가 허탈하다던가 뿌듯하다던가 이런 느낌들보다는 "하얗게 불태운 한 달이었어.."라는 말밖에...
뭔가 교훈적인 리뷰로 이 글을 끝마치고 싶은데 이 프로젝트 발표를 마치고 나서 든 감정들이 언어로 표현하기가 애매했다.

코드스테이츠에서의 마지막 프로젝트인만큼, 그리고 어쩌면 팀원분들과 함께 할 수 있는 마지막 프로젝트일 수도 있는 만큼 이 프로젝트에 우리의 모든 역량을 다 해보길 원했고, 새롭게 떠오르고 있거나 인기있는, 그러나 우리가 아직 배워보지 않은 프레임워크나 라이브러리를 시도해보길 원했다.
로직이나 코드가 완벽하거나 깔끔하진 않았어도, 우리가 우당탕탕하며 어찌저찌 만들었어도 그 과정을 통해 배우고 성장한 것들이 너무나도 많아 개인적으로는 만족스럽다.

(하지만 아직 끝나지 않았다.. 아직 구현할 페이지가 둘 정도 남아 있다..)

한달동안 너무너무 고생했음에도 불구하고 항상 웃음을 잃지 않았던 RPQ 팀원분들께 감사의 인사를 전한다.
너무 급하게 포스트를 마무리하는 감이 있지만, 오늘은 코드스테이츠를 수료한 날이고 크리스마스 이브니까! 가족들과 시간을 보내러 이만 마치겠다.

0개의 댓글