위코드에서의 1차 프로젝트 회고(Hangover / VIVINO)
- 1차 프로젝트에 대한 회고
- vivino사이트를 뼈대로, 새롭게 컨셉을 잡은 Hangover라는 사이트 개발에 백엔드개발자로서 참여
- 시연영상 : https://www.youtube.com/watch?v=KVso2gmJGH8
- 백엔드 github : https://github.com/wecode-bootcamp-korea/32-1st-Hangover-backend
- 내가 맡았던 기능
- 메인페이지(캐러셀)의 상품리스트
- 검색창
- 특정 카테고리와 일치하는 검색어가 입력될 경우, 해당 카테고리에 속하는 상품을 반환
- 카테고리에 일치하는 상품이 없을 경우, 검색어가 상품명에 포함되는 상품을 반환
- 검색어가 상품명&카테고리에 포함되지 않을 경우, 추천검색어를 반환
- 필터페이지
- 가격, 주종, 잘어울리는 음식, 원산지 등 다양한 필터링에 관한 정보를 받아, 필터링된 상품리스트를 반환
- 상품리스트를 정렬하기 위한 정렬기준을 받아, 정렬조건에 맞게 리스트를 정렬하여 반환
- API서버 AWS배포
프로젝트의 시작!
어느순간부터 위코드에서의 시간이 급격히 흐르더니, 덜컥 두번째 달이 되면서 그와 동시에 프로젝트가 시작되었다. 갑자기 팀원들과 클론을 진행할 사이트가 정해지고, 갑자기 자리가 바뀌더니, 갑자기 냅다 프로젝트가 시작되었다.
프로젝트를 시작할때의 기억을 되감아보면, 기대되는 마음과 얼떨떨한 마음을 함꼐 가지고 있었다.
기대되었던 부분은 그동안 배웠던 것들을 실제로 사용해볼 수 있다는 점. 특히 부트캠프 초반에는 희미했던 장고의 CRUD나 테이블들간의 관계 같은 것들이 프로젝트를 시작할 때 즈음에는 점점 선명해지면서 꽤나 자신감이 생겼고, 배웠떤 개념들을 조합해서 어떤 결과물을 만들어보고 싶은 마음이 뭉게뭉게 생기게 되었다.
그러나 한편으로 얼떨떨한 마음을 주었던 것은, '겨우 이정도 배운것들로 무언가를 할 수 있단 말이야?' 라는 생각이였다. 어려웠다기보다는 생소했을 뿐인, 그래서 습득하기는 사알짝 버거웠으나 습득하고 보니 꽤나 얕은 지식들(물론 매우 가치있지만)만을 가지고 사이트를 개발한다는 것 자체가 조금은 시기상조처럼 느껴졌다.
아무튼 그런 부담감 반, 설렘 반으로 탑승하게 된 1차 프로젝트는, 팀원들이 옹기종기모여 참고할 사이트를 관찰하는 것으로 출항 신호를 알리게 되었다.
HANGOVER from VIVINO
우리팀이 뼈대로 삼은 사이트는 와인을 판매하는 커머스 사이트인 VIVINO.
팀원들끼리 웅성웅성거리면서 VIVINO사이트를 관찰하다가 주류판매를 목적으로 하는 VIVINO사이트를 숙취라는 관점으로 재미있게 풀어보자는 아이디어가 나왔고, 이를 바탕으로 우리 사이트의 이름이자 팀명은 HANGOVER가 되었다!
목표의 정리
사이트의 컨셉을 정하고나서 한 일은 목표, 즉 구현하려는 기능들을 정하는 일이였다. 사실 여기서는 뭔가 깔끔하게 의견이 합일되지가 않았다. 다들 처음이다 보니 우리가 어디까지 할 수 있을지도 잘 모르는 상태였고, 팀원들의 목표에 대한 관점도 달랐다. 우리가 처음이기도 하고, 많은 걸 하려다 실패하면 핵심기능조차 구현하지 못할 수도 있을테니, 최소한의 것들만을 목표로해보자는 의견도 있었고, 반대로 의욕과다로 무엇이든 할 수 있다!(나인듯..)를 외치는 의견도 있었다.
결국 정해진 결론은, 반드시 구현할 필수 구현목표와 시간이 남으면 도전해볼 추가 구현목표를 분리시키자는 것이였다. 없어서는 안될 기능들을 먼저 구현하고, 그에 덧붙이면 멋진 혹은 해보고 싶은 기능들을 추가해보자는 것. 다들 동의했고 이에 따라 필수구현사항과 추가구현 사항을 다음과 같이 정리하였다.
필수 구현 목표
- 메인페이지(Carousel)
- 필터페이지 : 비비노 사이트의 핵심기능인, 제품에 대한 아주아주 상세한 필터링
- 상품 디테일 페이지 : 상품에 대한 디테일 페이지. 해당 상품에 대한 상세한 정보 및 리뷰/평가를 제공
- 각 리뷰에 대한 다른 유저들의 좋아요 및 댓글기능
- 로그인 / 회원가입
추가 구현 목표
- 상품 검색
- 로그인 API사용(카카오,네이버)
- 장바구니
- 프로필
- AWS 배포
필수 구현목표와 추가 구현 목표를 분리했던 것 자체는 여전히 참 잘했다고 생각한다. 다만 아쉬웠던 점은 스스로의 역량조차도 모르다보니 팀 전체의 역량은 더더욱 인지하지 못했다는 점. 특히 프론트의 입장을 참 모른다는 걸 느꼈다. 물론 첫번째 프로젝트이기도 하고, 백엔드 관련 지식만 머리에 넣는다고 바빴으니 스스로를 자책할 생각은 전혀 없다.
다만 '우리가 할 수 있는 만큼만 구현하자'가 규칙이 된 상황에서, 개인적으로 많은 걸 해보고 싶다보니 이것저것 해보자고 한 것들이 참 많았다.내 입장에서는 대부분의 기능들이 CRUD라는 틀안에 있어서 유사하다고 느꼈고, 그래서 대부분의 것들을 할 수 있다고 판단해서 많은 걸 해보자고 얘기했던 것 같은데 프론트분들에게는 좀 기분 나쁘게 들리셨지 않을까 싶다. 프론트쪽에 대한 무지에서 비롯된 무례가 아니였을까? 프론트의 입장을 이해하는 게 선택이 아니라 필수라고 느꼈고, 결국은 그들의 입장을 이해하고 공감하기 위해 프론트와 관련된 지식역시도 차차 정복해 나가야 한다고 느꼈다.
모델링
구현해야할 목표가 정해지고 나서는 모델링을 시작했다. 생각보다 정말 많은 시간이 모델링에 사용되었다. 첫주의 절반정도? 뭔가 급한데 본격적인 작업을 시작하지 못하는 느낌이라 초조함을 살짝 느끼기도 했다. 물론 프로젝트가 끝난 지금은 모델링이 너무 중요하다는 걸 알아서, 2차 프로젝트때도 모델링을 꼼꼼하게 하기 위해 많은 시간을 사용할 듯 하다. 여담이지만 우리팀 모델링 진짜 잘됐다. 단 한번도 갈아엎은 적이 없음! ㅋㅋㅋ
모델링 과정
- 우선은 커머스 사이트이다 보니, 상품을 중심으로 모델링을 진행하였다. VIVINO사이트는 정말 상세한 필터링이 핵심기능인데, 그 기능을 수행하기 위한 여러가지 카테고리들을 함께 생성해주었다.
- 그 다음은 리뷰를 중심으로 모델링을 진행하였다. 역시나 비비노 사이트의 핵심기능 중 하나인데, 상품을 구매한 유저는 해당 상품에 대한 평가(점수)와 함께 리뷰를 남길 수 있다. 또한 그 리뷰에 대해서 다른 유저들은 좋아요를 표시하거나, 댓글을 달 수 있다. 이러한 기능들을 고려하여 좋아요 테이블이나 댓글 테이블등을 함께 구현하였다.
- 마지막으로 상품을 구매하고 리뷰를 남길 유저를 위주로 테이블을 생성하였다. 또한 상품구매를 위한 장바구니 테이블을 함꼐 구현하였다.
역할의 분배
모델링이 완료되고 나서는 본격적으로 django를 활용해 로직을 만들어나갔다. 같은 팀의 백엔드 개발자분과 얘기한 결과, 우리가 구현해야할 기능은 크게 두가지로 분류할 수 있었다
- 리뷰, 댓글, 장바구니 등 CRUD에 집중된 기능
- 필터 / 검색창 / 캐러셀(Carousel)등 특정한 조건에 부합하는 상품의 리스트를 반환하는 기능
개인적으로 일반적인 CRUD보다는 조금 더 새로운 것들을 해보고 싶은 마음이 있었고, 같은 팀의 백엔드분과 잘 얘기가 되어서 내가 필터와 관련된 기능을 담당하게 되었다.
첫번째 어려움 : 좋은 코드의 기준
본격적으로 필터를 위한 view를 만들어나가는 과정은 무난했다. 살짝 시간적으로 빡빡함을 느끼긴 했으나 하나하나 찾아가면서 해나가는 것들이 꽤 재밌었다. 특히 annotate나 aggregate같은 것들을 사용하면서, 적어도 해당 프로젝트 내에서 목표한 기능의 '구현'만을 목적으로 한다면 어떻게든지 일단 해낼 수는 있었다.
맞다.. 구현만을 목적으로 한다면 솔직히 그렇게까지 힘들지 않았을 것이다... 꽤 고민했던 부분은 다음과 같은 상황에서 발생했다.
특정 상품에 대한 리뷰가 존재할 경우, 해당 상품의 리뷰들 중에서 좋아요를 가장많이 받은 리뷰를 선택해서, 그 리뷰를 해당 상품과 짝을 지어주려는 상황
처음에는 해당 기능을 수행하기 위한 코드를 간결하고 압축적으로 만들어 내려고 했고, 고민고민 끝에 다음과 같이 코드가 만들어 졌다.
if product._Review.all(): #product의 Review가 있다면
review = product._Review\
.annotate(review_likecount=Count('reviewlike'))\
.filter(review_likecount=max(Review.objects\
.annotate(review_likecount=Count('reviewlike'))\
.filter(product_id=product.id).values_list('review _likecount',flat=True))).first()
그리고 위의 코드의 내용을 설명하면 다음과 같다.
- 만약 어떤 상품의 리뷰가 존재한다면,
- 그 상품의 모든 리뷰를 선택해서
- 각각의 리뷰가 좋아요를 받은 개수에 대한 정보를 가지고 잇는 필드를 만들고
- 만들어진 해당 필드의 값을 모두 1차원 리스트로 배열한 후
- 그 중에서 최대값을 뽑고
- 그 최대값과 일치하는 좋아요 개수를 가진 리뷰들 중에 첫번째 리뷰를 선택해서
- 해당 상품과 짞을 지어준다.
음..솔직히 해당 코드를 만들고, 의도한대로 작동하는 것을 확인하고 살짝 뿌듯하긴 했지만 보면 볼수록 의구심이 들었다. 이게 맞는 건가? 위의 코드를 볼때마다 헷갈리고 어려웠다. 간결하고 깔끔한 코드가 좋다지만, 너무 간결해진 나머지 코드를 이해할 수 없을 정도라면 오히려 길게 쓰는게 훨씬 낫지 않나? 라는 고민을 하기 시작했고, 결국 다음과 같이 코드를 늘려서 적었다.(최종 코드라 살짝 기능이 다르긴 하다)
for product in products_list:
if product.reviews.exists:
reviewlike_dict = {
review.id:review.reviewlike_set.count() for review in Review.objects.filter(product_id=product.id)
}
if max(reviewlike_dict.values()) < 2:
review = None
else:
likemost_review_id = max(reviewlike_dict,key=reviewlike_dict.get)
review = Review.objects.get(id=likemost_review_id)
이부분에 대해서는 아직도 정답을 모르겠다. 개인적으로는 간결한 것 보다는 이해하기 쉬운 코드가 더 좋은 코드라고 생각이 드는데, 조금 더 고민이 필요한 부분이라 생각한다. 사실 이 부분에 대해서 조금 더 적극적으로 찾아보지 않은 이유는, 사실 좋은 코드의 기준에 있어 '성능'이 제일 큰 역할을 하지 않을까 싶어서이다. 심지어 2차 프로젝트 때 코드를 성능적으로 비교하고 개선하는 세션이 있다고 하니 좋은 코드의 기준을 찾는 일이 그때까지 밀린 느낌이다. 다만 결국에는 성능과 함께 가독성, 간결함, 이해하기 쉬움 등등을 함께 고려해야 할테니, 해당 세션을 듣고나서 한번 더 깊게 고민을 해볼 생각이다.
두번째 어려움 : 소통
두번째로 느낀 어려움은 바로 소통이였다. 우리팀이 소통 그 자체를 꽤 중요하게 여기고 의도적으로 많은 시간을 할애했는데도 불구하고, 대화가 끝나고 나면 서로가 이해하고 있는게 일치하나? 라는 순간이 좀 있었다 ㅋㅋㅋ
서로가 어디까지 해줄 수 있는지를 모른다는 것이 가장 컸다. 예를 들어, 백엔드에서 상품의 리스트를 요구조건에 맞게 깔끔하게 필터링&정렬해서 보내줄 수 있는데, 프론트 입장에서는 프론트대로 필터링이나 정렬을 고민한다든지의 경우.(물론 그런식으로 처리할 떄도 있긴 하지만)
무엇보다 서로가 서로의 용어를 모른다는 것도 꽤나 큰 BLOCKER였다. 내입장에서는 fetch나 component가 무슨 말인지 도통 이해를 못하다보니, 프론트분들의 이야기를 이해못하는 경우도 좀 있었다.
또 처음에는 내일에 바빠서 알아서 요청을 줄테고, 요청이 들어오면 적절한 응답을 내릴 수 있는 로직만 구성하면 되겠지~라는 생각을 한적도 있었다. 근데 아니더라. 기능은 절대 혼자서 만들어지는 게 아니고, 각자가 일이 어떤식으로 얼마나 진행이 되고 있는지, 최소한 어느 부분에서 고민을 하고 있는지에 대해서 이해하는 게 너무너무 중요하다고 느꼈다. 내가 서버에서의 로직을 완성해도, 프론트가 그에 맞춰주지 않는다면 무슨 소용일까? 팀 전체의 진행속도나 방향같은 걸 계속해서 인지하려고 하는 것이 중요하다고 느꼈다.
프론트와 백이 논의했던 것들
- 메인⇒ 로그인- 회원 가입이 동일한 컴포넌트에서 진행하는 것 그런데,이때 백으로부터 토큰하고 무엇을 받아서 이용해주면 좋을지
- 검색기능을만들때 데이터를 어떤식으로 보내줘야 하는지? URL? 쿼리파라미터?
- 메인페이지의 캐러셀에서 데이터요청을 한번에 할 것인지, 아니면 두번에 나눠서 해야하는 할 것인지?
- 상품리스트의 필터링 및 정렬은 프론트와 백 모두에서 가능한데 누가 어디까지 담당하는 게 맞는 것인지?
- 수정(update)의 로직
- 리뷰를 파라미터를 어떻게 받아올 것인지 , 리뷰를 어떤 기준으로 받을 것인지
- 프론트에서 데이터가 null로 들어올 때 어떻게 처리할 것인지
사실 소통이라는 게 참 어렵다. 쉬울때는 이것만큼 쉬운 게 없나 싶겠지만. 가볍게 사용하는 소통이라는 말 아래에는 상대를 향한 꾸준한 배려라든지(심지어 좀 극한의 상황에서도), 작은 일을 할때에도 타인의 입장에서 한번 생각해보는 귀찮음을 감수한다든지.. 처음 보는 사람들과 함께 어떻게든 굴러가기 위해 삐걱대는 잡음들을 억지로 무시해야 될때도, 또 때로는 책임지고 나서서 고쳐야할 때도 있고 말이다. 소통이라는 게 안될때는 또 엄청나게 안되는 것이지 않나ㅋㅋㅋ 뭐 그런의미에서 나와 같이 꾸준히 소통해주신 팀원들에게도 진심으로 감사하며 ㅋㅋㅋ 코드를 치는 것만큼이나 소통을 가치있게 여길 줄 아는 개발자가 되어야 겠다고 느꼈다.
프로젝트의 완성
아무튼 이런 어려움을 뒤로하고, 결국은 마지막날 즈음에 전체적으로 잘 마무리가 되었다. 마지막에 프로젝트를 합치면서 참 많이 웃엇는데 나도 모르게 피어오르는 뿌듯함을 부정할 수가 없더라. 정말 많이 느낀점은 남들과 함께 무언가를 완성하는데 있어, '협력'만을 위해서 사용되는 시간이나 에너지를 아깝다고 생각해서는 절대 안되겠다는 것. 단순히 소통이나 그런 것들이 아니라, 팀의 실수나 부족한 부분을 매워준다든지 혹은 약간 별개의 일(디자인, 영상..)등에 대해서도 할 여력이나 에너지가 있다면 나서서 하는 것이 훨씬 더 팀을 유연하게 만들어준다는 것을 느꼈다. 물론 그 정도라는 것이 있겠지만, 이러한 것들을 아깝게 여기지 않고 가치있게 여길 수 있다면 적어도 '소통'이라는 부분에서는 꽤나 괜찮은 동료가 될 수 있지 싶다.
생각해볼 부분1 : 기능의 재활용
앞서 말씀 얘기한 것처럼 비비노 사이트의 핵심기능 중 하나가 , ‘니가 어떤 조건의 술을 원하든지 반드시 하나는 찾아주겠다’ 라는 집념이 보이는 굉장히 상세한 필터링이였다. 따라서 그 기능을 수행할 수 있는 '조건에 부합하는 상품의 리스트를 반환하는 기능'을 가진 로직을 작성하고자 하였다.
그런데 기능을 구한하면서 느낀게 '특정조건에 부합하는 상품의 리스트를 요구하는 경우'가 필터페이지를 제외하고도 꽤나 많이 존재하는 것을 확인했다.
- 메인 페이지의 캐러셀
- 필터페이지
- 검색창(검색어라는 조건에 부합하는 단어를 반환)
결론적으로 이처럼 유사한 기능을 요구하는 경우에는 하나의 view로 구현을 할 수 있다면 하는 것이 좋지 않나 라는 결론을 내리게되었고 , 실제로 그렇게 구현을 하려했다. 물론 마지막에는 멘토님의 조언을 받아 검색기능은 따로 분리시켜서 로직을 작성했지만. 아무튼 다른 페이지에 있는 기능은 별개의 기능이라고 생각했는데, '데이터의 흐름'이라는 관점에서 인지하는 것이 중요하다고 느꼈다. 다른 페이지라 할지라도, '데이터의 흐름'이 유사하다면 동일한 뷰(로직)으로 처리할 수 있도록 하자!
생각해볼 부분2 : API문서
바로 위의 내용과 이어지는 부분이다. 하나의 로직이 다양한 상황에 대처할 수 있도록, 즉 코드의 활용성을 높이기 위해서는 API를 사용하는 사용자(보통 프론트 개발자분들)로부터 다양한 변수를 받아야 한다고 느꼈다. 조건에 부합하는 상품을 반환해야 하는 상황이라 해도, 어떤 경우에는 pagination기능이 있을 것이고, 또 어떤 경우에는 정렬 조건이 없을 수도 있다. 이러한 상황에 유연하게 대처할 수 있는 로직을 만들고 그 로직이 100% 온전하게 사용되려면, api는 (최소한 그전보다는)더 복잡해질 수 밖에 없다.
결국, 유저들의 이해를 위해 해당 api를 상세히 설명해주는 api문서가 필요하다고 느꼈다. 그래서 설령 조금 복잡한 api라 해도, api이용자가 백엔드 개발자의 도움 없이 혼자서 api를 100% 활용할 수 있도록 인도해주는 깔끔한 API문서를 작성하는 것 까지가 실질적인 api의 완성이 아닐까 한다.
잘했던 점들
- 깔끔한 모델링 : 우리팀의 모델링이 굉장히 깔끔했다. 멘토님들께서도 정말 깔끔하게 잘되었다고 말씀해주시기도 했고. 또 다른 팀들의 얘기를 언뜻언뜻 들었을 때 모델링에 문제가 있어서 수정을 하거나 새로 갈아 엎은 적이 몇번 있다고 했는데, 모델링과 관련해서 문제가 발생한 적은 단 한번도 없었다.
- 필수구현사항의 완수 : 내가 처음에 하려했던 필터기능의 구현은 무사히 마쳤음. 또 Q객체라든지 annotate나 aggregate등 필요한 것들을 마주칠 때마다 혼자 검색해서 구현해낸 부분도 약간 뿌듯하다. 구글 당신은 신이야!
- Git에 어느정도 익숙해졌다. 다만 실수가 발생하기 쉬운 부분이니 주의가 필요하고, commit의 단위 조절 및 커밋규약에 대해서 어느정도 규칙이 필요할 듯 하다. 너무 커밋이 많아지는 것 아닌가 싶기도 하고.
- 검색과 관련된 추가기능을 구현했다.(좀 미숙하긴 하지만) 그리고 elastic search라는 키워드도 접했는데, 한번 찾아볼 생각
- AWS를 어떻게든 구현해서 서버를 띄웠다. 해당 서버내에서 작동이 된다는 것도 확인했고. 무엇보다 2차 프로젝트때는 어느정도 merge가 된 부분을 빠르게 aws로 띄워서 프론트분들이 더이상 서버를 찾지 않아도 되도록 해주고 싶은 마음 + 약간 aws에 대해서 감을 잡은 느낌이다.
- 나와 함께 백엔드 포지션을 맡은 분과 꾸준히 소통하고, 서로가 맡지 않은 기능에 대해서 서로가 코드를 보면서 모르는 부분을 질문했다는 점.
아쉬웠던 점들
- 구현하지 못하거나 사용해보지 못한 기능들이 좀 있다.
- ERUM , Transaction, bulk_create
- kakao / naver 로그인 연동
- 구글맵 등등
- 프론트와 관련된 개념을 좀 더 명확하게 정리해두지 못한 것
- 아쉬움이 크게 남는 부분
- 프론트분야에 대한 공부의 필요성을 느끼기도 했다.
- API문서를 제대로 만들지 못했다.
- Postman을 활용하여 간단하게 만들긴 했지만, 사실 '나 만들기는 했어요~'하는 수준.
후기
난관일 거라 생각했던 기능의 구현보다 프로젝트의 기획단계나 팀원들과의 소통이라는 분야에서 어색함을 많이 느꼈던 프로젝트였다. '첫번째 프로젝트 였으니까, 첫번째 협업이였으니까' 와 같은 변명들도 함께 떠오르긴 하지만, 그래도 여러가지 잡음을 내면서 굴러갔던 우리의 첫번째 프로젝트를 반추해보면 더 깔끔하게 잘할 수 있었음을 부정할 수가 없다.
한편으로는 많은 것을 배울 수 있었다. 특히 팀과의 시너지를 내기 위해 코드를 치는 것만큼이나 중요한 것이 소통이고, 그렇기 때문에 소통을 위해 의도적으로 시간과 노력을 들여야만 한다는 교훈을 얻었다는 점이 참 만족스럽다. ㅋㅋㅋ 이렇게 말하니까 뭔가 우리팀의 소통이 잘안됐던 것 같은데, 오히려 '미리 잘 얘기하고 잘 정해둬서 진짜 다행이다' 싶은 경험이 많았어서 소통의 중요성을 더 크게 느꼈던것 같다.
아쉬움,곤란함, 뿌듯함 등 만감이 교차했었던 첫번째 프로젝트였지만 그래도 기간내내 참 즐거운 시간이였다.
멋진 팀원들과 수고한 나에게 칭찬과 박수를!