✚ 본죽 1차 클론 프로젝트 회고록🥣

haejun-kim·2020년 8월 30일
3
post-thumbnail

프로젝트 소개

데모 영상

사용 된 기술

Front-End

  • HTML / CSS
  • JavaScript
  • React
  • React-Router-DOM
  • SASS

Back-End

  • Python
  • Django Web Framework
  • CORS headers
  • Selenium
  • Beautiful Soup
  • MySQL
  • Bcrypt, JWT
  • pandas
  • RESTful API
  • AWS EC2,RDS 연동 및 gunicorn 사용한 배포

공통

  • JSON
  • Git
  • Linux
  • HTTP
  • Scrum 방식의 프로젝트 진행

협업 툴

Modeling

  • AqueryTool 사용하여 ERD 구성

전체 기능 / 내가 맡은 역할, 부분

직접 구현한 부분은 색깔을 주어 표시했습니다.

회원가입, 로그인

  • 회원가입 구현
  • 회원가입 시 email, phone_number, password에 대해서 validator를 사용하여 유효성 검사
  • 각종 Error에 대한 handling

Bcrypt, JWT를 통한 암호화 및 token 발행

  • 회원가입이 완료되면 bcrypt 사용하여 password 암호화하여 저장
  • 로그인 시 암호화 된 password를 비교하여 password 일치 시 JWT 사용하여 token 발행

메뉴 소개

  • 실제 홈페이지에서 메뉴, 가격, 메뉴 이미지에 대한 정보를 crawling하여 데이터를 가공 후 Frontend에 Json 형태로 Response
  • 전체 메뉴 뿐만 아니라 각 메뉴의 상세 페이지의 내용도 위와 같은 형태로 Response

메뉴 검색

  • 카테고리 또는 메뉴 이름을 검색 시 검색에 입력한 단어가 포함 된 검색 결과를 보여줌
  • 검색 결과에서 보여주는 페이지에서 메뉴의 상세 페이지로 이동 가능

장바구니

  • 장바구니에 관련 된 모든 함수에서 login decorator를 사용하여 권한이 인가 된 회원인지 판별
  • 메뉴 소개 페이지에서 장바구니 담기 버튼을 누르면 데이터베이스에 품명, 가격, 이미지, 제품id, 사용자id 저장
  • 장바구니 페이지로 들어가면 해당 유저가 장바구니에 담은 상품 목록을 Response
  • 장바구니 페이지에서 수량을 변경하면 데이터베이스에서 해당 품목의 수량을 update
  • 장바구니 페이지에서 품목 삭제를 하면 해당 데이터베이스에서 해당 유저의 해당 품목을 삭제

잘한 점 + 아쉬운 점 + 개선 방법

잘한 점

  1. 팀의 분위기와 소통
    개인적으로 생각했을 때 팀의 분위기가 7개의 팀 중 가장 좋았다. 정말로 그래보였다. 팀원들끼리 항상 같이 밥을 먹었고, StandUp Meeting을 할 때에도 팀원이 어떤 기능 구현에 대해서 성공한 것을 이야기하면 모두 함께 박수도 쳐주었고 축하해주었고, 빨리 자랑해달라며 보여달라고 하기도 했다.
    또한 본인이 부족한 부분을 이야기하면 다들 다독여주었고, 한 팀원이 어려워하면 그 팀원의 입장을 같이 공감하며 가서 도와주었다. 어쩌다 일찍 가야하는 팀원이 있어서 하루를 마무리하는 회의에 팀원이 참석하지 못한 경우 회의 내용들을 모두 텍스트로 정리해서 참여 못 한 팀원도 회의 내용을 알 수 있도록 슬랙에 보내주기도 했다.
    서로 함께 프로젝트 이야기를 하면서 같이 장난도 치면서 재밌게 프로젝트를 진행했다. 그래서 팀원들간의 커뮤니케이션의 문제로 스트레스를 받은적은 한번도 없었다.

  2. 코드가 조금씩은 깔끔해지고 있다
    프로젝트를 시작하기 전에 코드를 작성 할 때에는 깔끔한 코드에 대해서 어떤 게 깔끔한 코드인지도 몰랐고, 왜 그렇게 해야하는지도 말로는 이해했지만 몸으로, 머리로는 이해하지 못했다.
    하지만 이번에 프로젝트를 진행하면서 멘토님들에게 여러 리뷰를 보고, 다른 팀원들과의 얘기를 해보고 코드에 대해서 보면서 이야기를 나누면서 확실히 느끼게 되었다. 내가 작성한 코드가 아니였음에도 불구하고 로직에 물흐르듯 읽혔다. 주석이 필요하지 않을 정도로 처음부터 깔끔한 코드를 작성하는게 가장 좋다라는 이야기에 대해서 확실히 이해가 가게 됐던 부분이였다.
    머리로 이해했기 때문에 clean 한 code를 위해서 머릿속의 흐름대로 코드를 작성하는게 아니라 한 줄 한 줄에 대해 많이 고민해보는 좋은 습관이 생기게 되었다.

  3. 효율에 대해서 생각하게 되더라
    일상 생활을 하면서 1초 단위에 대해서 굉장히 짧은 시간이라고 생각했고, 그 이상의 효율이 사람이 체감이 되는 정도의 효율인가? 그렇게까지 ms의 단위의 효율에 목숨걸고 코드를 작성해야하나? 라는 생각을 한번도 하지 않았다면 거짓말이다. 그리고 그렇게까지의 효율이 필요한 코드도 아직까지 경험해보지 못했다. 그래서 더욱 체감을 못했나보다.
    하지만 프로젝트에서 장바구니에 대한 기능을 구현하던 중 filter()를 사용하여 객체를 가지고 왔다. 그리고 포스트맨에서 해당 로직을 test해보니 실행 결과가 꽤나 늦게 나오더라. 약 3~4초 정도가 걸렸다. 여기서부터 확실히 효율에 대해서 생각해보게 됐다. 이렇게 느린 사이트는 사용할 수 없다!
    그 전까지는 list comprehension을 작성하고 안하고에 대해서, select_related or prefetch_related를 사용하고 안하고에 대해서 '그렇게까지 효율이 차이가 발생하나..?' 라는 반신반의 상태였다면 이번 기회를 통해서 확실히 속도 차이가 난다는 것을 눈으로 확인할 수 있는 기회가 되었고 내 눈으로 직접 효율을 체감하고 나니 로직의 효율에 대해서 진지하게 생각하게 됐다. 3~4초가 걸렸던 로직이 ms 단위로 바뀌었을 때의 쾌감이 또 있더라.
    이렇게 나도 점점 백엔드의 성향으로 빠져가는구나!

아쉬운 점

  1. 팀원들과의 소통
    소통? 분명 잘한점에 소통을 적었는데 아쉬운점에도 소통? 그렇다. 소통은 잘됐다고 생각이 들면서도 아쉽다. 어떤 부분이 아쉬웠나?
    프론트엔드와 백엔드간의 데이터가 정확히 어떤식으로 통신이 되고, 프론트엔드에서 나의 데이터를 받기 위해선 나는 어떤식으로 데이터를 가공해서 줘야하고.. 이런것들에 대해서 잘 몰라서 그런지 "이건 프론트에서 할 수 있는거 아니예요?" 라는 말을 좀 많이 했던 것 같다. 나와 데이터를 주고 받았던 수민님도 처음이라서 어떤식으로 프론트에서 받는게 가장 좋은지 잘 몰랐던 상태였던 것 같은데 그에 대한 배려가 조금은 부족하지 않았나 싶다.
    2차 프로젝트 때는 데이터 가공해서 보내주는 부분에 대해서는 이번 프로젝트때보다 훨씬 더 프론트 팀원과 많은 대화를 해봐야겠다. 정말 사소한 부분까지 모두 나는 이야기해보려고 한다.
    "이런 부분은 이렇게 데이터를 보내드리면 될까요? 이런 방식은 어때요? 이런 데이터도 필요하나요?"
    위의 질문으로 서로 더 많은 대화를 나누는 것이 더 좋은 결과를 보여주는데 확실히 도움이 될거라고 확신한다 :)

  2. 기획을 철저하게
    처음에 프로젝트에 어떤 기능들을 구현할지에 대한 미팅을 하면서 회원가입, 로그인, 메뉴 소개, 매장 검색 이렇게 네가지의 기능을 Default로 구현하기로 했다. 매장 검색 기능은 지도 API를 활용해야 하는 것이였는데, 나는 이걸 백엔드에서 많이 작업이 필요한 걸로 알았다. 그래서 매장 정보에 대한 정보를 모두 crawling도 했고, 데이터 베이스에도 넣었다. 이제 기능 구현을 제대로 하려 하니 대부분의 Front에서 구현해야 할 부분이 많더라. Front에서 API를 가져다 쓰고, 주소를 작성해도 나의 서버가 아니라 KAKAO의 서버와 통신하여 위도, 경도값을 가지고와서 그대로 지도로 표시해주더라. 그래서 Front 팀원분들에게 이거 할 수 있겠냐고 여쭤봤더니 다들 이미 구현하고 있는 기능들이 있어서 인원이 부족했다. 그제서야 멘토님들에게 기획에 대해서 상담을 했고, 매장 검색에 대한 기능을 Drop하고, 원래 본죽 사이트에는 없는 장바구니 기능을 구현하기로 했다.
    2주라는 짧은 시간동안 구현해야 하기에 나에게는 하루하루가 굉장히 소중한 시간이였는데 그 중 매장 검색에 대한 작업을 하느라고 하루를 날려버렸다. 이 마저도 사실 연습은 될 테니 날린 시간이라고 생각하지는 않지만 실제로 결과물엔 없다. 그래서 아쉬웠다.
    '처음부터 내가 사이트를 제대로 이해하고 있었더라면 이런 불상사는 일어나지 않았을텐데. 그리고 장바구니에 대한 로직을 지금보다 더 잘 구현할 수 있었을텐데..' 라는 아쉬움이 많이 남는다.

  3. Django ORM 사용
    Django를 사용하는 가장 큰 장점 중 하나는 ORM이다. 데이터 베이스에 저장되어 있는 값들을 Query를 이용하여 간편하게 데이터 베이스의 값들을 꺼내올 수 있다. 하지만 이번 프로젝트를 진행하면서 이런 ORM의 장점을 완전히 활용하지 못한 것 같다. 또한 DB Hit를 덜 하며 훨씬 효율적으로 로지을 작성해야하는데, select_related, prefetch_related를 완전히 이해하지 못하고 사용한 것 같아 아쉬운 마음이 크다.
    내가 원하는 값을 Query에서 꺼내와서 사용하기 위해서 Django shell에 들어가서, Django 공식 문서를 옆에 띄워놓고 공식문서에 있는대로 다 쳐봤다. 다 쳐보면서 '아 이건 안되는구나', '아 이건 되는구나' 하였고, 값을 정상적으로 가져오는지 Shell에서 확인을 하면 그것을 코드로 옮겼다. 근데 내 스스로 생각하기에 너무 때려맞춘 느낌이 강했다. 안되면 될 때 까지했다.
    이에 대한 부분을 제대로 내가 이해해서 머릿속에서 여러 테이블간의 관계를 생각하고, '이건 정참조 or 역참조 관계이며, 훨씬 효율적으로 데이터를 가져오기 위해서는 역참조로 어떤 문법을 사용하면 되겠고, 이런 식으로 하면 내가 원하는 데이터를 모두 가져올 수 있겠구나' 라고 생각해서 코드로 옮기고 싶었다. 근데 그게 너무 안됐다. 그 부분이 가장 아쉬웠고, 가장 어려웠다..

개선 방법

  • '이런거까지 얘기를 해야하나..?' 라는 생각보단 그냥 모두 다 대화를 나눠보기. 서로 모르기 때문에 서로의 생각을 들어보는게 정말 중요하더라
  • 기능 구현에 급급해서 제대로 이해를 못하고 그저 구현에만 집중했다면 이제는 구현에만 집중하기보다는 확실히 이해하고 기능을 구현하도록 하자
  • 공식문서를 십분 활용하자. 답은 역시 공식문서에 나와있더라.

기록하고 싶은 코드 / 로직

import json

from django.views     import View
from django.db.models import Prefetch
from django.http      import (
    JsonResponse,
    HttpResponse
)

from .models        import Cart
from user.utils     import login_required
from user.models    import User
from product.models import (
    Product,
    Image
)
class CartView(View):
    @login_required
    def post(self, request):
        try:
            data      = json.loads(request.body)

            if not Cart.objects.filter(user_id = request.user.id, product_id = data['product_id']).exists():
                Cart.objects.create(
                    user        = request.user,
                    product     = Product.objects.get(id = data['product_id']),
                    total_price = data['total_price'],
                )
                return JsonResponse({'message' : 'CART ADDED'}, status = 201)

            return JsonResponse({'message' : 'ALREADY EXIST PRODUCT' }, status = 201)

        except KeyError:
            return JsonResponse({'message' : 'KEY ERROR'}, status = 400)            
    
    @login_required
    def get(self, request):
        try:
            user      = request.user

            items     = Cart.objects.filter(user_id = request.user.id)
            cart_list = [{
                "ID"       : item.product.id,
                'quantity' : item.quantity,
                'name'     : item.product.name,
                'price'    : item.product.price,
                'image_url': item.product.image_set.first().image_url
            } for item in items]
            return JsonResponse({'cart_list' : cart_list}, status = 200)
    
        except KeyError:
            return JsonResponse({'message' : 'KEY ERROR'}, status = 400)

    @login_required
    def delete(self, request):
        data = json.loads(request.body)
        try:
            user = request.user
            item = Cart.objects.get(user_id = request.user.id, product_id = data['product_id'])
            item.delete()
            return JsonResponse({'message' : 'DELETED'}, status = 200)
        
        except  KeyError:
            return JsonResponse({'message' : 'KEY ERROR'}, status = 400)

    @login_required
    def patch(self, request):
        data = json.loads(request.body)
        try:
            user = request.user
            item = Cart.objects.get(user_id = request.user.id, product_id = data['product_id'])
            if data['changed_quantity'] == 'plus':
                item.quantity +=1
                item.save()
                return HttpResponse(status = 200)

            data['changed_quantity'] == 'minus'
            item.quantity -=1
            item.save()
            return HttpResponse(status = 200)
                
        except KeyError:
            return JsonResponse({ 'message' : 'INVALID_KEYS' }, status = 400)

지금 봐도 정말 어려운 로직은 아니다. 실제로 다른 팀원들은 나보다 역참조를 잘 활용한다. 그렇지만 이 코드를 기억하고 싶은 이유는 정말 기본적이지만! 처음으로 CRUD에 대한 모든것을 구현해보았기 때문이다.
장바구니에 담을 때 Post를 사용하여 Create를,
프론트에서 json의 request를 받는다. 그리고 보통의 장바구니에선 이미 장바구니에 있는 상품을 다시 장바구니에 넣으려고 하면 이미 장바구니에 있다는 알림을 사용자에게 보여준다.
따라서, 장바구니에 넣기 전, 장바구니에 이미 존재하는 상품은 아닌지를 먼저 판별한다. 그리고 존재하지 않는 상품이라면 해당 유저가, 해당 상품을 선택했음을 데이터베이스에 생성해준다.

장바구니의 리스트를 보여줄 때 Get을 사용하여 Read를,
filter()를 사용하지 않으면 유저에 대한 것을 고려하지 않고 모든 유저가 장바구니에 넣은 상품들에 대한 Query를 모두 가져와버린다. 따라서 filter()를 사용하여 login decorator를 통해 user를 판별했으므로, 해당 user의 id를 받아와 해당 user가 선택한 상품에 대한 Query만을 가지고 와서 보여준다.
또한 이 부분에서 list comprehension을 사용하여 일반적인 for loop보다는 빠른 속도를 고려하였다.
많은 image_url 중 첫번째 이미지만 사용하면 되기 때문에 기존에는 [0]과 같이 조금은 하드코딩의 냄새가 나는 로직을 구현했는데, 멘토님의 리뷰를 받고 first() 메소드를 사용하는 로직으로 변경하였다.
사실 멘토님의 추천에는 위와 같이 작성 할 경우 main image인지 판별하기 어렵기 때문에 모델에서 is_main을 미리 지정해주시는 것이 좋다고 하셨다.
다음 2차때는 꼭 위의 피드백을 활용해보려고 한다.

장바구니의 품목의 수량을 변경할 때 Patch를 사용하여 Update를,
Front에는 수량을 변경하기 위해서는 +, - 버튼이 존재한다. 이 버튼을 눌렀을 때 각각 plus, minus라는 값을 받아서 해당 값이랑 일치할 경우 plus 혹은 minus에 대한 수량 변경 로직을 구현한다.

이 경우 단순히 수량만 올리고 내릴 것이 아니라 가격에 대해서도 내가 연산을 해 줬으면 더 좋지 않았을까 하는 아쉬움이 많이 남는다.

이런 부분에 대해서도 모델링에서 고려를 해 줄 것!

장바구니의 품목을 삭제할 때는 Delete를 사용하여 Delete를.
이 부분은 생각보다 간단했다. user_idproduct_id를 받아서 해당 유저가 선택한 상품의 객체를 가지고 와서 해당 아이템을 삭제해주었다.


백엔드 신입 개발자는 어느정도의 역량을 갖추고 있어야할까? 라는 많은 질문에 많은 대답이 CRUD를 구현할 수 있는가 였다. 내가 보기에도 굉장히 기본적인 CRUD이지만 구현을 했다는 것에 의의를 두게 되더라. 그래서 기록하고 싶은 코드에 남겨본다.

마치며

  1. 소통의 중요성은 아무리 강조해도 부족하지 않다. 항상 소통하자.
  2. 다른 사람과 비교하지 말고 지난 날의 나와 비교하자. 나는 성장했다.
  3. 발표를 하면서 느낀건데 프론트엔드는 백엔드의 발표를 이해하기 힘들 것 같다. 프론트엔드 분들도 훨씬 이해하기 쉬운 발표를 준비해보자.
  4. 개발에 본격적으로 재미라는 것을 느끼기 시작했다.
  5. 같이 일하고 싶은 개발자가 되고싶다.
  6. 멘토님들, 동기님들 항상 감사합니다.

0개의 댓글