Project1 - About Foodly Project

Heechul Yoon·2020년 4월 21일
0

LOG

목록 보기
43/62

프로젝트 소개

주제

구성원

  • 프론트앤드 3명, 백앤드 3명

기간

  • 2주(20200227 ~ 20200306)

협업

  • Trello를 스크럼방식 협업
  • 주단위 백로그작성
  • 일단위 스탠드업미팅
  • git rebase : 커밋 관리

적용 기술

  • Python, Django web framework
  • BeautifulSoup, Pandas
  • Bcrypt
  • JWT
  • KAKAO social login
  • MySQL
  • AWS EC2, RDS
  • CORS headers

프로젝트 Demo

깃허브

나의 역할

처음하는 프로젝트였기 때문에 처음시작 했을 때는 백엔드 3명의 역할이 모호했다. 프로젝트 조 발표가 있고나서 바로 모델링을 시작했다. 팀원과 모델링을 어느정도 완성하고 나서 각자의 역할이 정해지게 되었다.
필자는 상품과 관련된 데이터를 담당하게 되었고 task는 다음과 같다.

홈메뉴(메인상품, 시즌상품, 카테고리별 상품, 번들상품)
전체상품 페이지
카테고리별 상품 페이지
상품 상세 페이지
레시피 페이지
레시피 상세 페이지
Lookbook 페이지(추천 레시피)
데이터베이스 관리(MySQL)
크롤링
AWS EC2에 프로젝트 배포
AWS RDS 구축

처음에 대부분의 모델링을 했었기 때문에 테이블과의 관계에 있어서 제일 많이 엮여있는 상품을 맡게 되었다. 프로젝트안에 있는 대부분의 데이터에 대한 GET기능을 구현했어야 해서 자연스럽게 데이터 크롤링과 데이터베이스 관리를 맡게 되었다.

크롤링

상품 하나에 대한 정보가 상품전체페이지와 상품상세페이지에 나뉘어 담겨져 있어서 하나의 상품에 대한 정보를 가져오려면 상품전체 페이지에서 데이터를 가져오고, 그 상품의 링크를 타고 들어가서 하나의 상품에 해당하는 데이터를 가져와야 했다.
모든 상품에 대한 상세페이지로 들어가는 url을 리스트로 만들어서 하나의 상품에 대한 크롤링이 끝나면 다음 url로 넘어가게 하는 크롤러를 만들어 데이터를 수집했다.

모델링(ERD)

크게 상품, 유저, 주문으로 나뉘어 테이블간의 관계를 잡아주었다. 모델링에서 중요한 관계에 있는 테이블들을 알아보자.

  • 우선 상품테이블안에서 유사상품 필드의 자기참조이다. 하나의 상품은 여러개의 유사상품을 가지게 되기 때문에 처음에는 유사상품이라는 상품테이블과 똑같은 테이블을 만들고 foreignkey로 상품테이블을 가지게 해서 두 테이블을 ManyToMany relation으로 만들어 주었다. 하지만 self라는 자기테이블 참조 기능을 알게 되면서 해당 필드에 자기참조를 넣어 중간테이블을 만들어주어 해결했다.

  • 두번째로는 번들상품이다. 하나의 번들 프로모션 테이블은 여러개의 상품을 가질 수 있다. 하지만 하나의 상품이 여러개의 번들에 포함되는 것이 이해하는것이 어려웠지만 프로모션은 일주일단위로 바뀌는 기간제의 성격을 가지기 때문에 하나의 상품이 중복해서 추천될 수 있다는 점에서 ManyToMany관계가 성립하는 것이 이해가 되었다.

  • 세번째로는 장바구니, 주문, 유저의 세가지 테이블의 관계였다. 장바구니와 주문의 생성과정을 알아보자.

  1. 상품페이지 또는 상품 상세페이지에서 'add to cart'를 누른다.
  2. 장바구니 테이블과 주문테이블이 동시에 생성된다. 장바구니 테이블에는 상품이 즉시 담기고 주문테이블은 생성만되고 어떤유저가 생성을 했는지만 표시된다.(주문테이블은 foreignkey로 유저테이블을 가진다.)
  3. 유저가 장바구니를 checkout(결제창으로 이동)하면 주문테이블에 방금 체크아웃한 장바구니 정보가 update된다.
  4. 유저가 결제창에서 정보(배송지, 결제주소, 카드정보 등)를 입력하고 결제를 완료하면 주문테이블에 유저가 입력한 정보가 업데이트 된다.(2에서 주문테이블이 생성되었을 때는 해당필드에 null이 들어가있음.)

상품과 주문의 관계를 보면, 상품은 여러개의 주문에 들어갈 수 있고, 주문 또한 여러개의 상품을 가질 수 있다. 즉 장바구니는 상품과 주문의 ManyToMany관계에서 중간테이블 역할을 수행한다.

  • 네번째로는 결제지 주소(billing address)의 개념이다. 결제창에서 유저는 billing address를 입력해야하며 billing address가 shipping address와 같으면 체크를 눌러 따로입력하지 않고, 다르면 주소를 입력해서 post한다. 그렇다면 billing address테이블과 주문테이블에서 billing_address_id 필드가 생성되는 과정을 알아보자.

<결제정보 = 배송지정보>
1. 유저가 결제정보 = 배송지정보를 체크하면 billing_address테이블의 shipping_address_id필드에 id값이 입력된다.(shipping_address테이블을 foreignkey로 가지기 때문)
2. 주문 테이블은 billing_address테이블의 id를 foreignkey로 가지기 때문에 즉시 주문테이블의 billing address 필드에 값이 생선된다.
<결제정보 != 배송지정보>
1. 유저가 입력한 결제지 주소 데이터가 billing_address 테이블에 입력된다. 그리고 is_shipping_address 테이블에 False값이 들어간다. shipping_address_id 필드에는 null이 들어간다.
2. 주문테이블에 billing_address_id필드에 값이 입력된다.

이렇게 주문테이블이 생성되면 결제 영수증의 개념으로 주문테이블의 value와 주문테이블이 참조하는 테이블이 데이터로 뿌려질 것이다.

기능구현

홈페이지 대부분의 GET기능을 구현했다.

  1. 페이지네이션과 정렬기능
    def get(self, request):
[1]     sort_by = request.GET.get('sort_by', 'id')
[2]     offset   = int(request.GET.get('offset', 0))
        limit    = int(request.GET.get('limit', 12))
        product_info = Product.objects.select_related('harvest_year', 'measure').order_by(sort_by).values(
                'name',
                'id',
                'price',
                'small_image',
                'harvest_year__year',
                'measure_id__measure',
                'is_on_sale',
                'is_in_stock',
        )[offset:offset+limit]

페이지네이션 : 페이지 기능이 들어가는 전체 상품 페이지, 전체 레시피 페이지, 카테고리별 상품 페이지에 사용된다.
쿼리파라미터로 offset값(시작하는 값) limit값(보여주는 개수)을 받고, 시작하는 값부터 시작하는값 + 보여주는 갯수를 더한값 까지 보여주도록 한다. 보여주는값인 limit의 default를 12로 두고 값이 들어오지않으면 시작값부터 12개를 보여주도록 한다. 처음부터 12로 지정하지 않은 이유는 나중에 보여주는 갯수가 변화할 수 있는 경우를 반영하기 위해서 이다. [2]에서와 같이 쿼리파라미터가 들어오지 않은경우 default로 시작을 0, 끝을 0+12로 주어 처음부터 12개의 유닛을 뿌리도록 만든다.

정렬기능 : 전체상품페이지, 카테고리별 상품 페이지에 구현되며 이름순, 가격순으로 정렬이 가능하다. 쿼리파라미터로 정렬기준을 key로 받으면 그것을 쿼리매서드인 order_by()안에 바로 넣어서 정렬된 값을 리턴한다. [1]에서와 같이 값이 들어오지 않으면 default로 'id'를 기준으로 변수에 담아 id순의 정렬된 값을 리턴한다.

쿼리파라미터로 정렬기준('sort_by'), 시작값('offset')을 동시에 받아서 2페이지에서의 가격순 정렬도 가능하다.

  1. 자기참조필드 get
        data_caching = Product.objects.select_related('measure', 'harvest_year').prefetch_related('similar_product').get(id=product_id)
        product_info = {
                'id'                    : data_caching.id,
                'name'                  : data_caching.name,
                'harvest_year_id__year' : data_caching.harvest_year.year,
                'measure_id__measure'   : data_caching.measure.measure,
                'is_in_stock'           : data_caching.is_in_stock,
                'description'           : data_caching.description,
                'price'                 : data_caching.price,
                'small_image'           : data_caching.small_image,
                'big_image1'            : data_caching.big_image1,
                'big_image2'            : data_caching.big_image2,
                'big_image3'            : data_caching.big_image3,
                'energy'                : data_caching.energy,
                'carbonydrate'          : data_caching.carbonydrate,
                'protein'               : data_caching.protein,
                'fat'                   : data_caching.fat,
                'mineral'               : data_caching.mineral,
                'vitamin'               : data_caching.vitamin,
                'similar_product'       : list(data_caching.similar_product.values('name', 'harvest_year_id__year', 'is_in_stock', 'measure_id__measure'))
                }

manytomany 관계에 있는 유사상품과 상품은 하나의 상품이 여러개의 유사상품을 가지기 때문에 하나의 객체를 가져와 values()로 여러개의 유사상품을 리스트에 담고 리스트화 해준다. 여기서 values()를 통해서 모든 값을 다 불러와 similar_product만 그 딕셔너리안에 추가해주면 되는것 아니냐는 의문이 들 수 있지만, values()로 key와 value를 뿌려주고 되면 key값을 정할 정할 수 없다. 그래서 lookup을 했는 경우 'harvest_year_id__year'과 같이 key값이 지저분하게 남아 프론트엔드에게 가독성 좋은 키값을 넘겨줄 수 없다.(필자의 경우 이미 values()로 지저분한 키값을 넘겨주고 프론트엔드에서 키값설정이 끝난 후 이 사실을 깨닳아 get으로 객체를 뽑아 리턴함에 키값이 지저분하게 되었다.)

스스로에 대한 피드백

잘한점

  • 기능이 되는것에만 신경쓰지 않고 최대한 중복을 최대한 피하고 줄일 수 있는 코드는 줄이려고 노력하다보니 좀 더 효율적인 코드를 찾을 수 있었다고 생각한다.
  • 처음에 모델링을 통해서 어떤테이블을 만들 것인지, 각 테이블간의 관계는 어떻게 될것인지 생각해서 웹페이지 전반에 대해 깊게 이해하려고 했던 것이 로직을 만들고 데이터를 뿌려주는데 많은 도움이 되었다고 생각한다. 데이터의 관계에 대한 깊은 이해가 중요하다고 느꼈다.
  • 내가맡은 일만 끝내기보다는 백엔드 팀원들에게 닥친 문제를 같이 고민하고 찾아보면서 팀 전체의 개발속도와 분위기를 생각하며 작업에 임하려고 노력했고, 같이고민하는 과정에서 더 많이 얻었다는 느낌이 든다.

아쉬운점

  • 프론트앤드에 대한 이해가 부족해 초반에는 각자에게 주어진 기능을 만들기 바빴던 것 같다. 만들어진 엔드포인트를 붙혀서 작동시키기 보단, 많은 엔트포인트를 만들어 나중에 붙히자는 생각에 실질적으로 연결하는데 어려움을 겪었다.
  • 초반에 values()로 값을 거의다 가져온 탓에 프론트앤드에게 정돈되지 못한 키를 넘겨주게 되었다. 이미넘겨준 키를 나중에 바꾸기에는 기회비용이 너무 커서 객체로 값을 뽑아와서 키값을 수정가능함에도 바꾸지않고 정돈되지 않은 키를 사용한 점이다.
  • 깃과 깃허브에 대한 이해가 부족해 리모트 마스터에서 풀을 할 때 많은 어려움을 겪었다.
  • 실재 쿼리효율에 대해 충분히 테스트할 시간을 가지지못했다.

개선방법

  • 프론트앤드와 개발 초반부터 협업을 해나가는 것이 중요하다고 느꼈다. 2차프로젝트 때는 같은 기능을 맡은 프론트앤드와 개발속도를 맞추기 위해 노력하고, 만들어졌다면 다음 기능작업으로 가는 것이 아닌 붙히는 작업을 먼저 할 것 같다.
  • 프론트엔드에게 정돈된 값을 줘 직관적인 키를 주기 위해 값을 가져오는 방식, 키의 네이밍에 신경쓸 것이다.
  • 기본적이고 전체적인 깃과 깃허브의 흐름을 좀 더 공부하고, 커밋을 하기전에는 내 팀원이 풀받는 상황을 상상하며 커밋을 해야 겠다고 생각했다. 그리고 migrations애러가 가장 많이 발생했는데 팀원과 상의해서 푸시를 하기전에 migrations파일을 다 지우고 마지막 마이그래이션을 해서 '0001'파일만 남겨 둔 채로 푸시를 하기로 약속을 해야겠다.
  • 쿼리 효율에 대한 여러가지 경우를 테스트해서 경우에 따른 효율적인 쿼리를 신경써야 겠다.

느낀점

1차 프로젝트 때 배운점은 커밋의 무게이다. 어렵고 복잡한 작업을 하고 그 작업이 팀원의 작업에 영향을 미치는 만큼 내 코드와 커밋이 팀과 프로젝트에 끼치는 영향을 항상 고려하여 책임감을 가져야 겠다고 생각했다. 두번째로는 커뮤니케이션 능력이 생각이상으로 중요하다는 것이다. 백앤드 안에서도, 프론트앤드와 백인드끼리도 결국 겹치는부분이 있고 그부분에 대한 원활한 소통이 결국 좋은 결과물을 가져온다. 그래서 내가하는 작업을 상대방이 알아듣기 쉽게 말해야하고, 상대방이 원하는 것이 무엇인지 핵심을 파악하는것이 중요하다고 느꼈다. 마지막으로는 누구도 내문제를 해결해주지 않는다는 자세로 임해야 한다는 것이다. 물론 필요한 것을 질문 할 수는 있겠지만 그 질문은 내 깊은 고민에서 나와야 하고, 내 문제해결능력에 발전이 있는 질문이어야 한다. 어떤 문제도 당장 정답을 물어보기 보다는 혼자 해답을 찾는 시간을 가지고 그 시간에 익숙해지는 것이 여러 상황에 대한 대처능력을 키우는 것이라고 느꼈다.

profile
Quit talking, Begin doing

0개의 댓글