프로젝트

파이널 프로젝트 - 클라이언트에서 POST요청시에 생긴 트러블 슈팅(API)

Y0un9Ki 2024. 6. 2. 22:36

이번에는 만들어진 API에서 POST요청시에 생긴 트러블 슈팅에 대해서 작성을 해보고자 한다.

 

예약기능을 만들다 트러블 슈팅이 생겼다. 이전에도 비슷한 경험이 있었는데 그때에는 다른 방식으로 해결을 했는데 이번에 새로 해결한 방식이 더 좋은 방식인거 같아서 작성을 해보고자 한다.

 

일단 reservation의 모델이 어떠한 식으로 만들었는지 한번 살펴보자.

class Reservation(models.Model):
    id = models.AutoField(primary_key=True, unique=True, null=False, blank=False)
    user = models.ForeignKey(User, related_name='reservation', on_delete=models.CASCADE)
    performance = models.ForeignKey(Performance, related_name='reservation', on_delete=models.CASCADE)

 

reservation 모델은 user와 performance 모델을 외래키로 참조하고 있다. 그렇기에 POST요청을 보낼 때 body에 꼭 user의 id와 performance의 id값을 넣어서 POST요청을 보내야만 데이터베이스에 생성이 되게 된다.

 

아래에 그림은 POST요청시에 user의 id와 performance의 id를 Body에 담아서 보내지 않았기 때문에 생긴 에러를 볼 수 있다. 

 

그러면 프론트에서 POST요청을 보낼 때에 토큰을 까서 user의 정보를 가지고 와야 하는 굉장히 안좋은 방식이된다.

토큰을 디코딩하는 것은 무조건 백앤드에 영역에서의 일이다!!!!

 

그렇기 때문에 백앤드 영역에서 이것을 해결해주기 위해서 많은 생각을 하게 되었다.

 

아래에는 view의 코드이다. 이 view코드를 한번 잘 살펴보자.

class ReservationCreate(APIView):
    authentication_classes = [JWTAuthentication]
    permission_classes = (IsAuthenticated,)
    
    def post(self, request, pk, format=None):
        try:
            performance = Performance.objects.get(pk=pk)
            user=self.request.user
            user_point = user.point
            if user.point is None:
                user.point = 0
                user.save()
            performance_price = performance.price
            serializer = ReservationSerializer(data=request.data)
            if serializer.is_valid():
                if user_point >= performance_price:
                    user.point = user_point - performance_price
                    user.save()
                    serializer.save(user=user, performance=performance)
                    
                    response_data = {
                        'user_id': serializer.data['user'],
                        'performance_id': serializer.data['performance'],
                        'performance_name': performance.name,
                        'message': '예약에 성공하셨습니다.'
                    }
                else:
                    point = performance_price - user_point
                    response_data = {
                        'user_id': user.id,
                        'message': f'{point}포인트가 부족해요ㅠㅠ'
                    }
                
                return Response(response_data, status=status.HTTP_200_OK)
            else:
                Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
                
        except Performance.DoesNotExist:
            raise NotFound({'message':'해당 공연은 존재하지 않습니다.'})

 

위에 코드를 보면 엔드포인트에 pk값 여기서는 performance의 pk값에 해당되게 된다.

 

이제 돌아가는 코드를 하나하나 디버깅을 해보면서 값을 보자.

 

serializer = ReservationSerializer(data=request.data) 이코드에서 request.data 부분이 요청을 보낼때 Body에 들어가는 데이터에 해당이 된다. Body에 {'user' : '{user_id}', 'performance': '{performance_id'}} 이러한 형태를 적어서 보내주게 되면 reqeust.data에 값이 들어가게 된다.

 

이렇게 디버깅을 하면서 request.data에 들어가는 값을 알게 되었고 그렇다면 request.data에 들어가는 값을 백앤드에 영역에서 준다면 해결이 되지 않을 까? 라는 생각을 하게 되었고 한번 해보기로 했다.

 

위에서 코드를 보면 우리는 엔드포인트에 performance에 id값을 받아오고 user=self.reqeust.user를 통해서 요청을 보낸 사용자를 식별을 하게 된다.

그러면 우리는 request.data에 들어가는 값인 performance에 id값, user의 id값을 모두 백앤드 영역에서 알 수 있다는 것이다.

이것을 깨닫고 reqeust.data자리에 필자는 새로운 request_data라는 딕셔너리를 만들어서 필요한 값을 넣어서 view를 다시 만들었다.

 

밑에 코드는 POST를 하기 위해서 body에 performance의 id값, user의 id값을 넣어주어야 한는데 백앤드에서 처리를 하게끔 만들어준 코드이다.

class ReservationCreate(APIView):
    authentication_classes = [JWTAuthentication]
    permission_classes = (IsAuthenticated,)
    
    def post(self, request, pk, format=None):
        try:
            user=self.request.user
            performance = Performance.objects.get(pk=pk)
            user_point = user.point
            if user.point is None:
                user.point = 0
                user.save()
            performance_price = performance.price
            # 이 try구문은 예약이 이미 존재한다면 다시 예약을 못하게 막아놓기 위해서 만들었다.
            try:
                reservation = Reservation.objects.get(performance=performance, user=user)
                if reservation:
                    response_data = {
                        'message': '이미 예약이 존재해요'
                    }
                    return Response(response_data)
            except Reservation.DoesNotExist:
                # 이 부분 코드를 추가하게 되었다.
                request_data = {
                    'user': user.id,
                    'performance': pk
                }
                serializer = ReservationSerializer(data=request_data)
                if serializer.is_valid():
                    if user_point >= performance_price:
                        user.point = user_point - performance_price
                        user.save()
                        serializer.save(user=user, performance=performance)
                        
                        response_data = {
                            'user_id': serializer.data['user'],
                            'performance_id': serializer.data['performance'],
                            'performance_name': performance.name,
                            'message': '예약에 성공하셨습니다.'
                        }
                    else:
                        point = performance_price - user_point
                        response_data = {
                            'user_id': user.id,
                            'message': f'{point}포인트가 부족해요ㅠㅠ'
                        }
                    
                    return Response(response_data, status=status.HTTP_200_OK)
                else:
                    Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
                    
        except Performance.DoesNotExist:
            raise NotFound({'message':'해당 공연은 존재하지 않습니다.'})

 

필자는 request_data라는 딕셔너리를 만들어서 요청을 보낸 user의 id를 가지고 와서 user라는 키에 담아주고, 엔드포인트에서 받은 pk값, 즉 performance의 id값을 가지고 와서 performance의 키값에 담아주고 그것을 ReservationSerializer(data=resquest_data)에 담아주게 되었다. Body에 들어가게 되는 키값은 Reservation의 모델의 외래키 필드로 정해준 이름으로 따르게 된다.

Reservation모델을 보면 user와 performance로 만들어 놓은것을 models에서 볼 수 있다.

 

이제 postman을 이용해서 POST요청을 날려보자.

아무런 예약을 가지고 있지 않은 user를 만들어서 performance를 예약을 해보자.

 

 

성공이다!!!!

 

Body에 user와 performance의 id값을 넣어주지 않아도 예약이 생성되는 것을 볼 수 있다.

이때 user는 요청한 사용자의 token을 서버에서 디코딩해서 들고오는 것이기에 요청한 사용자가 바뀐다면 user에 들어가는 user의 id도 요청한 사용자에 따라 다르게 들어간다. 그리고 performacne의 id값도 엔드포인트에서 받기에 다른 performance를 보게 된다면 다른 performance의 id값이 들어오게 될 것이다.

 

 

혹시 몰라 mypage에서 reservation이 잘 나오나 확인을 해봤는데도 아주 잘나오는 것을 볼 수 있다.

 

이로써 Post시에 백앤드 영역에서 Body에 들어가는 값을 처리해서 프론트에서 토큰을 깐다거나 데이터를 힘들게 들고 오지 않아도 백앤드영역에서 처리하는 방법에 대해서 아주 많이 고민을 하고 알게 되었다.

 

@@++