직접 구현한 부분은 색깔을 주어 표시했습니다.
팀의 분위기와 소통
개인적으로 생각했을 때 팀의 분위기가 7개의 팀 중 가장 좋았다. 정말로 그래보였다. 팀원들끼리 항상 같이 밥을 먹었고, StandUp Meeting을 할 때에도 팀원이 어떤 기능 구현에 대해서 성공한 것을 이야기하면 모두 함께 박수도 쳐주었고 축하해주었고, 빨리 자랑해달라며 보여달라고 하기도 했다.
또한 본인이 부족한 부분을 이야기하면 다들 다독여주었고, 한 팀원이 어려워하면 그 팀원의 입장을 같이 공감하며 가서 도와주었다. 어쩌다 일찍 가야하는 팀원이 있어서 하루를 마무리하는 회의에 팀원이 참석하지 못한 경우 회의 내용들을 모두 텍스트로 정리해서 참여 못 한 팀원도 회의 내용을 알 수 있도록 슬랙에 보내주기도 했다.
서로 함께 프로젝트 이야기를 하면서 같이 장난도 치면서 재밌게 프로젝트를 진행했다. 그래서 팀원들간의 커뮤니케이션의 문제로 스트레스를 받은적은 한번도 없었다.
코드가 조금씩은 깔끔해지고 있다
프로젝트를 시작하기 전에 코드를 작성 할 때에는 깔끔한 코드에 대해서 어떤 게 깔끔한 코드인지도 몰랐고, 왜 그렇게 해야하는지도 말로는 이해했지만 몸으로, 머리로는 이해하지 못했다.
하지만 이번에 프로젝트를 진행하면서 멘토님들에게 여러 리뷰를 보고, 다른 팀원들과의 얘기를 해보고 코드에 대해서 보면서 이야기를 나누면서 확실히 느끼게 되었다. 내가 작성한 코드가 아니였음에도 불구하고 로직에 물흐르듯 읽혔다. 주석이 필요하지 않을 정도로 처음부터 깔끔한 코드를 작성하는게 가장 좋다라는 이야기에 대해서 확실히 이해가 가게 됐던 부분이였다.
머리로 이해했기 때문에 clean 한 code를 위해서 머릿속의 흐름대로 코드를 작성하는게 아니라 한 줄 한 줄에 대해 많이 고민해보는 좋은 습관이 생기게 되었다.
효율에 대해서 생각하게 되더라
일상 생활을 하면서 1초 단위에 대해서 굉장히 짧은 시간이라고 생각했고, 그 이상의 효율이 사람이 체감이 되는 정도의 효율인가? 그렇게까지 ms의 단위의 효율에 목숨걸고 코드를 작성해야하나? 라는 생각을 한번도 하지 않았다면 거짓말이다. 그리고 그렇게까지의 효율이 필요한 코드도 아직까지 경험해보지 못했다. 그래서 더욱 체감을 못했나보다.
하지만 프로젝트에서 장바구니에 대한 기능을 구현하던 중 filter()
를 사용하여 객체를 가지고 왔다. 그리고 포스트맨에서 해당 로직을 test해보니 실행 결과가 꽤나 늦게 나오더라. 약 3~4초 정도가 걸렸다. 여기서부터 확실히 효율에 대해서 생각해보게 됐다. 이렇게 느린 사이트는 사용할 수 없다!
그 전까지는 list comprehension을 작성하고 안하고에 대해서, select_related
or prefetch_related
를 사용하고 안하고에 대해서 '그렇게까지 효율이 차이가 발생하나..?' 라는 반신반의 상태였다면 이번 기회를 통해서 확실히 속도 차이가 난다는 것을 눈으로 확인할 수 있는 기회가 되었고 내 눈으로 직접 효율을 체감하고 나니 로직의 효율에 대해서 진지하게 생각하게 됐다. 3~4초가 걸렸던 로직이 ms 단위로 바뀌었을 때의 쾌감이 또 있더라.
이렇게 나도 점점 백엔드의 성향으로 빠져가는구나!
팀원들과의 소통
소통? 분명 잘한점에 소통을 적었는데 아쉬운점에도 소통? 그렇다. 소통은 잘됐다고 생각이 들면서도 아쉽다. 어떤 부분이 아쉬웠나?
프론트엔드와 백엔드간의 데이터가 정확히 어떤식으로 통신이 되고, 프론트엔드에서 나의 데이터를 받기 위해선 나는 어떤식으로 데이터를 가공해서 줘야하고.. 이런것들에 대해서 잘 몰라서 그런지 "이건 프론트에서 할 수 있는거 아니예요?" 라는 말을 좀 많이 했던 것 같다. 나와 데이터를 주고 받았던 수민님도 처음이라서 어떤식으로 프론트에서 받는게 가장 좋은지 잘 몰랐던 상태였던 것 같은데 그에 대한 배려가 조금은 부족하지 않았나 싶다.
2차 프로젝트 때는 데이터 가공해서 보내주는 부분에 대해서는 이번 프로젝트때보다 훨씬 더 프론트 팀원과 많은 대화를 해봐야겠다. 정말 사소한 부분까지 모두 나는 이야기해보려고 한다.
"이런 부분은 이렇게 데이터를 보내드리면 될까요? 이런 방식은 어때요? 이런 데이터도 필요하나요?"
위의 질문으로 서로 더 많은 대화를 나누는 것이 더 좋은 결과를 보여주는데 확실히 도움이 될거라고 확신한다 :)
기획을 철저하게
처음에 프로젝트에 어떤 기능들을 구현할지에 대한 미팅을 하면서 회원가입
, 로그인
, 메뉴 소개
, 매장 검색
이렇게 네가지의 기능을 Default로 구현하기로 했다. 매장 검색 기능은 지도 API를 활용해야 하는 것이였는데, 나는 이걸 백엔드에서 많이 작업이 필요한 걸로 알았다. 그래서 매장 정보에 대한 정보를 모두 crawling도 했고, 데이터 베이스에도 넣었다. 이제 기능 구현을 제대로 하려 하니 대부분의 Front에서 구현해야 할 부분이 많더라. Front에서 API를 가져다 쓰고, 주소를 작성해도 나의 서버가 아니라 KAKAO의 서버와 통신하여 위도, 경도값을 가지고와서 그대로 지도로 표시해주더라. 그래서 Front 팀원분들에게 이거 할 수 있겠냐고 여쭤봤더니 다들 이미 구현하고 있는 기능들이 있어서 인원이 부족했다. 그제서야 멘토님들에게 기획에 대해서 상담을 했고, 매장 검색에 대한 기능을 Drop하고, 원래 본죽 사이트에는 없는 장바구니 기능을 구현하기로 했다.
2주라는 짧은 시간동안 구현해야 하기에 나에게는 하루하루가 굉장히 소중한 시간이였는데 그 중 매장 검색에 대한 작업을 하느라고 하루를 날려버렸다. 이 마저도 사실 연습은 될 테니 날린 시간이라고 생각하지는 않지만 실제로 결과물엔 없다. 그래서 아쉬웠다.
'처음부터 내가 사이트를 제대로 이해하고 있었더라면 이런 불상사는 일어나지 않았을텐데. 그리고 장바구니에 대한 로직을 지금보다 더 잘 구현할 수 있었을텐데..' 라는 아쉬움이 많이 남는다.
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_id
와 product_id
를 받아서 해당 유저가 선택한 상품의 객체를 가지고 와서 해당 아이템을 삭제해주었다.
백엔드 신입 개발자는 어느정도의 역량을 갖추고 있어야할까? 라는 많은 질문에 많은 대답이 CRUD를 구현할 수 있는가 였다. 내가 보기에도 굉장히 기본적인 CRUD이지만 구현을 했다는 것에 의의를 두게 되더라. 그래서 기록하고 싶은 코드에 남겨본다.