[기업협업 - koodon] 찜 리스트

hyuckhoon.ko·2020년 7월 26일
0

What I learned in wecode

목록 보기
97/109

1. 찜 리스트에 대하여

1) 프로세스

1) 사용자가 로그인 후, 임의의 제품을 찜 리스트에 넣는다.

2) 사용자가 탈퇴를 하면, 찜 리스트는 삭제하면 그만이다.
(사용자가 직접 탈퇴를 했기 때문에, 문제의 소지가 없다.)

3) 제품이 품절될 경우
--> (케이스1) on_delete=models.CASCADE
--> (케이스2) on_delete=models.DO_NOTHING
--> (케이스3) on_delete=models.SET_NULL, null=True



유저의 입장에서 생각해보자.
UI/UX 디자이너의 입장에서까지는 아니지만, 그래도 기본적인 역지사지를 할 필요가 있다.

예를들어, 쿠팡에서 맘에 드는 물건을 찜 리스트(==좋아요 리스트)에 넣어뒀다.

다음날 쿠팡앱을 확인해보니, 내가 찜 리스트한 품목이 사라져버렸다.

제품이름을 겨우 떠올려 검색해보니 해당 제품이 품절(혹은 단종, 절판)되었음을 확인했다.

내 입장에서는 조금은 납득하기 힘든 상황이다. 찜 리스트에서 해당 제품이 품절되어 구매할 수 없다는 식으로 알려주지 않고, 찜 리스트에서 없애버린 것이다.

실제 쿠팡은 위와 같이 작동하지 않는다.

다시 한번 강조하지만, 실제 쿠팡은 위와같이 하지 않는다.

실제 쿠팡에서는 품절 혹은 단종된 경우
찜 리스트에서 해당 제품 이미지가 회색 음영처리된 것을 확인할 수 있었다.

품절된 상품이라는 걸 단번에 알 수 있으며, 그럼에도 불구하고 여전히 내 찜 리스트에 있게 조치해 뒀다.


2) 직접 구현해보기(테스트)

중고명품 거래 서비스를 제공하는 쿠돈(koodon)에서도
찜리스트 및 장바구니 기능을 구현해야 한다.
이런저런 테스트를 해보고 있다.
(실제 배포될 서비스이기에 이런저런 테스트를 스스로 해보고 있다. 너무 떨린다!!!!)


(1) Product를 품절 등의 이유로 제품이 삭제됐을 때

ㄴ SET_NULL, null=True

>>> UserProduct.objects.values()
<QuerySet [{'id': 1, 'user_id': 1, 'product_id': 1}]>

User와 Product의 중간테이블인 UserProduct 가 좋아요(찜) 리스트인 상태다.


>>> Product.objects.get(id=1).delete()

품절 및 단종 등의 이유로 제품을 삭제했다고 가정해보자.




참고로, 제품 models.py는 아래와 같다.
실험을 위해 최대한 간략하게 작성했다.

from django.db import models
   
class UserProduct(models.Model):
    user = models.ForeignKey('accounts.User', on_delete=models.CASCADE)
    product = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)
 
    class Meta:
        db_table = 'users_products'
   
  
class Product(models.Model):
    name = models.CharField(max_length=100)
    is_valid = models.BooleanField()



다시, 좋아요(찜) 리스트를 보자.

>>> UserProduct.objects.values()
<QuerySet [{'id': 1, 'user_id': 1, 'product_id': None}]>

제품 id가 None이 되었음을 알 수 있다.

이는 Product 클래스를 정의할때,

  product = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)

위와 같이 작성했기 때문이다.





ㄴ DO_NOTHING

그렇다면, on_delete=models.DO_NOTHING을 해보자.
UserProduct 클래스를 부분수정후, Product를 삭제하려하니
Integrity 에러가 발생했다.

django.db.utils.IntegrityError: FOREIGN KEY constraint failed

왜 Integrity 에러가 발생할까

외래키로 참조하는 객체가 삭제되었는데도, DO_NOTHING을 사용하는 것은 관계형 데이터베이스에서 비추천하는 방식이라고 한다.(비권장)
DO_NOTHING 지정후, 참조하는 객체를 삭제하려고 하면
Integrity에러를 일부러 발생시킨다고 한다.

다음 포스팅에서,
장고에서 그토록 비추천한다는 DO_NOTHING을 한번 진행해보려 한다.



이번엔 유저가 탈퇴한 상황을 가정해보자.

(2) User 삭제

예측이 가능한 결과다. 그래도 해보는 거다.
User id가 5번인 유저를 삭제했다.

예상결과는 UserProduct에 해당 row(데이터)가 삭제될 것이다.

>>> User.objects.get(id=5).delete()



좋아요(찜) 리스트를 확인해보자.

>>> UserProduct.objects.values()
<QuerySet [{'id': 1, 'user_id': 1, 'product_id': None}]>

역시, 유저 아이디가 5번과 관련된 데이터가 삭제됐다.
(위에 보이는 쿼리셋 객체 하나는 이전 테스트때 저장된 객체다.)


product_id가 None과 같이 변경되면
발생하는 문제가 하나 있다.
맨 처음 언급한 시나리오처럼 다음날 나의 찜리스트에서
품절된 제품이 더이상 보이지 않는다는 것이다.

이 방식은 문제가 있어 보인다.

해답을 로그 기록방식에서 찾을 수 있진 않을까?


장바구니 및 결제 정보를 다루는 테이블이라면,
로그 테이블을 통해 유저가 삭제되더라도
로그 정보는 남아있어야 한다.
아니, 로그는 유저의 발자취이자 흔적이다.
유저의 기록을 전부 기록하고 삭제되어서는 안된다.


" 유저가 삭제되서, 유저 id(pk)가 User테이블에 없다면??????

그런데 로그 기록엔
예를들어 user_id가 None이 된 기록이 남아있다.
유저가 한 두명도 아니고 None으로 바뀐 탈퇴 유저를 어떻게 찾아야 할까?"

즉, 예를들어 134번이었던 유저가 회원탈퇴를 하니,
-> 유저 테이블에서 삭제되고
-> 해당 유저를 참조하는 테이블의 데이터가 None으로 된 상황!!!!
(SET_NULL, null=True 설정시)

결론부터 말하면SET_NULL, null=True방식을 채택하게 됐다.
이와 같이 채택하게 된 과정을
아래의 테스트들을 통해 설명해보려 한다.



1) models.CASCADE 의 경우(비채택)
class Log(models.Model):
    create_at = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey('accounts.User', on_delete=models.CASCADE)
    user_name = models.CharField(max_length=100)
    user_phone_number = models.CharField(max_length=100)
  
    class Meta:
        db_table = 'logs'

당연한 결과가 예상된다.
로그의 경우, 절대 위와 같이 코드를 작성하면 안 된다.
유저가 탈퇴를 할 경우, 로그 역시 삭제되기 때문이다.

결과를 보자.

>>> Log.objects.create(user_id=4, user_name=User.objects.get(id=4).user_name, user_phone_number=User.objects.get(id=4).phone_number)
<Log: Log object (2)>

이제 유저를 삭제시켜보자.

>>> User.objects.get(id=4).delete()

로그를 확인해보자.

>>> Log.objects.values()
<QuerySet []>

예상했던 대로, 유저가 삭제되니 로그 역시 삭제됐다.
즉, 이러한 방식으로 사용하면 안된다.!



2) models.DO_NOTHING의 경우
>>> Log.objects.values()
<QuerySet [{'id': 1, 'create_at': datetime.datetime(2020, 7, 26, 6, 58, 44, 577096, tzinfo=<UTC>), 'user_id': 5, 'user_name': 'test_staff', 'user_phone_number': '010-1233-4567'}]>

유저를 삭제해보자.



>>> User.objects.get(id=5).delete()

다음과 같은 에러 메시지가 출력됐다.
django.db.utils.IntegrityError: FOREIGN KEY constraint failed

역시나......
장고에서 DO_NOTHING은 on_delete 옵션에서 1000000% 배제하자.
(다음 포스팅에서 DO_NOTHING에 대해 다뤄보려 한다.)



3) models.SET_NULL, null=True의 경우

그렇다면, 로그 models.py를 수정해보자.(SET_NULL, null=True)

class Log(models.Model):
    create_at = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey('accounts.User', on_delete=models.SET_NULL, null=True)
    user_name = models.CharField(max_length=100)
    user_phone_number = models.CharField(max_length=100)
  
    class Meta:
        db_table = 'logs'

다시, 유저를 삭제해보자.

>>> User.objects.get(id=5).delete()

삭제가 되었다.

로그는 어떻게 됐을까?
로그를 확인해보자.

>>> Log.objects.values()
<QuerySet [{'id': 1, 'create_at': datetime.datetime(2020, 7, 26, 6, 58, 44, 577096, tzinfo=<UTC>), 'user_id': None, 'user_name': 'test_staff', 'user_phone_number': '010-1233-4567'}]>

user_id 가 기존 5에서 None 으로 바꼈다.

하지만,
유저의 이름유저의 전화번호는 남아있으므로 로그의 역할을 충실히 하고 있다.

즉, 유저가 삭제되면 유저 id(pk) 정보로는 추적을 할 수가 없다.
(너무나 당연하다. 유저 테이블에서 User 정보가 사라지기 때문)

다만, 이것은 큰 문제가 될 것처럼 보이지 않는다.

왜냐하면,
회원가입시의 아이디 (또는 휴대폰 번호 등을 unique=True)
역시 로그에 남기면,
탈퇴한 회원마저 로그에서 추적이 가능하기 때문이다.

(즉, log 클래스의 attribute에

  • user = models.ForeignKey ~
  • created_at = models.DateTimeField ~
    뿐만 아니라,
  • user_name = User.objects.get(id=user.id).user_name
  • user_phone_number = User.objects.get(id=user.id).phone_number

등과 같이 유저를 식별할 수 있는 필드들을 추가하였다.)





2. 결론

1) DO_NOTHING은 사용하지 말자.

2) Log 기록시, user_id를 활용하여 user의 필드 정보를 기록하자.

0개의 댓글