모델링을 쉽게 생각하고 접근했었다. '왜 이틀~사흘이나 모델링에 투자해야 된다고 하는 걸까?'
db구조를 모델링 하는 것이 중요한 이유는, db의 구조를 도중에 업데이트하거나 추가 또는 삭제하는 것이 번거롭기도 하고 위험부담이 있기 때문이다.
발사각이 0.1도라도 엇나가면 궤도가 완전 달라지는 우주선처럼, 프로젝트 초기의 방향설정을 얼마나 바람직하게 하느냐에 따라 뒤늦게 모델링 수정할 때의 수고로움을 덜 수 있을 것이다.
결국 우리 프로젝트도 중간에 모델링을 뒤집게 되었다. 곱빼기라는 옵션 구현을 어떤 구조로 구현할 것인지 방법을 정확히 했어야 했다.
결국 2차 모델링 그대로 가지 못했다. 주문기능(order)는 시간 상의 이유로 구현하지 못했고 옵션의 단순화와 그로인한 불필요한 테이블 삭제 등의 이유이다.
class Product(models.Model):
name = models.CharField(max_length = 30, null = True)
price = models.DecimalField(max_digits = 10, decimal_places = 2)
description = models.TextField()
calory = models.DecimalField(max_digits = 10, decimal_places = 2, null = True)
category = models.ForeignKey("Category", on_delete = models.SET_NULL, null = True)
relative_product = models.ForeignKey("self", on_delete=models.CASCADE, null=True)
sales = models.IntegerField(default = 0)
한솥 메뉴에는 각 상품마다 곱빼기추가, 김 추가 등의 옵션을 구비해놓았다. 우리는 그 중에 곱빼기만 구현하기로 하였고 relative_product
column이 그와 관련있다. 예를 들어 pk가 1인 제품이 치킨마요(보통) 이고 pk가 2인 제품이 치킨마요(곱빼기)라면 2번의 relative_product
column에서 1번을 참조하기로 한 것. 이 경우 1번의 relative_product
column은Null
값을 갖는다. relative_product
column 이 Null인지 아닌지에 따라 보통인지 곱빼기인지 상품을 구분할 수 있고, 프론트로 부터 곱빼기 여부를 받아 그 결과에 따라 해당 제품을 인식할 수 있었다.
연습용 DB와 실제 DB : 처음에는 클론코딩이니까 완벽한 제품 기획이 있은 후에 완벽히 입력된 DB를 가지고 API를 만들 때 사용할 수 있을 것이라 기대했다. 사실 조금만 생각해봐도 이런 (백엔드에게) 이상적인 상황이 있을 리가 없다. 이는 Agile한 마인드도 아니기도 하다. 당연히 연습용 DB를 과하지 않은 선에서 작성한 후에 실제 DB를 마지막에 입력했어야 했다. 실제로 그렇게 일이 진행 될 수 밖에 없더라.
확장성에 대한 고민 : 곱빼기 옵션 때 했던 고민. 실제로 프론트와 합의한 것은 곱빼기 옵션만을 구현하기로 한 것이었지만 그 외의 김 추가 같은 다른 옵션들을 더 구현하기 위해서는 위의 셀프참조의 형태로는 힘든 것이었다. 만약 옵션을 더 추가하게 된다면 옵션 테이블이 따로 필요한 것은 뻔한 상황. 현업이나 지속적인 프로젝트를 가정했다면 당연히 옵션 테이블을 만들었어야 했었다. 아마 추후에 다시 develop하게 된다면 그 때 추가하게 되고 또 DB수정이 필요해지지 않을까?
아이디, 비밀번호, 이메일의 형식검사(유효성검사)를 FE, BE 어디서 처리할 것인가? : 전부 BE에서 처리하면 불필요한 통신이 과하게 발생할 것이며 실시간으로 유효성 검사 요청을 보내는 건 리소스 낭비같았다. 그래서 FE 근휘님께서 랜더링으로 유효성 검사를 일차적으로 하시고 이차적으로 회원가입 요청이 들어왔을 때 백엔드에서 검사를 하는 것으로 했다. 다만 비밀번호 유효성 검사는 백엔드에서 하기로 했다. (보안상의 문제가 될 수 있기 때문)
장바구니에서 FE와 통신할 때 장바구니 수량변경 또는 삭제 요청을 product_id 와 cart_id 중 어느 것을 기준으로 할 것인가? : 위의 셀프참조로 곱빼기를 정의하게 되면 곱빼기 유무에 따라 다른 product_id를 갖기 때문에 애초에 혼란을 방지하기 위해 cart_id를 기준으로 통신하기로 했다.
get_or_create
를 사용하면 장바구니 생성을 쉽게 할 수 있다.cart, created = Cart.objects.get_or_create(user = request.user, product_id = product.id, defaults={'quantity': 1})
if not created:
cart.quantity += 1
cart.save()
return JsonResponse({"message" : "UPDATED"}, status = 200)
annotate
를 통해 가상의 column 을 생성하고 이용할 수 있다.from django.db.models import Case, When
def get(self, request):
carts = Cart.objects.filter(user = request.user).annotate(
has_relative_product = Case(When(product__relative_product__isnull = True, then=False), default=True)
)
언더바 두 개를 이용하여 (위의 코드처럼) 참조 키를 통해 다른 column으로 접근할 수 있다.
Path Parameter
, Query Parameter
를 사용하여 통신하게 되었다.
단 RESTful
하게 설계하는 것에 주의해야 한다.
default 값을 이용해 하나의 API로 여러 경우의 통신을 하나의 로직으로 처리할 수있다. (대표적으로 제품 리스트 필터링 로직)
이번 프로젝트를 통해 반성하게 되고 보완해야 겠다는 생각을 했던 점들에 대해 이야기해보려한다.