1차 프로젝트 3. 갈아 엎기

코지클래식·2021년 10월 15일
0

Mecook

목록 보기
3/4

기존에 만든 View는 모델링을 합쳐서 약 이틀에 걸쳐 만든 결과물이었고,
프론트 엔드분들에게 보여주기 위해 30분만에 만들기는 했지만, DB 접속 횟수를 줄여 효율성을 높이기 위해 나름대로 리팩토링도 했다.

그러나 결국 갈아엎게 되었는데, 이전의 코드와 이후의 코드를 비교해보고자 한다.


1. 갈아엎기 이전 코드

1. 상품 리스트 API(by 카테고리)

  • URL path prameter를 통해 받는 "카테고리"의 값을 기준으로 삼았다.
  • 상품의 속성하나가 "Category" 테이블을 정참조 하고 있어서 select_related로 한번에 호출했다.
  • 각 상품에 대해 1:N 조건을 갖고있는 "Like" 테이블을 여러번 사용해야 하기 때문에 DB접속이 여러번 일어나지 않도록, 부를만한 데이터를 한번에 불러와서 이용했다.
# 카테고리, 메뉴이름, 조리시간, 몇인분인지, 좋아요 개수
class ListByCategory(View) :
    def get(self,request,category_id) :
        data = Products.objects.select_related('category').filter(category_id=category_id)
        product_lists = []
        for i in data :
            product_lists.append(i.id)

        # 좋아요 테이블에서, 우리가 보여줄 목록에 있는 상품이 들어있는 모든 좋아요 데이터를 가져와서 Counter 사용
        likes = list(Like.objects.filter(product_id__in=product_lists).values_list())
        likes_list = Counter([x[1] for x in likes])
        like_boolean = []

        # AccessToken이 있는 경우에, Like 테이블에서 해당 사용자가 좋아요를 누른 상품 리스트를 찾음.
        if 'Authorization' in request.headers :
            user_id = get_user_id(self,request)
            like_boolean = [x[2] for x in list(Like.objects.filter(user_id =user_id, product_id__in=product_lists).values_list())]

        result = []
        for i in data :
            result.append({
                "id"            : i.id,
                "category"      : i.category.name,
                "name"          : i.name,
                "like"          : likes_list[i.id],
                "this_user_like": int(i.id in like_boolean),
                }
            )
        

        return JsonResponse({
            "result" : result
        })
        

2. 상품 상세페이지

  • URL PATH 로 상품 ID를 받아 이를 기준으로 처리했다.
    1. N:N 관계인 해시태그
    2.. 1:N관계인 좋아요, 상세이미지
    3.. 역참조관계인 상세정보
    를 한번에 불러오기 위해, Django에서 제공하는 prefetch_related()를 사용했다.
# 제품명, 조리시간, 몇인분인지, 카테고리, 좋아요 개수,  해시태그
class DetailByProduct(View) :
    def get(self,request,product_id) :
        
        # product 테이블의 ID를 foreignKEY로 사용하는 테이블들을 한번에 가져오기 위해 prefetch_realated사용
        products = Products.objects.select_related('category'
        ).prefetch_related('product_main_images','products_hashtag', 'product_detail_attrs'
        ).get(id=product_id) #1

        likes = Like.objects.filter(product_id=product_id).count()
        
        hash_numbers = [x[0] for x in list(products.products_hashtag.values_list('hashtag_id'))]
        detail = [{
            "text" : i.text,
            "image_url" : i.image_url,
            "priority" : i.priority,
            } for i in products.product_detail_attrs.filter(product_id=product_id)]
        
        hash_names = [x[0] for x in list(Hashtags.objects.filter(id__in=hash_numbers).values_list('name'))]

        result = {
            "image"         : products.product_main_images.values("main_image_url").get(product_id=product_id)["main_image_url"],
            "category"      : products.category.name, #1
            "name"          : products.name,
            "likes"         : likes,
            "hashtag"       : hash_names
        }

        return JsonResponse({
            "result" : [result],
            "detail" : detail
        })

3. 상품 리스트 (좋아요 기준, 우선순위 기준, 검색어 기준)

  • 각자 다른 기준이기 때문에 각자 다른 테이블을 먼저 갖고 온다.
  • 이후 각 기준에 따라 상품ID를 가져오고, 해당 상품ID를 토대로 카테고리로 정렬하는것과 거의 똑같을 거라고 생각했지만... 코드가 미묘하게 달라져서 (속성 이름이라던가), 일부 기능을 함수로 빼서 카테고리 기준의 리스트와 합치려니 부담스럽게 되버렸다.
# 카테고리 기준으로 상품리스트 반환하는것과 거의 동일하지만, 카테고리 기준이 아닌 좋아요가 높은 숫자의 상품을 반환하도록 되어 있음.
class ListByLike(View) :
    def get(self,request,*args) :
        # 좋아요에서 가장 숫자가 높은 친구들 찾기.
        likes = [x["product_id"] for x in list(Like.objects.all().values('product_id'))]
        top6 = [x[0] for x in sorted(list(Counter(likes).items()), key=lambda x : x[1], reverse=True)[:6]]
        likes_lists = [x[1] for x in sorted(list(Counter(likes).items()), key=lambda x : x[1], reverse=True)[:6]]
        product_lists = Products.objects.filter(id__in =top6)
        
        
        idx = 0
        like_boolean = []
        if 'Authorization' in request.headers :
            user_id = get_user_id(self,request)
            like_boolean = [x[2] for x in list(Like.objects.filter(user_id =user_id, product_id__in=product_lists).values_list())]

        result = []
        for i in product_lists :
            result.append({
                "id"            : i.id,
                "mainImage"     : i.thumbnail_out_url,
                "subImage"      : i.thumbnail_over_url,
                "category"      : i.category.name,
                "name"          : i.name,
                "cookingTime"   : i.cook_time,
                "serving"       : i.servings_g_people,
                "like"          : likes_lists[idx],
                "this_user_like": int(i.id in like_boolean)
                }
            )
            idx+=1
        

        return JsonResponse({
            "result" : result
        })


2. 문제점 및 피드백

1. 문제점

내가 스스로 생각했던 문제점은 이렇다.
1. 분명히 상품 리스트(카테고리), 상품 리스트(좋아요), 상품 상세페이지에는 서로 겹치는 내용이 있음에도 불구하고, 기능별로 구분해서 함수화를 못한 상태이다.
2. select_related()와 prefetched_related()를 사용하기는 했는데, 정확히 어떤식으로 효율이 좋아지는지 알지 못한 상태로 코드를 작성했다.
3. URL Path Parameter로 INPUT으로 들어오는 데이터의 유형이 달랐다. 따라서 변수명이 다르고,
코드의 흐름이 달랐기 때문에 코드를 합치기에 문제가 있었다.

2. 피드백

멘토님에게 받은 피드백은 지향점은 같지만 전혀 새로운 방법을 제시해주는 것 이었다.

클래스를 하나로 합치라. 백엔드는 Input/Output 에 따라 코드를 작성하는 것이 아닌, 데이터의 흐름에 따라 코드를 작성해야 한다.

이 피드백을 듣고, 조금이라도 좋은 코드를 만들기 위해 클래스를 하나로 합치게 되었다.


3. 새로운 코드

  • 만들어둔 PPT 이미지로 대체.
  • 일단 상품기준으로 데이터를 만들고, URL Query param으로 들어오는 키값의 존재 여부에 따라 일련의 다른 동작들을 작동시켰다. (상세페이지 / 어떤 기준으로 상품을 찾을지)
  • 한줄 이상이 넘어가는 동작들은, 가능한 한 외부로 빼서 동작 시켰다.
  • 보여줘야할 데이터의 리스트를 딕셔너리 형태로 외부에 빼두고 관리해서, 출력하는 데이터들이 뭘 위한 데이터인지 변수명에 표현할 수 있었다.



4. 아쉬운 점 (코드만)

  1. 각자의 조건에 따라 필터링/기능들을 구현하기 때문에, 매 요청마다 모든 기능의 key값에 대해 검사를 한번 씩 돌려야 한다는 것이 아쉽다.
  2. filter() 를 계속 추가하는 방식을 사용했는데, Q를 사용했으면 or이 되는 조건에 대해서도 고려할 수 있지 않았을까? 하는 아쉬움이 있다.
  3. 여러 모델에 created_at / updated_at 이 들어가 있는데, 상속용 공통 클래스를 만들 생각을 하지 못했다. 다른 조 사람들은 사용했더라. 스스로 아쉬운 부분
profile
코지베어

0개의 댓글