> Wecode 1차 프로젝트 회고록 - 오늘의 집 클론 SweetHome

Sua·2021년 2월 28일
7

Project

목록 보기
1/2
post-thumbnail

Project Overview

2주 간의 위코드 1차 프로젝트가 끝이 났다. 모르는 것은 찾아보고 물어보고, 먼저 알게된 것은 알려주면서 하루하루 성장하는 게 느껴졌던 시간이었다!

솔직히 처음 오늘의 집에 팀 배정이 되었을 때는 '이걸 1차 프로젝트에 할 수 있을까?'라는 의심이 들었다. 하지만 팀원들과 첫 미팅을 가지면서 우리가 할 수 있는 것과 없는 것을 구분하고 팀 목표를 세우면서 '할 수 있겠다!'라는 자신감을 얻었다. 막연한 두려움은 사라지고 즐거움과 기대감이 생겨났다!

우리 팀의 이름은 'Sweethome'으로 정했고, 정말 Sweet한 사람들만 모여있었는지 한 번의 다툼없이 잘 마무리되었다.

🏡 오늘의 집 클론 프로젝트

오늘의 집은 누구나 쉽고 재미있게 자신의 공간을 만들어가는 문화가 널리 퍼지기를 꿈꾸는 소셜 커머스 사이트이다. 1000만 회원이 이용하고 있는 원스톱 인테리어 플랫폼으로서 다양한 인테리어 콘텐츠와 개인 맞춤형 커머스 기능을 제공하고 있다.

팀 목표

사용자가 사이트에 들어와서 경험할 수 있는 하나의 Cycle을 완성하는 게 우리 팀의 목표였다.

회원가입/로그인을 하고
ㅡ> SNS 기능을 둘러보고
ㅡ> 스토어에서 상품을 고르고
ㅡ> 장바구니에 담고
ㅡ> 주문하기로 마무리

그 외 포스팅 업로드 하기, 마이페이지 기능 등은 추가 구현 사항으로 남겨두었다.

팀원 소개

프론트엔드 3명 / 백엔드 4명

기간

2021.02.15-2021.02.26 (12일)

백엔드 기술 스택

  • Django
  • Python
  • MySQL
  • Bcrypt, JWT
  • Git & GitHub
  • AWS EC2, RDS

커뮤니케이션

  • Trello
  • Notion
  • Slack

백엔드 주요 구현 사항

✅ 표시는 내가 구현한 기능

공통

  • 데이터 모델링 ✅
  • 데이터 csv 파일 작성, 데이터 업로더 작성 ✅

회원가입 & 로그인(User)

  • Bcrypt를 사용한 비밀번호 암호화
  • JWT를 사용한 로그인 구현
  • 로그인 데코레이터를 통한 토큰 인증

커뮤니티(Posting)

  • 포스팅 및 댓글 조회(조건별 필터링 및 정렬)
  • 포스팅 좋아요, 스크랩 기능

스토어(Product)

  • 카테고리 조회(사이드바) ✅
  • 상품 메인페이지 조회(조건별 필터링 및 정렬) ✅
  • 상품 상세페이지 조회 ✅
  • 상품 리뷰 조회(조건별 정렬)

장바구니(Order)

  • 상품의 장바구니 등록
  • 장바구니 내역 조회
  • 장바구니 상품 수량 변경 및 가격반영

개인 목표

기술적 목표

오늘의 집은 인테리어 시장의 정보 비대칭성을 해결하기 위해 만들어졌고, 맞춤형 정보를 검색할 수 있는 기능이 발달했다. 따라서 커뮤니티나 스토어 공간에서 자신이 원하는 컨텐츠나 상품을 조건별로 세부 검색할 수 있다.

나는 스토어 부분을 맡게 되었는데 카테고리별 상품 조회, 다양한 조건별 필터링 및 정렬 기능을 제대로 구현해보고 싶었다.

마음가짐

나는 '잔잔한 바다에서는 좋은 뱃사공이 만들어지지 않는다' 말을 좋아한다.
개발이라는 게 계속해서 새로운 것을 배워가는 과정이기에 모든 것이 낯설고 힘들 수 밖에 없다.
하지만 새로운 것을 마주하는 것을 두려워하지 않고, 어렵다고 포기하지 않고,
이 모든 과정이 좋은 개발자가 시간이라고 생각하며 즐기기로 마음을 다잡았다.

팀원으로서의 나

처음엔 팀원으로서의 나는 어떠해야 하는지 깊게 생각해보지 않았다. 하지만 프로젝트가 진행될 수록 소통 또 소통이 가장 중요하다는 것을 느꼈다.

기본적으로 기술적인 소통도 중요하겠지만 그보다 우선되어서 '저건 나랑 상관없는 거야', '알아서 잘 하겠거니'라는 식의 마인드를 버리고 팀원들이 어떤 상황에 처해있는지 잘 살펴야겠다는 생각이 들었다.

Project Review

💻 공유하고 싶은 코드

1. extra()와 order_by()로 정렬하기

낮은가격순, 높은가격순 정렬을 구현하기 위해 extra 메소드를 사용했다.
내가 마주한 문제는 할인가격을 기준으로 정렬해야 하는데 원래 가격(original_price)와 할인율(discount_rate)만 가지고 있다는 것이었다.
annotate로 구현하려고 시도했으나, annotate는 SUM, COUNT, AVG 등의 기본적인 수식계산만 지원하고 할인가격을 계산하는 original_price * (100 - discount_percentage) / 100 수식을 적용할 수 없었다.
폭풍 검색하던 중 extra 메소드로 메인 쿼리에 sql문을 추가 반영할 수 있다는 걸 알게 되었고, 아래와 같이 적용하였다!

order_condition = request.GET.get('order', None)

order_by_price = {'min_price' : 'discount_price', 'max_price' : '-discount_price'}

if order_condition in order_by_price:
    products = products.extra(
        select={'discount_price': 'original_price * (100 - discount_percentage) / 100'}).order_by(
        order_by_price[order_condition])

참고로 파이썬 sorted 메소드를 활용해도 되는데, 이는 데이터를 모두 불러온 뒤에 파이썬이 정렬을 해주는 방식이다. 데이터를 데이터베이스에서 불러올 때부터 정렬을 해서 가져오기 위해서 장고의 order_by와 extra 메소드를 활용했다.

정렬에 대해 더 알고 싶다면 여기로!!

2. 딕셔너리로 필터링 기능 구현

원래는 필터링 조건을 쿼리 파라미터와 if문으로 하나씩 나열했었다. 이렇게 말이다.
(카테고리별 조회도 필터링 개념으로 이해했다.)

category        = request.GET.get('category', None)
sub_category    = request.GET.get('subcategory', None)
detail_category = request.GET.get('detailcategory', None)
color           = request.GET.get('color', None)
size            = request.GET.get('size', None)

if category:
   products = products.filter(detail_category__sub_category__category=category)

if sub_category:
    products = products.filter(detail_category__sub_category=sub_category)

if detail_category:
    products = products.filter(detail_category=detail_category)

if color:
    products = products.filter(productoption__color=color).distinct()

if size:
    products = products.filter(productoption__size=size).distinct()

순서에 맞게 조건을 잘 만들었다는 칭찬도 받았지만, 비슷한 형태의 if문이 반복된다는 review를 받았다.

이틀 정도 찾아보고 고민하면서 딕셔너리로 구현하려고 했는데 쉽지 않았다. 다른 멘토님께 딕셔너리로 구현하려고 노력한 과정과 어떤 부분에서 막히는지 설명을 드렸더니 참고할 수 있는 예시를 보내주셨다. 그 자료를 참고삼아 딕셔너리 방식의 필터링을 구현하였다.

filter_prefixes = {
    'category': 'detail_category__sub_category__category__in',
    'subcategory': 'detail_category__sub_category__in',
    'detailcategory': 'detail_category__in',
    'color': 'productoption__color__in',
    'size': 'productoption__size__in'
}

filter_set = {
    filter_prefixes.get(key): value for (key, value) in dict(request.GET).items() if filter_prefixes.get(key)
}

products = Product.objects.filter(**filter_set).distinct()

그냥 단순하게 딕셔너리 개념만 쓰이는 게 아니라 장고의 __in과 파이썬의 딕셔너리 언패킹 개념까지 이해해야 했었다.

그리고 이 문제를 해결하면서 print를 찍어보는 게 중요하다는 느꼈다. print를 찍어보니 request.GET이 QueryDict 형태이고 그 안의 값은 list 형태로 들어 있는 걸 알게 되었다. 어떤 식으로 값이 들어오는지 확인하니 코드를 짜는데 더 수월했던 것 같다.

필터링에 대해 더 알고 싶다면 여기로!!

3. prefetch_related와 select_related로 메모리 캐싱

prefetch_related와 select_related를 이용한 메모리 캐싱은 추가 구현으로 남겨둔 기능이었다. 1차 프로젝트 때는 구현을 못하겠다 싶었는데, 발표 당일 새벽 4시까지 매달려 결국 구현해낸 코드이다..!!

기본적인 prefetch_related와 select_related은 빨리 구현했는데, image 정보를 불러올 때 사용한 first()와 리뷰 별점 평균 rate_average을 계산할 때 사용한 aggregate()가 반복적인 추가 쿼리를 발생시키는 문제가 발생했다.

폭풍 구글링하여 first()는 all()[0]로 aggregate()는 annotate()를 사용하는 방식으로 문제를 해결했다.

(자세한 코드 설명은 추가 포스팅을 작성할 예정이다.)

products = Product.objects.select_related('company', 'delivery__fee') \
    .filter(**filter_set) \
    .prefetch_related('productimage_set', 'productreview_set') \
    .annotate(rate_average=Avg('productreview__rate')).distinct()

products_list = [{
    'id': product.id,
    'name': product.name,
    'discount_percentage': int(product.discount_percentage),
    'discount_price': int(product.original_price) * (100 - int(product.discount_percentage)) // 100,
    'company': product.company.name,
    'image': product.productimage_set.all()[0].image_url,
    'rate_average': round(product.rate_average, 1) if product.rate_average else 0,
    'review_count': product.productreview_set.count(),
    'is_free_delivery': product.delivery.fee.price == 0,
    'is_on_sale': not (int(product.discount_percentage) == 0),
    } for product in products
]

이렇게 많이 보내졌던 sql문이

이렇게 줄었다!

👍 잘한 점!

지식 공유의 힘

개발자 문화 중 가장 좋은 점이라고 생각하는 것은 바로 적극적인 지식 공유이다.

그래서 이번 프로젝트에서 내가 배운 것을 공유하려고 노력했다.

특히, 중간발표 때 Product.objects.filter(detail_category__sub_category__category=category) 처럼 fk로 엮여있는 테이블을 언더바 두 개 __로 이을 수 있다는 걸 공유했는데, 몇몇 분들로부터 도움이 되었다는 피드백을 받아서 좋았다. 이 외에도 내가 먼저 구현한 기능에 관심이 있는 동기들이 있으면 설명해주고, 좋은 블로그 글이나 영상이 있으면 공유했다!

또한, 반대로 다른 사람들이 배운 것을 흡수하려고 노력했다.

다른 팀은 어떻게 하고 있는지 관심을 가지고 어떻게 문제를 해결했는지 먼저 물어보고, 때론 같이 고민했다. 내 프로젝트는 아니어도 언젠가는 내가 마주할 문제이니 충분히 의미있는 시간이었다고 생각한다.

끝까지 해보기

공유하고 싶은 코드에서 작성한 3가지 코드 모두 해결하기 쉽지 않았던 문제들이다.
새벽 3~4시까지 구글링을 하면서 방법이 없을까 찾아보고 고민했다.
할 수 없을 줄 알았는데 결국 되더라!

또한, 설령 구글링으로 해결되지 않더라도, 내가 할 수 있는 만큼 최대한 고민을 하고나니 멘토님께 드리는 질문의 깊이도 달라지는 것을 느낄 수 있었다.

이 과정을 통해 구글링 실력 + 아무리 어려워보이는 문제라도 해결할 수 있다는 자신감을 얻었다.

👎 아쉬웠던 점!

프론트엔드와의 통신

더 적극적으로, 더 빨리 프론트엔드와 소통을 하면서 개발을 했었으면 좋았을 것 같다.

백엔드로서 API 로직을 잘 짜면 80~90% 이상은 다 끝난 것이라고 생각했다. 하지만, 아무리 로직을 잘 짰더라도 프론트엔드에서 받아주지 못하면 소통이 없었다.
기능 구현 단계에서부터 백엔드는 어떻게 하면 프론트엔드에서 데이터를 받기 좋을지, 또 프론트엔드는 백엔드 쪽에서 어떻게 데이터를 보내줄지 이해해야 한다는 것을 알았다.

우리 팀은 통신을 시작하면서 노션페이지를 열어 통신 방식과 데이터 형식을 맞춰갔다. 2차 프로젝트에서는 개발 초기 단계부터 적극적으로 맞춰가야겠다.

또한 내가 맡은 부분은 장바구니 등의 기능과 달리 프론트와 주고 받는 부분이 많이 없었는데 2차 때는 이런 부분을 더 많이 경험하고 싶다.

멈춰버린 블로깅

프로젝트 기간 중에는 블로그를 제대로 작성하지 못했다. 현업에서 일을 하면서 꾸준히 블로그를 운영하시는 분들이 대단하다고 느껴지는 2주였다.

몇몇 동기들은 프로젝트 기간에도 블로깅을 꾸준히 했었는데, 프로젝트를 하면서 배웠던 점을 생생하게 남겨둘 수 있어서 좋아보였다. 2차 때는 시간을 따로 떼서라도 블로깅을 열심히 해야겠다.

🌸 위코드의 꽃, 프로젝트!

위코드의 장점이 오프라인으로 함께 프로젝트를 할 수 있다는 점이다. 서로의 코드를 보면서 고민하고 시도하고 실패하고 계속 반복하고.. 하지만 결국엔 성공해내는 과정이 너무 재미있었다. 또한, 멘토님들의 적절한 리뷰 덕분에 매번 한 단계 더 성장할 수 있었던 것 같다. 혼자 했다면 절대 이루지 못했을 결과라고 생각한다.

마지막으로 좋은 분위기를 만들어준 스위트홈 팀원들 덕분에 웃으면서 코딩할 수 있었다! 다음엔 더 멋지게 성장한 모습으로 만나요~

profile
Leave your comfort zone

18개의 댓글

comment-user-thumbnail
2021년 2월 28일

많이 배우고 갑니다~ 화이팅

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

수아님 ㅠㅡㅜ 이렇게 끝이나서 너무 아쉬워서 어쩌죠ㅠ 덕분에 든든했고 즐거웠어요! 수아님의 집요한 문제해결능력 본받아 갑니다!🥰

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

수아님 진짜 너무 1차 프로젝트 때 다른 팀이지만 진짜 고마웠어요!! 2차 때도 잘 부탁드릴께요!

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

너무나 착하신 수아님 ㅠㅅㅠ 덕분에 힘이 났습니다. 고생 많았어요! 고마워요!

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

사랑하는 sua님.. 저도 원따봉 드리고 갑니다.. 😘🐹 스윗홈 넘 멋있어서 어쩌죠 ㅠㅠ 정말 케미가 좋은 팀이라는게 느껴졌어요!!! 정말 고생많으셨습니당!!!

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

솨님~ 지식 공유의 힘 멋지군요 ! 고생 많이 하셨고 다음에 프젝때 기회되면 뵈어요 ㅎ

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

저의 마니또 수아님~!! 프로젝트 때문에 정신이없어서 제대로 챙겨드리지 못해서 정말 아쉬웠어요ㅠㅠ 인사할 타이밍도 놓치고ㅠㅠ 다음에 같은팀 되면 좋겠습니다! 우리 2차 프로젝트도 화이팅하고 스윗홈 정말 오늘의 집이랑 똑같더라구요!! 저도 많이 배우고 갑니다!! 화이팅 하십쇼✊✊

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

노력파 수아님! 옆에서 보면 정말 탄탄하게 기반을 다져놓은게 보여요 :) 수고 많으셨고, 이번 플젝도 화이팅~~~

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

멋진 정리 잘 봤습니다. 수아님 2차 플젝도 화이팅입니다~!!!

1개의 답글