[1차 프로젝트 배민스토어 21.03.15 ~ 03.26] Project Retrospective

TK·2021년 3월 28일
10

Project

목록 보기
4/6
post-thumbnail

1. First Project 배민스토어, 우리는 어떤식으로 시작했나?

  • 팀원 5명 (프론트엔드 3명, 백엔드 2명) 으로 1차 프로젝트 배민문방구 클론 프로젝트를 시작했다.
  • 배민문방구 자체는 딱 커머스 사이트의 본질적인 기능에 충실했기 때문에 무엇을 구현해야 하는지 확실했다.
  • 팀이 정해진 첫 날 팀원 분들과 2주동안 어떤 기능을 필수로 구현하고, 어떤 기능을 추가로 구현할 것인지 Trello 에 정리하였다.
  • AqueryTool 을 이용하여 모델링 ERD 를 만들기 시작했다.
  • 실제로 모델링만 거의 일주일이 걸렸다고 해도 무방했다. 처음 3일은 모델링을 어떻게 하면 효율적으로 짤 수 있을까에 대한 고민만 했다. (어떤 테이블에 어떤 컬럼정보가 들어가야하나, 어떻게 many-to-many, one-to-many, one-to-one 관계를 설정해야 하나 를 고민하며 실제로 개발할 때 쿼리를 효율적으로 날릴 수 있도록 고민했다.)
  • 모델링을 마치고 만든 ERD 를 보면서 models.py 를 app 별로 작성하였다.

결과적으로 팀이 꾸려지고 난 뒤 제일 처음 한 것이,

  • modeling ERD 작성
  • 사이트의 기능 중 2주라는 시간을 고려하여 어떤 기능을 추가, 생략 할 것인지 에 대해 정했다.

지금 와서 느낀 것이지만,

  • 모델링 하는데 시간이 가장많이 걸리고
  • 모델링을 할 때는 무조건 확장성을 고려하여 만들어야한다는 것이였다.
  • 우리 팀이 지금 이 기능만 구현할 것이라고 해서, 모델링을 딱 그 range 에 한정해서 해버리면
  • 나중에 확장할 때 기존에 있던 model 의 틀 자체가 바뀔 가능성이 있기 때문에
  • view 자체를 통째로 수정해야 할 가능성이 있다.

--> 그래서 처음부터 모든 기능을 고려하여 확장가능한 모델링을 하려고 노력했다.

2. Agile 하게, Scrum 방식으로

우리팀은 제일 처음 약속한 방식이 있었다.

프론트, 백 따로 기능 열심히 만들다가 마지막 순간에 맞추는 일만 없었으면 좋겠다고 ..

그래서 우리는 scrum 방식으로 agile 하게 프로젝트를 진행하려는 노력을 정말 많이했다.

다음은 그 디테일을 소개하겠다.

Sprint

  • 1주동안 한번의 sprint 로 구성했으며 2주동안 매주 첫날마다 새 sprint 가 시작되었다.
  • 한번의 sprint 동안 1주간의 목표를 짜고,
  • 그 일주일 동안 해야할 일들을 세부적으로 나누어 분담했다.
  • 그리고 sprint, 말 그대로 달렸다 한 sprint 동안.

Scrum in agile

앞서 언급한 것 처럼,
sprint 를 할 때 한 기능을 죽어라 다 만들고
이후에 그것을 프론트랑 맞춰보지 않았다.

어떤 기능이 동작 할 만한 최소한의 코드가 완성되면, 공유된 키값을 바탕으로 프론트엔드랑 바로 맞춰봤다.
즉, (development & test) 흐름을 기능이 최소한으로 완성될 때 마다 계속 반복했다.

한번에 만들고 테스트를 해버리면 어딘가 잘못되면 찾기가 너무 힘들기 때문이다 ..

실제로 한번은 어떤 식으로 데이터를 주고 받을지 완벽하게 이야기가 되지 않은 상태에서 데이터 구조를 먼저 정했다가 프론트엔드(래영님) 에게 혼란을 드린적이 있었는데,

이것 역시 초기에 test 를 하지 않았으면 발견하지 못했을 부분이다. 아마 로직을 수정하느라 시간을 허비했을 것이 분명하다. 이래서 agile 한 방식이 중요하구나 하는 것을 느꼈다.

Stand-up meeting

  • 아침 11시마다 우리 팀 테이블에서 stand up meeting 을 진행하였다.
  • 개인별, 프론트엔드/백엔드 별, 팀 전체 로 나눠서 이야기 할 topic 을 정했다.
  1. 개인별 topic : 팀원 별로 돌아가면서 자기가 이 때까지 구현한 것은 무엇인지, 오늘 할 task 는 무엇인지, blocker (bottle neck) 은 무엇이였는지 에 대해 이야기했다.
    이 과정에서 서로가 어느 부분까지 진행하고 있는지 확인하며 흐름을 맞추려고 했으며, 팀원이 blocker 가 있으면 서로가 아는 부분만큼 도와주려고 애썼다.

  2. 프론트엔드/백엔드 별 topic : 그 날 프론트엔드와 백엔드가 맞춰야 할 사항들을 이야기했다.
    예를들어 백엔드 쪽에서 나는 "오늘 래영님하고는 장바구니 api 통신을 2시에 맞춰보고, 송희님이랑 3시에 카테고리 별 상품분류 페이지 api 부분 맞춰보는게 어떨까요?" 와 같이 프론트엔드 팀원분들과 api 통신을 테스트하기 위한 세부 일정을 맞추려고 노력했다.

  3. 팀 전체 topic : 팀 전체가 나아가야 할 방향에 대해 이야기했다. 2주라는 시간 안에 배민문방구의 기능들을 모두 다 구현하는 것이 사실 불가능했기 때문에, 작업상황을 공유하여 우리 팀이 어떤 기능에 더 신경써야할지 덜 신경써야할지에 대해 이야기하는 시간을 가졌다.

이렇게 stand-up meeting 을 하고나면 내가 무작정 하루동안 코딩만 하는 것이 아니라,
팀이 어디까지 왔는지 진행상황을 알 수 있고,
오늘 어느 부분에 좀 더 신경을 써야하는지에 대해 알 수 있어서 좋았다.

Trello, for project flow management

우리팀은 프로젝트의 flow 관리를 위해 Trello tool 을 활용하였다.

  • Backlog
  • This week
  • In Progress
  • Done

크게 네 카테고리로 나눠서

  • Backlog 에 프로젝트에 필요한 모든 티켓을 넣고
  • This week 에 이번주에 해야 할 기능을 옮겨놓고
  • In Progress 에 현재 작업하고 있는 기능을 옮겨놓고
  • Done 에는 PR 이 끝나 master branch 에 merge 가 된 기능들을 옮겨놓았다.

추가적으로 우리는 프론트엔드와 백엔드 간 key 값을 공유하기 위해 다음과 같이 RESTFUL API 주소명으로 티켓을 만들고 그 안에 key 값을 공유했다.

개인적으로 key 값을 trello 에 공유한 건 매우 잘한 부분이라고 생각한다.

Slack, for communication

슬랙을 통해 바로바로 필요한 정보가 있으면 공유를 하였고,
github bot 을 연동시켜서 팀 전체가 어떤 PR 을 올렸는지, 어떤 기능이 master 에 merge 가 됐는지 바로바로 확인할 수 있게끔했다.

슬랙에 깃헙 봇을 연동시키는 일은 생각보다 간단해서 금방했다.

3. Github 으로 프로젝트 버전 관리

다음으로는 프로젝트 동안 프로젝트의 버전 관리를 위해 사용한 github 에 대해서 이야기를 해 보려고 한다.

우리는 먼저 프론트엔드, 백엔드를 나눠서 repository 를 판 뒤 기능별로 branch 를 만들어서 작업했다.

백엔드는 나와 다민님 두명이여서, 각자 맡은 기능별로 branch 를 파서 작업했다.

master branch merge conflicts

github 을 쓰면서 이전에 외주를 하거나, 혼자 사이드 프로젝트를 진행했을 때 많이 해보지 못한 것들을 많이 경험해본 것 같다.

왜냐면

  • 외주를 할 때는 프로덕트 전체를 한 브랜치에서 작업했기 때문에 기능 별로 충돌 날 일이 없었다.
  • 사이드 프로젝트는 기능 별로 여러 브랜치에서 작업을 하긴 했지만, 혼자 작업했기 때문에 충돌이 날 확률이 적었다.

하지만 이번에는 나 혼자가 아니라 백엔드 팀원 분과 함께 기능을 분담하여 진행했다.
따라서 같은 app 의 같은 view 에서 작업 할 일이 잦았기 때문에 master branch 에 merge 할 때 conflict 가 발생하는 경우가 잦았다.

이번에 어떻게 merge conflict 를 resolve 하는 방법에 대해서도 자세히 알게되는 계기가 되었다.
간단하게 정리하면, 다음과 같은 순서이다.

  • master branch checkout
  • git pull origin master
  • conflict branch checkout & git merge master
  • resolve conflicts
  • merge to master branch

Pull Request

풀 리퀘스트(PR) 을 날릴 때, 어떤 식으로 리뷰요청을 남겨야 할 지에 대해 많이 고민해보는 시간이 되었다.

그 전에는 그냥 master 에 merge 해달라는 심정으로 올렸다면,
지금은 reviewer 의 입장에서 한번 더 생각해보고 PR 요청을 하도록 내 자신이 바뀐 것 같다.

프로젝트 동안 PR 요청을 하면서 왜 reviewer 의 입장에서 요청을 해야되는지 느낀점을 나열해 보려고 한다.

  1. reviewer 는 원래 내가 짠 코드를 보고 이해하는 시간이 나보다 오래걸린다.
    --> 내가 만들었기 때문에 나는 쉽게 이해하지만, reviewer 는 아니다. 따라서 왜 이런 의도로 짰는지, 질문사항은 무엇인지 PR 요청을 보낼 때 최대한 구체적으로 적어야 한다.
  2. 가독성이 없는 코드는 reviwer 를 힘들게 한다.
    --> 물론 코드가 짧아질 수는 있지만 알아보기 힘들다. 필요하다면 코드에 주석을 달거나 코드를 가독성 있게 짜야 리뷰하는데 시간도 줄고, 나중에 리팩토링 할 때 내가 보기에 편하다. 따라서 나만 알아볼 수 있는 코드는 최대한 지양해야 한다.

이 외에도 더 많은 이유가 있지만 대표적으로 두가지만 적어보았다.

PR Label management

그 전에 개발할 때는 PR 라벨에 대해서 별로 신경을 안 썼는데

지금와서 써보니, PR 라벨을 관리하는 것의 장점이 너무 많다는 사실을 깨달았다.

라벨에 status 별로 색과 이름이 다르며,
reviewer 가 달 수 있는 라벨과 (수정요청, Accepted, 리뷰진행중, conflict 해결요청)
reviewee 가 달 수 있는 라벨이 나뉘어져 있다.(도움요청, 리뷰요청, 추가기능구현중)

단적인 예로, 내가 PR 을 올렸는데 수정해야 할 기능이 급하게 생기거나 급하게 commit 을 해야 할 경우, reviewer 가 리뷰를 하기 전 추가기능구현중 으로 라벨을 바꾸면 된다.

1차 프로젝트를 진행하는 동안 라벨관리가 꽤 인상깊었다.

4. 프로젝트에서 내가 맡은 역할

1. 팀 내 역할 : Backend work flow manager

  • 나를 포함해서 두명으로 이루어진 작은 백엔트 팀에서 할 일들이 많았고, 어떤 기능부터 처리해야 나머지 기능과 결합시킬 수 있는지 판단하고 기능별로 각자의 task 를 분배하는 역할을 했다.
  • 또한 프론트엔드 팀원들과 논의하면서 기술적으로 현재 실력으로 구현 가능한 부분과 그렇지 못한 부분을 가렸다.

실제 있었던 예시를 들면,
장바구니에서 결제페이지로 상품을 이동시키는 로직이 있었다.
장바구니에 담긴 상품들을 선택적으로 결제페이지로 이동시켜야 했는데,
이 때 프론트엔드에서는 아직 전역상태관리라는 기술을 배우지 않았기 때문에
장바구니 창에서 선택된 상품을 결제 페이지 창에도 띄우는 기술을 구현하지 못한다는 것을 알고 있었다.
그래서 나는 프론트와 상의해서 백엔드 쪽에서 결제페이지로 넘어간 상품의 order status 를 pending payment 로 바꿔서 관리하겠다고 이야기했다. 물론 까다로웠지만 어쩔 수 없는 부분이였기에 그렇게 했다.

  • 프론트엔드의 프레임워크에 대한 개발지식은 부족하지만, 프론트엔드 부분을 아주 조금이나마 다룬 경험이 있었기 때문에 프론트엔드가 어려움을 겪을 때 어떤식으로 해보지 않겠느냐 라는 기술적인 조언을 꾸준히 드리려고 노력했다. 그리고 백엔드와 프론트엔드가 다르긴 하지만, 로직같은 부분은 같은 프로그래밍의 영역이기 때문에 그런 부분도 도움을 요청받았을 때 최대한 머리를 맞대고 아는만큼 최대한 도와드리려고 노력했다.

2. 기능 담당

  • 회원가입
  • 메인페이지 상품 보여주기 (구매, 할인, 신상 순으로 보여주기)
  • 카테고리 별 상품 보여주기
  • 제품 상세페이지 보여주기
  • 장바구니 담기
  • 장바구니 품목 결제페이지 이동
  • 장바구니 품목 삭제
  • 상품 결제

기능별로 브랜치를 만들어서 Github 에서 Version Control 했다.

5. 실제 진행한 프로젝트의 백엔드 프로세스

1. 초기세팅 & 모델링

초기세팅

miniconda 를 통해 python version 을 3.8 로 맞춰서 새로운 가상환경을 만들었다.
또한 mysqlclient 와 같이 필수적인 패키지들을 다운받은 뒤, 해당 가상환경에 설치된 모든 패키지 파일들을 requirements.txt 파일에 명령어 하나로 넣어주었다.

무거운 Django framework 를 사용하는 만큼, 최대한 경량화 하기 위해 쓸데없는 app 은 주석처리하고 추가해야 할 app 들은 추가해주었다.

또한 SECRET_KEY 나 DATABASE, HASHING_ALGORITHM 과 같이 노출되어서는 안되는 값들은 my_settings.py 에 넣은 후 .gitignore 에 추가해서 tracking 하지 못하도록 했다.

모델링

AqueryTool 을 이용해 모델링을 ERD 를 만들었다.

  • User, Product, Order 세 부분으로 나누어 모델링을 하였고, app 별로 색깔을 다르게 했다.

  • many-to-many, one-to-many, one-to-one 관계를 명확하게 설정하였다.

  • unique=True 및 null=True 인 경우를 고려했다.

    모델링을 하며 정말 많이 고민했던 부분들이 있었다.

[Product App]

1) Product App 의 products_options 중개테이블

  • 모든 상품마다 옵션이 있고, 그 옵션이 모두 다른 경우라면 그냥 one-to-many 로 테이블을 하나 만들어서 products 테이블과 직접 연결하면 해결됐을텐데
  • 배민문방구에서는 양말이나 신발 사이즈 및 책의 언어 옵션과 같이 sub category 마다 옵션이 겹치는 경우가 대부분이었기 때문에 one-to-many 테이블을 만들어서 중복되는 row 를 만들고 싶지 않았다.
  • 또한 옵션이 아예 없는 상품들도 절반 이상이였기때문에 중개테이블을 만드는 것이 효율적이라고 판단했다.
  • 그래서 products_options 중개테이블을 만들어
    (products - products_options - options) 와 같이 양쪽 두 테이블을 연결하도록 했다.

2) products_options 중개테이블의 sub_category_id

  • 우리팀은 나중에 2주 프로젝트 이후에도 상품을 쉽게 추가하기 위해 admin page 를 만들기로 했다.
  • admin page 에서 특정 sub category 를 선택하면 이제까지 등록되었던 옵션들을 보여주기 위해
  • products_options 테이블이 sub_categories 테이블을 참조하도록 했다.
  • 물론 sub_categories 테이블을 참조 안하고도 관련된 옵션들을 가져올 수 있다.
    이렇게 하면 된다.

    products 에 접근 -> 해당 product 의 sub_category 로 접근 -> 해당 sub_category 에 연결된 모든 products 를 전부 가져옴 -> product 마다 products_options 를 통해 options 테이블로 접근

  • 이렇게 하면 쿼리가 너무 길어져서 복잡하기 때문에, 그냥 products_options 에 sub_category_id fk 를 추가하여 다음과 같은 쿼리를 날리면 간단하게 가져오도록 했다.

sub_categories 테이블에서 products_options 테이블 역참조 (장고에서는 <sub_category instance>.productoption_set.all() )

이렇듯 특정 컬럼이 없어도 값을 가져올 수는 있지만, 나중에 실제로 데이터를 어떻게 가져올 지 먼저 생각해 본 다음 쿼리가 너무 길어질 것 같다 싶으면 쿼리최적화를 위해 테이블에 fk 를 더 추가하여 모델링을 하는게 훨씬 좋다는 걸 깨달았다.

결론은 모델링을 하면서도 데이터를 어떻게 효율적으로 가져올지에 대해서 고민을 하는 것이 필수라는 것이다.

3) products_recommendations 중개테이블

  • 상품추천을 하기 위해 self recursive many-to-many 모델을 만들었다.
  • A 상품에 B, C, D 를 등록해놓으면 나중에 B, C, D 를 각각 보았을 때 A 상품을 추천하도록 설계했다. A 가 B 의 추천상품이면, B 도 A 의 추천상품이 되도록 django 에서 symmetrical=True 옵션을 주었다.

[Order App]

4) order_status <- orders <- carts 모델구조

  • orders 테이블과 carts 를 함께 관리했다.
  • 따로 관리하려고 하면 orders 테이블에 carts 와 정확히 같은 구조를 가진 테이블을 또 하나 생성해야되었기 때문이다.
  • 그렇게 되면 효율적이지 못하다고 판단하였다.
  • orders 테이블에 누구의 오더인지 확인하기 위해 users 테이블을 연결하였고
  • 어떤 주문상태에 있는지 확인하기 위해 order_status 테이블을 연결하였다.

이 때 order_status 는 유저의 input 과 관계없는 서비스 전부터 만들어져있어야 하는 데이터이다.

  • 카트에 상품이 담길 때, 먼저 orders 테이블에 order_status_id 가 1번 (구매전) 인 row 를 만들고, 그 다음 cart 에 상품 row 를 만들어 orders 테이블에 생성된 row 와 연결한다.
  • 카트에 있던 상품 A, B, C 에서 A 만 선택적으로 구매한다고 했을 때
  • orders 테이블에서 order_status_id 가 3번 (배송완료) 인 row 를 만들어 carts 테이블의 A 상품과 연결시킨다.

실제로 코드를 짤 때 이 로직때문에 많이 애를 먹었지만, 데이터를 관리하기엔 효율적인 구조라는 것을 깨달았다.

5) reviews 테이블

  • 처음에는 아무 생각없이 상품 상세페이지에 리뷰가 있어서 리뷰 테이블을 products 테이블과 one-to-many 로 빼놓았다.
  • 물론 one-to-many 로 빼놓은 것은 당연한 것이지만, order 와 review 의 관계에 대해 충분히 생각하지 못했다.
  • 우리가 커머스 사이트에서 상품을 구매하면, 해당 주문에 대해서 리뷰 하나를 쓸 수 있다.
  • 그런데 우리 백엔드 팀은 그걸 생각 못하고 그냥 누구나 상품 리뷰를 달 수 있도록 처음에 구조를 짠 것이였다.
  • 그래서 상품 리뷰와 order 를 연결시켜서, 그 상품에 대한 order 가 있는 user 만 리뷰를 달 수 있게끔 설계하려고 reviews 테이블에 order_id fk 컬럼을 추가했다.

추가적으로 생각해봤던건, 두번의 각기 다른 주문으로 같은상품 A 를 두번 샀다면 어떻게 해야할까 ?
유저가 이 상품에 대해 리뷰를 쓸 때 자신이 어떤 상품에 대해 review 를 남기고 있는 것인지 알게해야하지 않을까? 라는 생각을 했다.

  • 여기에 대해서 우리팀은 나중에 A 상품 리뷰 페이지에 자신이 주문했던 내역을 보여주고, 어떤 주문에 대해서 리뷰를 달 것인지 클릭하게끔 UX/UI 를 기획했다.

    이렇듯 UX/UI 를 유저 입장에서 미리 생각하고 기획, 설계 하는 것이 매우 중요한 일임을 깨달았다.

2. 모델링을 바탕으로 코드 작성

  • 이 과정에서 프론트엔드와 키 값을 공유하고 정확히 한 기능안에 어떤 것들이 구현되어야 하는지 논의를 거친 뒤 작업에 착수했다.
  • agile 하게 최소 작동하는 기능을 만든 뒤 프론트와 맞춰 보는 development & test 루틴을 계속 지키려고 했다.

실제로 코드를 작성한 것을 여기에 다 적기엔 너무 많기에, 기록하고 싶었던 코드들을 소개하려고 한다.

3. 기록하고 싶은 코드

장바구니 상품 선택 -> 결제페이지 이동

  • 사실 애를 먹은 코드이기도 하고, 그 만큼 기억에 남고 또 기록하고 싶은 코드이기도 하다.
  • 장바구니에서 전체구매하기를 눌러버리면 애초에 이 view 자체가 필요없이
  • 그냥 결제페이지에서 장바구니에 있던 모든 상품들을 뿌려주면 되지만,
  • 우리는 상품을 선택적으로 결제페이지로 옮겨 담을 수 있도록 기획했고,
  • 프론트엔드에서 아직 전역상태관리가 기술적 blocker 였기 때문에
  • 백엔드 쪽에서 해당 view 가 필수적이였다.
 # 장바구니에서 상품을 몇개만 선택할 수 있음
    @auth_check
    def patch(self, request):
        try:            
            selected_products = json.loads(request.body)['selected_products']
            if not selected_products:
                return JsonResponse({'message': 'PRODUCT_NOT_SELECTED'}, status=400)

            with transaction.atomic():
                before_purchase, _  = OrderStatus.objects.get_or_create(id=1, status='구매전')
                pending_purchase, _ = OrderStatus.objects.get_or_create(id=2, status='결제중')

                order_before_purchase, _  = Order.objects.get_or_create(user=request.user, order_status=before_purchase)
                order_pending_purchase, _ = Order.objects.get_or_create(user=request.user, order_status=pending_purchase)

                for selected_product in selected_products:
                    product_id        = selected_product['product_id']
                    product_option_id = selected_product['product_option_id']

                    # 결제중인 상품도 장바구니에 노출되는데, 결제중인 상품을 선택해서 담을 가능성이 있기때문에
                    # get 으로 하면 에러가 나기 때문에 filter로 했음. 
                    if product_option_id:
                        cart = Cart.objects.filter(
                                                product_id        = product_id,
                                                product_option_id = product_option_id,
                                                order             = order_before_purchase
                                                ).first()

                        pending_cart = Cart.objects.filter(
                                                        product_id        = product_id,
                                                        product_option_id = product_option_id,
                                                        order             = order_pending_purchase
                                                        ).first()
                    else:
                        cart = Cart.objects.filter(
                                                product_id = product_id,
                                                order      = order_before_purchase
                                                ).first()

                        pending_cart = Cart.objects.filter(
                                                        product_id = product_id,
                                                        order      = order_pending_purchase
                                                        ).first()
                    if not cart:
                        continue

                    if pending_cart:
                        pending_cart.quantity += cart.quantity
                        pending_cart.save()
                        cart.delete()
                    else:
                        cart.order = order_pending_purchase
                        cart.save()
            return JsonResponse({'message': 'SUCCESS'}, status=201)

        except JSONDecodeError:
            return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)
        except Cart.DoesNotExist:
            return JsonResponse({'message': 'CART_DOES_NOT_EXIST'}, status=404)
    

인기순, 신상순으로 가져오는 로직

  • django 의 강력한 기능인 QuerySet method 및 field lookup 기능에 대해 공부할 수 있는 좋은 기회였다.
  • 다음은 인기 상품 10 개를 가져오는 로직이며 SQL 의 group by 와 order by 를 사용한 것과 동일한 코드이다.
hot_products = cart_querysets.values('product_id').\
               annotate(total_quantity_sold=Sum('quantity'))\
               .order_by('-quantity')[:10]

  • 특히 field lookup 기능을 이용하면 코드 몇줄짜리을 한줄로 줄일 수 있고, 가독성도 높일 수 있어서 신세계라고 느꼈다.
  • 다음은 django 의 field lookup 기능을 이용한 code snippet 이다.
  • product_dict 에 'is_new' 키 값을 확인해보자.
 product_dict = {
                            'product_id'       : product.id,
                            'product_name'     : product.name,
                            'product_price'    : float(product.price),
                            'product_thumbnail': product.thumbnail_image_url,
                            'discount_rate'    : discount_rate * 100,
                            'discounted_price' : discounted_price,
                            'stock'            : product.stock,
                            'is_in_wishlist'   : is_in_wishlist,
                            'is_new'           : 1 if product in Product.objects.filter\
                                                (updated_at__gte=datetime.datetime.today()-datetime.timedelta(days=7))\
                                                else 0,
                            'is_best'          : 1 if product in get_hot_products_querysets() else 0,
                            'is_sale'          : 1 if discount_rate > 0 else 0
                            }
  • Product.objects.filter(updated_at__gte=datetime.datetime.today() -datetime.timedelta(days=7))
  • 오늘 일자를 기준으로 7일 내 상품만 신상품으로 취급되도록 했다.
  • 삼항연산자 (ternary operator) 을 이용하여 가독성있게 코드를 짰다.

나머지 코드들

이 외에도 기록하고 싶은 코드 및 문제를 해결하는 과정이 담긴 코드들은
프로젝트 기간동안 블로그에 TIL 로 정리해놓았다.

4. 코드 테스트

  • postman 을 사용하여 내가 실제로 짠 view 가 잘 동작하는지 테스트했다.

  • 프로젝트를 하기 전까지 httpie 를 써서 간단한 테스트만 해왔었는데, 이렇게 했을 때 불편한점이 이만저만 아니였다.
  • 그러다가 postman 이라는 툴을 알게되고 신세계를 경험했다.
  • 너무 좋은 툴이라서 앞으로 계속 쓸 것 같다.

다만 한가지 아쉬운 것은, 지금까지는 trello 에 키값을 공유했지만 postman 으로 API 명세를 만들 수도 있다는 사실을 늦게 알아버리는 바람에 이 기능을 활용하지 못했다.

다음 프로젝트 부터는 postman 을 활용해 API 문서를 만들 계획이다.

5. 마지막으로 AWS EC2 & RDS 배포

  • EC2 로 인스턴스를 만들어서 django 서버를 배포하고,
  • RDS 인스턴스 또한 생성해서 로컬 mysql database 를 dump 하여 migrate 한 뒤
  • EC2 와 RDS 를 연결했다.
  • 오픈된 서버 주소는 Trello 에 팀원들과 공유했다.

6. 잘한 점, 개선할 점 or 아쉬웠던 점

잘한 점

  • 여러명이서 한 팀이 되어 짧은 시간에 결과를 내야 하는 프로젝트였던 만큼 agile in scrum 을 준수하려고 했다.
  • sprint 마다 프론트/백엔드 가 해야 할 목표와 역할분담을 잘 해서 방향성 있는 프로젝트를 진행할 수 있었다. 누가 뭘해야되고 이제 뭘 하지 와 같이 혼란스러운 상황이 발생할 일이 없었다.
  • 매일 stand-up meeting 을 통해 각자의 work flow 를 바로 알 수 있었고, 막히는 문제를 여러명이서 고민하다보니 금방 blocker 들을 해결할 수 있었다.
  • trello 를 이용하여 팀원 간 스케줄 관리를 했고
  • slack 을 이용하여 그때그때 필요한 정보들을 공유했다.
  • github 에서 PR 또한 reviewer 의 입장에서 생각해서 요청했으며, 리뷰를 적극 반영하려고 노력했다.

개선할 점 or 아쉬웠던 점

  1. 브랜치 분리
  • 각 기능별로 브랜치를 처음엔 잘 나누었었다. 하지만 점점 갈 수록 특정 기능이 다른 브랜치에서 필요해지고 하는상황이 반복되었다.
  • 또한 급하게 프론트와 맞춰봐야 할 상황에서 master 에 merge 될 때까지 기다릴 수 없어서 나중에는 한 브랜치에서 연관된 여러 기능들을 만들게 되었다.
  • 이러다 보니 그 브랜치가 merge 될 때 까지 시간이 오래걸렸고, reviewer 입장에서도 코드가 거대해지기 때문에 압박을 많이 받았을 것 같다. 이 부분이 좀 아쉽고 reviewer 한테 죄송하다.
  • 다음에는 세분화 된 기능마다 브랜치 하나를 만들려고 해야겠다.
  1. 모델링 시 꼼꼼하게 사이트 살펴보기
  • 실제로 모델링이 3일차에 다 끝나고도, view 를 만들면서 아, 이거 필요한데 했던 순간들이 여러번 있다.
  • 그 때마다 fix/models 와 같은 브랜치를 파서 수정한 뒤 master 에 merge 요청하는 PR 을 많이 날렸고, 사이트를 꼼꼼히 더 볼걸 하는 생각이 항상 들었다.
  1. 모델링 시 data 길이 넉넉하게 잡기
  • 상품 상세정보와 같이 얼마나 길어질 지 모르는 column 들은 길이를 넉넉하게 잡지 못해서 DataError 가 뜬 적이 몇번 있었다. 앞으로는 데이터 길이를 넉넉하게 잡아야겠다고 생각했다.
  • image_url 같은 부분을 max_length=2000 으로 잡았는데 이것보다 초과되는 image_url 들도 여럿 있었다. 근데 이것은 다음 프로젝트 때 s3 저장소를 사용하여 image url 의 길이 역시 2000 자를 넘지 않을 것으로 생각되기 때문에 ..
  • 그래도 일단 데이터의 길이에 대해서는 항상 신경을 쓰고 있어야겠다.
  1. 시간 부족
  • 야심찬 추가 기능 구현을 세웠지만, 계획했던 추가기능구현 사항들을 모두 구현하지는 못했다.
  • 백엔드 / 프론트 간 같은 기능을 만들고 있어야 했기 때문에, 어느 한 쪽이 빨리 다른 걸 막 만들고있을 수 없었다.
  • 이 또한 다음 프로젝트 때 실력이 업그레이드 되면 점점 해결 될 문제같다.
  1. 커뮤니케이션
  • 전체 팀 중 우리 팀 소통이 가장 많았고, 그만큼 결과도 좋았다고 생각한다.
  • 그럼에도 불구하고 더더더 많은 소통을 했으면 더 좋은 결과가 나왔을 것이라고 믿는다.
  • 앞으로는 해당 페이지를 담당하는 프론트엔드 팀원 분과
  • key 값 부터 시작해서, 페이지에 어떤 로직과 세부 기능들이 들어가는지, 케이스 마다 어떤 오류를 발생시킬 것인지 정말 꼼꼼하게 분석할 예정이다.

7. 마무리

다들 모난 데 없이 유쾌하고 좋은 팀원들이였음에 감사하다.
프로젝트 중 기술적으로 필요한게 있으면 돌려서 이야기하지 않고
직접적으로 이야기 하셨지만 전혀 기분나쁘게 들리지 않았고, 나 또한 그렇게 하려고 노력했다.

그러다보니 자연스럽게 팀 분위기도 좋고, 2주라는 짧은 시간 안에 나온 결과물도 아쉬운 부분이 분명 존재하긴 하지만 우리가 하려고 했던 데 까지는 모두 구현했다는 점에서 만족스러웠다.

팀 끼리 프론트/백 나뉘어서 제대로 각 잡고 진행했던 프로젝트는 이번 프로젝트가 처음이였지만 정말 많은 것을 배울 수 있었다.

기술적인 부분은 당연하고,

프로젝트의 매끄러운 진행을 위해 커뮤니케이션을 어떻게 이끌어가야하는지를 배웠다.

프로젝트가 남의 프로젝트가 아니라 우리의 프로젝트, 내 프로젝트 였기 때문에 자연스레 오너십이 생겼고,

그 오너십이 있으면 정말 뭐든 할 수 있겠구나 라는 것을 느꼈던 시간이였다.

마지막으로 끝까지 모르는 것을 열정적으로 질문해주신 백엔드 팀원 다민님 덕분에
어떻게 좀 더 잘, 그리고 쉽게 대답하는 방법에 대해 스스로 고민해 볼 수 있게 되어 좋은 시간이였다.

내가 완전 개발을 막 시작했을 때 주변 개발자 친구들에게 궁금한 것을 물어봤을 때가 떠올랐다.
내 친구들은 항상 나에게 이해하기 쉽게 알려주되 절대 정답을 바로 알려주진 않았는데,
나도 "질문하면 이해하기 쉽게 대답해주는 사람", "또 물어보고 싶은 사람", "정답을 알려주지 않고 힌트를 주는 사람" 이 되기 위해 노력할 것이다.

그리고,

IT 현업에 계시다 와서 db 를 설계하고 query 를 어떻게 날려야 최적화 할 수 있는지에 대한 인사이트를 주신 송희님,
pm 으로써 어떤식으로 팀을 이끌어야 하는지에 대한 인사이트를 주신 미현님,
기술적으로 어려운 부분임에도 백엔드에서 요청드린대로 끝까지 포기않고 팀을 위해 프론트 기능 구현해주신 래영님께 감사드린다.

profile
Backend Developer

10개의 댓글

comment-user-thumbnail
2021년 3월 28일

2차 플젝때는 더 많은 배움 얻으시길!!
너무너무 수고많으셨습니다 택향님!! 👏🏻👏🏻

1개의 답글
comment-user-thumbnail
2021년 3월 28일

택향님 고생많으셨습니다~ 👍🏻

1개의 답글

전문용어 적재적소에 잘 쓰셔서 읽으면서 공부가 되는 느낌이네요 2주간 너무 고생 많으셨어요. 덕분에 좋은 결과 낸거 같고 많이 배웠습니다.

1개의 답글
comment-user-thumbnail
2021년 3월 28일

TK 고생 많으셨어요!! 수료 후에도 이웃사촌끼리 만나서 프로젝트 보완합시다 좋아요좋아요👌

1개의 답글
comment-user-thumbnail
2021년 3월 29일

TK님 첫 프로젝트 성공적 마무리 축하 드려요~!

1개의 답글