코드스테이츠 파이널 프로젝트 회고

양해승·2021년 4월 27일
0
post-thumbnail

4주 간의 파이널 프로젝트를 마쳤다. 이제는 정말로 부트캠프 수료를 목전에 두게 되었다.
부트캠프 전체 수료 과정에 관한 회고는 따로 적기로 하고, 여기에는 4주 프로젝트에 관한 회고만 남기고자 한다.

프로젝트 소개

프로젝트 사이트
프로젝트 서버 레포
프로젝트 깃헙 위키

우리 조는 리그 오브 레전드 게이머를 위한 사이트인데 크게 두 가지 기능을 제공한다.
하나는 매칭 기능이다. 내 소환사명과 다른 소환사명을 입력, 검색하면 서로 얼마나 궁합이 잘 맞는지를 숫자로 보여준다.(데이터 시각화)

두 번째는 커뮤니티 기능이다. 글을 쓰고, 읽고, 고치고, 삭제하는 Post 관련 CRUD 기능뿐만 아니라 마음에 드는 글에 좋아요를 눌러놓고 나중에 볼 수 있게 하거나(Like), 다른 유저를 팔로우하는 기능을 추가했다.(User, Follow)

내 역할

역할: 백엔드

사용 스택

  • TypeScript
  • GraphQL
  • Type-GraphQL
  • MySQL
  • AWS(EC2, RDS)
  • TypeORM
  • Passport
  • JWT
  • Bcrypt

내가 한 일

GraphQL 구현

  • Type-GraphQL로 스키마 및 리졸버 작성

데이터베이스 관련

  • TypeORM으로 엔티티 구성, 엔티티 간 관계 설정

서버 설정 관련

  • Express에 Apollo-Server-Express 연결
    • TypeORM으로 데이터베이스(MySQL)와 연결

암호화, 인증

  • Bcrypt로 로컬 가입 유저 비밀번호 암호화
  • Passport로 oAuth 가입 유저 인증
  • JWT 방식으로 로컬 유저, oAuth 유저 인증
  • 미들웨어로 로그인 여부 확인

AWS(EC2, RDS)

  • 중간 배포 및 이슈 핸들링

기타

  • 커뮤니티 기능 아이디어 제안
  • DB Schema, Back-end flowchart 작성
  • RESTful API Documentation 작성

준비 단계

역할

백엔드 개발자가 되고 싶기 때문에 2주 프로젝트에서도 백엔드를 했고, 이번 4주 프로젝트에서도 백엔드를 했다. 덕분에 '내가 무엇을 해야 하는지'를 빠르게 머릿속에 그린 채 시작할 수 있었다.

기획

나는 서버 세팅과 ORM 연결 등 큰 틀을 잡고 커뮤니티 기능을 구현하는 일을 맡았다.

스택

(그야말로 스택 오버플로우...)
놀랍게도 처음에 써보자고 한 스택 거의 대부분을 사용했다.
이 중 상당수가 처음 써본 스택이었다.
'어떤 스택을 왜 골랐는지' 등 스택에 관한 이야기는 하기(下記)한다.

스택 이야기 (새로 배운 스택)

TypeScript

가장 먼저 도입 의견이 나온 스택이었고 이견이 없었다.
직접 써보니 개발속도 저하라는 트레이드오프를 상쇄하고 남을 만큼 좋은 언어라고 느껴졌다.
코드를 잘못 적으면 곧장 알려주고, 이에 따라 나중에 디버깅하는 시간을 줄여주기 때문이다. 다만 Passport에서 겪었던 문제처럼 매우 힘들어질 때가 있는데 이럴 때에는 과감히 검색을 하자.

GraphQL

under-fetching 문제를 해결하기 위해 도입했다.

한 페이지만 해도 이렇게 많은 정보가 필요한데 만약 RESTful API를 썼다면?
클라이언트 측에서 한 페이지를 위해 요청을 4~5개까지 해야 한다.
GraphQL을 쓰면 이 문제가 깔끔하게 해결돼서 좋았다.

엄청 흥미로운 방식이라고 느껴졌다.

  • 아폴로에서 제공하는 playground도 신기했고,(물론 나는 포스트맨을 좋아해서 포스트맨을 사용했다.)
  • 글 작성자 => 작성자의 이메일 => 해당 이메일의 팔로워 => 이 팔로워의 닉네임처럼 연결고리를 타고 뺑뺑 돌 수 있다는 점이 무척 재밌었다.
  • API 문서를 따로 작성하지 않아도 알아서 스키마를 작성해준다.

다만 문제점도 있었다.

  • 새로 배워야 한다.
  • app.use(morgan('dev'))을 했더니 이런 고오급 정보만 볼 수 있었다... (하지만 나는 해결했다.)

Apollo Server

  • GraphQL에 적합하다
  • 이미 익숙한 Express 위에 얹어서 사용 가능하다.(apollo-server-express라는 라이브러리 사용하면 됨!)

Type-GraphQL

처음에는 이 스택을 사용하기가 정말 고통스러웠다.

  • TypeScript에 대한 사전 지식이 필요하다.
  • GraphQL에 대한 사전 지식이 필요하다.
  • 참고자료가 부족하다. (npmjs.com을 확인해보면 주간 다운로드 수가 겨우 10만을 조금 넘는 수준이다.)

하지만 어려움을 극복하고 나니깐 매우 편리한 프레임워크라고 느껴졌다.

  • schema를 클래스와 데코레이터만으로 작성할 수 있다.
  • resolver를 일반 클래스의 메소드처럼 작성하면 된다.

TypeORM

내가 느끼기에는 Sequelize보다 훨씬 편하다.
특히 관계 설정은 매우 간편하다.
쿼링하는 방법이 여러 개라 취사선택할 수도 있다.

Passport

처음 배우는 과정이 힘들었지만 그럼에도 불구하고 배울 가치가 충분히 있다고 생각한다.
한번 배워놓으면 전략만 바꿔서 다양하게 사용할 수 있고, 클라이언트와 협업을 따로 할 필요 없이 백엔드에서 뚝딱뚝딱 만들고 '이쪽으로 GET 요청해주세요!'라고 엔드포인트만 알려주면 되기 때문이다.

개발 과정: 고통, 짧은 기쁨, 다시 고통의 반복

난관을 헤쳐나가면 또 다른 난관을 마주했고, 삽질을 끝내면 또 다른 삽질이 이어졌다.

JavaScript -> TypeScript로 변경

처음에는 백엔드에서도 다양한 스택을 쓰려고 했다. 하지만 1주차에 TypeScript를 배우면서 위축됐고, JavaScript + Sequelize 조합을 쓰기로 계획을 선회했다.

이미 앞선 2주 프로젝트에서 써본 조합이었기 때문에 백엔드 업무는 빠르게 진척됐다. 하지만 이렇게 순조로우면 프로젝트가 아니다. 뒤늦게 'Sequelize가 프론트엔드쪽 TypeScript 코드와 충돌을 일으킬 수 있다'는 말을 듣게 되었다. 결국 백엔드에서도 TypeScript를 쓰기로 결정했다. ORM은 TypeORM을 쓰기로 했다.

서버 초기 세팅 + Type-GraphQL 초기 세팅 문제

처음 서브웨이에 샌드위치 사러 간 날을 아직도 기억한다. 어떻게 주문해야 하는지 몰랐기 때문이다. 다른 사람들이 주문하는 모습을 몇 번 본 후에야 어설프게나마 주문을 했다.

이번 프로젝트도 '어떻게 시작해야 하는지'를 몰라서 캄캄했다.

ExpressApollo Server는 어떻게 얹어야 하는지, 여기에 TypeORM은 어떻게 연결해야 하는지, GraphQL은 어떻게 사용해야 하는지, Type-GraphQL은 도대체 뭔지 하나도 몰랐기 때문이다.

특히 Type-GraphQL은 처음 써볼 뿐더러 아예 들어본 적조차 없는 스택이었다.

하지만 모든 문제에는 다 해결책이 있는 법. Type-GraphQL의 공식문서를 읽다가 다음 문구를 보게 됐고 이 덕분에 프로젝트를 무사히 해낼 수 있었다.

A lot of these topics are covered in Ben Awad's TypeGraphQL video series on YouTube.

영어를 할 줄 안다는 사실이 얼마나 큰 자산인지 다시 한번 느끼게 되었다.

session -> token으로 인증 방식 변경

개인적으로 토큰 방식 인증을 선호한다. 토큰은 클라이언트쪽에 보관되기 때문에 서버에 문제가 생겨도 지장이 없고, 서버의 무상태성도 유지할 수 있기 때문이다. 그리고 페이로드에 담는 정보에 따라 사용자 그룹을 지정할 수도 있다. 그래서 토큰을 쓰려고 했지만 oAuth에 쓰기로 결정한 Passport가 세션을 기본으로 작동한다는 사실을 알게 됐다. 처음 써볼 Passport를 토큰 방식으로 고치는 일보다, 로컬 유저 인증방식을 세션으로 구현하는 게 훨씬 쉽기 때문에 세션을 인증 방식으로 채택했다.

하.지.만.

EC2에 올려놓고 테스트를 해보니 인증이 제대로 안됐다. 크롬 브라우저에서 쿠키 관련 문제가 발생했던 것 같았다. 왜 추측형으로 말을 하냐면, 세션 인증이 안되는 원인을 찾고 해결할 바에 아예 토큰으로 바꾸자고 했기 때문이다. (개인 정보를 쿠키에 담는 일은 꺼려진다.)

Postman 설정 문제

JWT 인증방식을 개발하다 보면 Bearer token을 일일이 리퀘스트에 넣기가 굉장히 귀찮다. 그래서 나는 늘 하던 대로 Postman에서 이를 자동화했는데 이상하게도 요청 결과값이 예상과 달랐다. 처음에는 '내가 GraphQL을 처음 쓰는 거라 gql을 틀리게 적었나?'라고 생각해서 여러 번 시도했는데도 계속 예상과 다른 결과가 나왔다.

나중에서야 원인을 찾게 됐는데 Postman에서 자동화하는 코드를 짤 때 오타가 있었다. 진작에 curl로도 요청 날려볼걸...

Join Querying 문제

GraphQL에서는 테이블 간 연결이 되어 있으면 쿼리를 할 때 자동으로 연결이 반영된 결과가 반환되는 줄 알았다. 지금 돌이켜보면 어이없는 생각이었지만 진지하게 그렇게 생각했다. '자동으로 되니깐 under-fetching 문제가 해결된 거고 RESTful API 대신 GraphQL을 쓰는 게 아닐까?'라는 망상 같은 생각을 했다.

당연히 안되는 걸 시도하느라 별고생을 다 했다. '도대체 돼야 하는 건데(돼야 하는 게 아님) 왜 안되지?' 하면서 어느 스택 문제인지를 한참 찾았다. 'TypeORM 문제인가? 아니면 Type-GraphQL 문제인가? 아니면 Apollo Server 문제인가?' 이 멍청한 짓을 며칠 동안 했다. Stack Overflow에 질문을 올렸더니 비추만 받았고 답은 달리지 않았다.....(상처 입음...)

어느날 샤워를 하면서 '쿼리를 할 때 조인을 안 써서 그런 거 아닌가...?'라는 생각이 들었고 이를 테스트해보니 혹시나가 역시나였다. 쿼리에서 조인을 안해서 생긴 간단한 문제였다....

Passport 문제

기능구현이 얼추 다 끝나고 oAuth 기능만 남았다. 문제는 Passport를 어떻게 사용해야 하는지 아무리 공식문서를 봐도 알 수 없었다는 점이다. oAuth 자체의 흐름이 워낙 복잡하기 때문에 어떻게 클라이언트와 협업해서 코드를 짜야 할지 고민을 거듭했다.

한참을 검색하다가 Github에서 Passport로 페이스북 인증을 구현한 레포를 하나 발견했는데 View 관련 코드를 보고 깜짝 놀랐다. 로그인 페이지에 달랑 GET 요청 코드 한 줄 밖에 없었기 때문이다. 이를 보고 감이 왔고 '아하 이래서 사람들이 패스포트를 많이 쓰는구나'라는 생각을 했다.

Passport 사용법을 찾아서...

하지만 저 레포는 JavaScript + session 인증 방식이었고 나는 TypeScript + token 인증을 구현해야 했다. 처음에는 strategy에서 곧장 토큰을 발급하려고 했지만 생각해보니 기(旣)가입자와 신규 가입자 모두 토큰을 줘야 했기 때문에 strategy에서는 ORM 관련 작업만 했다.

req.user의 타입 지정 문제

callback 함수 단계에서 토큰을 발급하려고 했는데 어려운 이슈를 만났다. authenticate을 마치고 callback 함수 단계로 넘어간 정보를 TypeScript가 'User 타입으로 지정해!!!'라며 빨간 줄을 그은 것이다. 이는 패스포트 라이브러리 내부에서 일어나는 일이라 Object Type이나 interface를 새로 만들어서 해결하기 어려운 문제였다. 검색을 했는데 이 문제로 고통 받는 사람이 꽤 많았고, 여러 방법을 적용해봐도 빨간 줄이 사라지지 않았다.

하지만 연주님께서 이 문제의 해결법을 찾아오셨다. 정말 간단한 해결책이었다.

const userData: any = req.user;
/* 패스포트에서 req.user로 넘어온 데이터를
타입스크립트는 무조건 User 타입으로 맞추라고 하지만
새로운 객체에 담아서 any 타입으로 지정하면 해결! */

redirect 메소드로 어떻게 클라이언트측에 정보를 전달해주지?

user 정보를 담은 객체도 만들었고, 토큰도 만들었다. 하지만 또 문제가 발생했다.
res.redirect()와 res의 body에 정보, 토큰을 담아주는 일을 어떻게 동시에 해야 하는지를 전혀 생각하지 못했다.
역시나 또 검색을 했고, 이 문제로 고통 받는 사람이 꽤 많았음을 확인했으며, 여러 방법을 적용해봐도 해결되지 않았다.
하지만 이번에도 연주님께서 해결법을 찾아오셨다.

res.redirect(`/url?token=${토큰}`);

redirect의 주소에 토큰을 실어나르면 됐다. 확인을 해보니 토큰이 제대로 전달됐고 구글, 페북, 깃헙 모두 빠르게 oAuth를 적용할 수 있게 됐다.

그 외

  • 로컬에서 잘 작동하던 코드가 EC2에서는 작동하지 않아서 원인을 찾았다. import할 모듈이 없다는 에러메시지를 보고 해당 라인을 읽어보니 대소문자가 틀려있었다. 도대체 어떻게 로컬에서는 작동했는지 의아했다.
  • 비슷한 일이 또 일어났다. 이번에는 EC2에서 폴더명의 대소문자가 로컬의 그것과 달라서 작동하지 않음을 배포 직전에서야 알게 됐다.(성국님께서 원인을 찾으셨다.) 알고 보니 Git이 case insensitive 하다는 사실을 알게 됐다.
  • 전날까지 EC2에서 제대로 작동하던 코드가 다음날에는 작동하지 않았는데 tsconfig에서 target을 ES6에서 ES5로 바꿨더니 안된 거였다. (이 또한 성국님께서 찾으심.) 다시 ES6로 바꿔서 코드는 잘 돌아가게 됐지만 무슨 이유 때문에 결과가 달라지는지는 아직도 모르겠다.

팀원으로서

이전에 채용지원 담당자 분께서 '사이트 못생기면 1초컷'이라고 하셔서 걱정이 많았다.
'내가 아무리 백엔드 작업을 잘해놔도 프론트엔드 팀원이 못하면 어떡하지'라는 생각이 들었기 때문이다.
하지만 이번 프로젝트는 사이트가 미려하게 잘 나와서 오히려 내가 이득을 본 느낌이다. 이번 프로젝트의 모든 페이지가 이쁘고 멋있다. 조원들께 감사하다.

성장한 나로서

나는 스스로에게 쉽게 만족하지 않는 편이다. 이번에도 예외가 아니다.
'socket io로 채팅방 기능도 만들고, docker 배워서 무중단 배포도 해보고, 댓글 기능도 구현하고, admin 페이지도 만들고 싶다'라는 생각이 머리에서 떠나질 않는다.
'조금 더 빨리 초기 세팅을 했더라면, 조금 더 빨리 join 문제를 알아차렸다면'이라는 아쉬움이 남는다.

하지만 그럼에도 불구하고 기쁘다.
나는 늘 내 자신을 slow-starter라고 여겼는데 이번 프로젝트에서는 다양한 스택들을 짧은 시간 내에 배워서 구현까지 해냈다. 이 점은 스스로 뿌듯하다.

가장 크게 얻은 수확은 여러 지식도 있지만, 이전까지는 '내가 뭘 모르는지조차 모르는 상태'에서 이제는 '내가 뭘 모르는지 아는 상태'가 되었다는 점이다. 어제 프로젝트를 마무리하며 '앞으로 공부해야 할 점'을 노트에 적어내려갔는데 빼곡히도 적었다. '지식의 섬이 커질수록 무지의 해안선이 늘어난다'는 말이 떠올랐다.

프로젝트가 무사히 끝나서 감사할 따름이다.

profile
블로그 이사갔습니다. https://chachagogogo.github.io

0개의 댓글