렌카는 렌터카 중개 O2O 서비스 플랫폼 개발 및 공급사업을 하는 기업입니다. 렌터카를 요청하는 렌카 앱웹과 렌터카를 제공하는 IMS 앱웹을 개발하는 프로젝트를 진행하였습니다.
주로 보험사 직원이 사용하는 요청자 웹앱(동영상 오른쪽)에서 요청을 하면 주로 렌터카 업체에서 사용하는 제안 웹앱(동영상 왼쪽) 목록에 요청이 뜨고 제안을 하면 요청자가 선택하여 1:1 매칭해주는 플랫폼입니다. 요청부터 반납까지 비즈니스 로직에 따라 서버와 통신하며 프로젝트를 진행하였습니다.
1) Next.js(함수형, 클래스형)
2) Mobx
3) Sass
4) Socket.io
1) Flask
2) Database Modeling (AQueryTool)
3) AWS
1) Git / Github / Git-flow
3) Slack
4) Notion
팀원들과 회의 후 회원가입/로그인 페이지는 클래스형 컴포넌트를 사용하기로 했습니다.
서버에서 받은 회사 리스트를 검색 로직 구현
생산성을 높이기 위해 Input 컴포넌트 재활용
Mobx를 활용해 state 관리
search.js
삼항으로 조건을 주고 공통 Input 컴포넌트에서 props를 수정 가능한 state로 만들었다.
input component
공통 콤포넌트의 input은 onChange에 삼항을 주어서 선택된 카드가 수정 가능한 상태로 만들었다.
예약이 확정되면 고객과 업체가 채팅이 가능하다. 원래는 댓글로 되어있던 부분을 팀 회의를 통해 채팅 기능으로 수정하기로 결정했고 Socket.io를 통해 TCP 통신을 했다.
서버와 채팅 페이지를 진행하면서 몇 가지 문제가 생겼다. 먼저 Socket.io의 이해도 부족으로 인해 메시지가 쌓이면 느림 현상이 발생한 것. 메시지 10-15회 이상 주고 받으면 서버로 부터 엄청나게 많은 요청이 들어오게 된다.
chat.js
connect 이후 join으로 id와 name을 서버로 전송하는데 connect와 join은 최초 렌더만 발생하고
messages를 on으로 받을 때마다 useEffect
를 실행하는데
메시지를 보낸 수만큼 서버에서 response가 들어온다. 백엔드 팀원과 room id, socket.id, 등 문제를 해결하려고 했지만 결국 해결하지 못한 채 부채로 남았다.
요청자(왼쪽)에서 제안을 선택하면 제안자(오른쪽)쪽에서 제안중 탭에 있던 카드가 예약확정으로 넘어간다.
SSR(Server Side Rendering)으로 초기 View 로딩 속도 향상
Axios Custom Instance를 활용해 에러 선처리
Next.js의 동적 라우팅을 이용해 효과적인 routing
Next.js의 경우 디렉토리 구조로 곧 route가 되기 때문에 index.js파일에 [id].js
로 잡고
카드 클릭시 상세 페이지로 넘어가는 함수
router.push
를 해주면 해당 페이지로 넘어갈 수 있다. 여기서 문제가 생겼는데 제안이 확정된 이후부터는 id값이 달라진다. 무슨 말이냐면 요청자에 의해 제안이 확정 되기 전에는 request_id
를 기준으로 end point를 잡았기 때문에 상세 페이지로 이동하기 위해서 해당 카드의 request_id
를 endpoint로 API를 요청했다.
하지만 예약이 확정된 후 백엔드의 모델링 때문에 예약확정 상세페이지에 가기 위해서는 suggestion_id
를 end point로 써야 했다. request_id
를 가지고 있던 상황에서 suggestion_id
도 가져와야 했기 때문에 suggestion_id
를 매번 Mobx에 state로 관리하는 불편한 상황이 발생했다.
하지만 router.push
를 하게 되면 getServerSideProps
의 context.query
로 id값을 가져올 수 있음을 알게 되었고 suggetion_id
를 넣어 문제를 해결했다.
예약 확정 상세 페이지 부터request
와suggestion
두 개의 데이터가 들어오는데 여기서 문제가 발생했다.
모델링에 따라 requst 데이터 안에 suggestion의 키값으로 데이터가 들어왔는데 Proposal 컴포넌트도 공통으로 사용하다 보니 suggestion을 키값으로 잡고 데이터를 받아오면 삼항 연산자가 두 번 연달아 쓰여야 할 불편함이 생겼다.
삼항 연사자를 두 번 쓰면 에러 확률이 높을 수 있다고 판단하여 백엔드 팀원과 상의 후 request, suggestion 키값으로 병렬 구조로 데이터를 받기로 하고 해결되었다.
-Mobx에 stat를 관리하고 POST로 서버로 데이터 전달
요청하기 완료 시 제안이 없을 때
제안이 생성되었을 때
제안을 선택했을 때
요청하기가 완료되면 요청이 제안자 쪽에 등록된다. 제안자가 제안을 하면 요청자에 제안이 생성된다. 요청자가 제안을 선택하면 제안이 등록되고 1:1 매칭이 된다.
제안탭의 최초 상태는 "업체 제안을 기다리는 중입니다" 빈 페이지고 제안자가 제안을 넣으면 제안 카드가 나온다. 이 부분은 삼항으로 처리했다. 이제 선택을 누르면 선택된 제안에 관한 데이터가 담긴 UI 컴포넌트로 변해야 한다. 이 부분에서 문제가 생겼다.
일단 채팅, 제안, 요청서, 히스토리
탭을 route하는 로직을 짰을 대 제안을 선택 후 UI가 한 번 더 변하는 부분을 생각하지 못했다.
tab.js
tab 상수를 만들어서 url키를 을 end point로 잡았다. 이렇게 tab을 짜면 선택 후 url이 달라졌을 때 다른 탭에서 제안 탭으로 돌아오는 것이 불가능해진다. 요청 후 page 전환이 아니라 모달을 이용해서 바꿔보려고 했지만 시간적 제약으로 page 전환으로 그대로 진행하였다.
해결방법으로 조금 무식하지만 선택하기를 클릭하고 제안이 선택됐을 때 서버로부터 받은 request_id를 local storage에 저장했다. 그리고 local storage에 request_id의 유무를 분기점으로 잡고 tab 로직에 추가해주었다.
이런 식으로 request_id가 현재 서버로부터 받은 requst의 id가 일치 할 때 변화될 UI인 reservation을 router로 탈 수 있게 분기점을 만들었다.
제안자 배차 완료
제안자 배차 포기
제안자 반납 완료
요청중, 예약확정, 배차중, 반납완료
의 순으로 제안자와 요청자가 이벤트를 만들 때마다 UI가 달라져야 한다. 처음에는 모든 조합을 하나의 객체로 만들어서 객체를 바꿔주는 식으로 어렵게 접근했는데 생각보다 쉬웠다. 먼저 4단계로 컴포넌트를 만든다.
그리고 status 값을 키값으로 주고
props로 받은 status 값만 넣어주면 변화한다! 여기서 더 응용하면
이런 식으로 조건에 따라 UI가 계속 달라질 때 경우의 수를 객체로 만들 수 있다.
Next.js라는 새로운 스택을 바로 프로젝트에 적용해야 했다. 리액트 이후 새로운 스택은 처음 배우는 것이라 헤맸다. 그래도 2번의 프로젝트를 진행하며 어느 정도 스킬이 생겼는지 생각보다 쉽게 Next.js를 적용할 수 있었다.
먼저 공식 문서를 보며 튜토리얼을 따라 만들어본다. 튜토리얼엔 가장 필수적인 기능이 들어가있다. Next.js의 경우 router와 Server Side Rendering 위주로 튜토리얼이 짜여있다. 튜토리얼을 짜보면서 기존에 알고 있던 스택(react)와 차이점을 비교하면서 두 세번 하나의 기능을 만들어보았다. 그랬는데도 사실 감이 잘 오지 않았다. 감이 잘 오지 않았다기보다 프로젝트의 바로 적용하는 데 두려움이 있었다. 그래서 며칠을 더 공부를 했었는데 지금 생각해보면 프로젝트에 적용하면서 배워도 괜찮았을 것 같다는 생각이다. 왜냐면 프로젝트에 적용하지 않고 프레임워크를 공부하다보니 배워야 할 것이 끝이 없었다. 이것도 알아야 할 것 같고, 저것도 알아야 할 것 같고... 계속 배워야 하는 게 나오다보니 프로젝트 시작이 늦어진 감이 있었다.
가장 기초적인, 필수적인 부분만 훑고 프로젝트에 적용하면서 필요한 부분만 계속 학습하는 방식으로 새로운 스택을 배우는 편이 좋을 것 같다. 새로운 전략을 수립했다.
리덕스를 사용하지 않고 Mobx를 사용했다. 리덕스보다 간편한 느낌이 있었다. Mobx는 클래스형, 함수형 컴포넌트에 적용하는 하는 방식이 다른데 Mobx를 제대로 파기 위해 클래스형과 함수형 컴포넌트를 둘 다 만들어 보았다.
Mobx는 리덕스보다 더 직관적이고 러닝커브가 낮아서 금방 적용할 수 있었다.
1차 프로젝트를 진행하면서 채팅에 대한 욕심이 생겨서 input을 넣으면 배열에 있는 메시지를 순서대로 뱉는 말도 안 되는 채팅을 만들었다. Socket.io로 TCP통신을 하면서 진짜 채팅을 만들게 되어 너무 즐겁게 배웠다.
일단 어려웠던 점은 기존의 HTTP 통신 방식과 다르다는 것이다. Request, Response 를 주고 받는 방식이 아니라 Request과 곧 동시에 Response가 된다. 그래서 백엔드와 맞춰볼 때도 키값만 맞추면 됐던 HTTP 통신과 다르게 옆에 딱 붙었어서 어떻게 메시지가 가고 어떻게 메시지를 받을 수 있는지 실시간으로 확인하지 않으면 굉장히 힘들었다. 실제로 민영님(백엔드 팀원)과 처음 소켓 통신을 할 때 zoom으로 맞춰보았는데 진전이 하나도 없었다.
또한 소켓 통신이 어려운 점은 프론트, 백엔드 서로가 양쪽 코드에 대한, 최소한 어떤 플로우로 진행되는지 이해가 있어야 한다는 것이다. 실제로 나는 백엔드 플로우를 이해하기 위해 express
를 이용하여 소켓 통신 예제를 만들어 보았다.
소켓 통신은 정말 팀원의 합이 잘 맞아야 한다는 것을 깨달았다. 부족한 프론트엔드 김동하를 잘 이끌어준 민영님과 규석님께 감사하다.
기존의 프로젝트는 사용자의 입장에서 만들었기 때문에 어떻게 보면 단방향이었다. 데이터를 뿌리고 사용자가 어떤 액션을 취하면 그것에 대한 알맞은 함수를 실행하는 방식으로 기존의 프로젝트들을 진행했다면 이번 프로젝트는 요청과 제안이라는 비즈니스 로직을 기반으로 두 개의 웹 상에서 이벤트를 주고 받는 식으로 진행했기 때문에 처음에 플로우를 이해하는 데 시간이 걸렸다. 요청자/제안자 둘 다 요청중/제안중/예약확정/배차중/반납완료 라는 5개의 컴포넌트가 있고 각 컴포넌트들은 비슷하지만 다 달랐기 때문에 공통 컴포넌트를 처음에 짜는 것이 중요했다. 코드 리뷰 중 아토믹 패턴을 처음 알게 되어 학습했고 프로젝트에 적용하려고 했지만 이미 프로젝트를 많이 진행한 상태에서 다시 시작하는 데 무리가 있어 아쉽지만 그대로 진행하였다.
팀원들과 스탠딩 미팅으로 매일 할 일을 점검했다. 서로의 진행과정을 상세히 노션으로 공유했다.
공통 컴포넌트가 많았기 때문에 컴포넌트를 짜기 전에 기능 정의를 먼저 하여 공유할 수 있게 했다.
각 컴포넌트들이 비슷하면서 달랐기 때문에 텍스트와 구두로만 팀원들과 컴포넌트를 정하고 나누고 기능을 정의하는 데 한계가 있었다. 그래서 제플린을 이용하여 공통 컴포넌트와 컴포넌트의 역할을 분배했고 작업을 시작할 때 이미지에 나온 기준점을 삼아 작업했다.
상수의 경우 이렇게 constants에 선언하고 export해서 사용했다. 전체 수정하는 데 무척 용이했다.
Token을 Local storage에 담아 사용하는 것이 습관이 되었는데 Cookie에 저장해보라는 리뷰를 받았다. Local storage와 동일하게 cookie.set('token', result.data.token);
이렇게 set을 하면 됐지만 get은 조금 어려웠다.
먼저 Next.js에서 제공하는 getServerSideProps
을 사용하여 데이터를 받아왔기 때문에 context.req
에서 token에 접근해야 했는데 parseCookies
라는 메서드를 따로 사용해야 했다.
parseCookies.js
이렇게 parseCookies()
를 정의하고
header에 담아서 보내면 성공. 하지만 getServerSideProps
가 아닌 Client단에서 Cookie에 접근할 때는 라이브러리를 사용해야 했다.
이렇게 cookieCutter
라는 라이브러리로 cookie에 접근해 token을 가져왔다.
Axios를 처음 사용하면서 fetch보다 간편하다는 것을 느꼈다. JSON으로 바꿀 필요도 없고 body의 부분도 간결해졌다. 특히 Axios Interceptors
를 배우게 됐는데 데이터를 받으면 말그대로 에러를 선처리 하는 로직을 생성자 함수로 만드는 것이다.
이렇게 callAPI.js
로 선언하고 URL만 넣어주면 request와 response에 따라 server side에서 선처리가 가능하다.
두 번의 프로젝트를 마치고 실무 수준의 팀 프로젝트를 진행했다. 우여곡절이 많은 프로젝트였다.
우리끼리 진행하는 프로젝트가 아닌 대표님을 비롯한 CTO, 개발팀 앞에서 결과물을 발표해야 하는 진짜 프로젝트였다. 아무리 인턴십이지만 아마추어 같아 보이고 싶지 않았다. 혹시 에러가 나는 거 아닐까 전전긍긍하면서 발표하고 싶지 않았기 때문에 심리적 부담을 안고 시작했다.
Next.js와 Mobx라는 새로운 스택을 배우고 렌카 회사 분위기에 적응해가면서 이제 막 프로젝트를 시작하려고 하던 일주일 차에 문자 그대로 날벼락이 떨어졌다. 팀원 중 한 명이 코로나 확진이 되어 팀원 모두 자가격리 2주 통지를 받은 것이다. 시작도 하기 전에 의욕이 꺾였다. 회사에 큰 파장을 준 것에 엄청난 죄송스러움이, 2주 간 원격으로 프로젝트를 진행해야 한다는 압박감까지 여러 감정이 섞여 망했다는 생각이 먼저 들었다.
그렇게 한 며칠을 격리 상태에서 의욕도 없이 시간만 때웠다. 파이팅 넘치던 팀원들도 갑자기 달라진 상황 때문에 힘이 없어 보였다. 그러던 중 규석님이 오히려 이게 기회일 수 있다며 이런 상황에서도 최고의 결과물을 만들어 모두를 깜짝 놀라게 해 주자고 팀원들을 격려했다. 그때 서로를 격려하며 의지를 다졌고 그날이 전환점이 되어 팀원 모두 제자리로 돌아왔다. 뒤처진 시간을 따라잡기 위해 밤낮으로 코딩만 했다. 아마 1,2차 프로젝트보다 더 코딩에 쏟는 시간이 많았을 것 같다. 크리스마스 이브, 크리스마스, 새해 할 것 없이 눈 뜨면 코딩만 했다. 크리스마스 전날에 채팅의 윤곽을 잡았는데 "크리스마스에 코딩하는 외톨이가 세상을 바꾼다."라고 팀원들끼리 농담을 주고 받았다. 아마 내가 보낸 여러 번의 크리스마스 중 가장 기억에 남는 크리스마스일 것이다. 앞으로 쭉.
그렇게 2주 간 미친 듯이 코드를 치니까 격리가 끝날 때쯤 제안자 웹앱이 어느 정도 완성되었다. 규석님의 말대로 렌카 분들이 놀라셨는지 원래 계획에 없었던 요청자 쪽도 만들어 보자고 하셨고 일주일이 더 연장되었다. 우리의 노력이 보상받는 것 같아 기뻤다.
그렇게 5주 간 쉼 없이 달려 요청자 & 제안자 플랫폼을 만들었다. 계획에 없던 요청자 쪽과 채팅 페이지를 만들어 다들 놀라셨다. 일단 놀라게 하자는 우리의 목표는 성공이었다. 어두운 대회의실에서 현재님은 발표를 했고 나는 발표에 맞춰 기능을 선보였다. 몇 번을 맞춰 봤는데도 떨려서 중간 중간 손에서 땀이 났다. 그렇게 발표를 마치는데 온몸에 힘이 빠지는 기분 오랜만이었다. 대표님이 한 달 동안 만든 것이라고 믿기지 않을 정도로 잘 만들었다고 말씀해주셨다. 아, 개발자가 되기로 한 것은 내 인생 최고의 선택이다. 너무 행복하다. 대회의실을 나서는데 어서 빨리 다른 프로젝트를 하고 싶다는 생각이 들었다.
많이 배울 수 있었던 5주였다. 인턴십을 하며 배웠던 것들을 쭉 쓰는데 너무 많아 무엇부터 써야 할지 행복한 고민에 빠졌다. 앞으로 개발을 하면서 힘든 상황이 많을 텐데 어떤 방식으로 스트레스 관리를 해서 극복할 수 있을지 시뮬레이션을 해본 것 같았다. 그리고 정말 절망스러운 상황이었지만 서로 믿고 의지할 수 있는 동기들이 있어서 힘듦을 이겨낼 수 있었다고 생각한다. 진심으로. 앞으로 개발을 하며 이런 동료들을 많이 얻었으면 좋겠다. "200 OK" 에 심장이 두근거리는 동료들을 많이 만나 누군가 말했던 것처럼 우주를 놀라게 해주고 싶다. 놀라게 하는 것은 언제나 재밌는 일이다.
멋있어요 동하님 무한박수 드립니다..👏🏻