3. Class-based Views

hyuckhoon.ko·2023년 10월 1일
0

FBV 방식이 아닌 CBV 방식으로 API를 만들 수 있다.
CBV로 구현을 하면 공통된 기능을 재사용할 수 있고 이는 결국 DRY(Don't Repat Yourself)를 가능하게 한다.


1. CBV

from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from django.http import Http404

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


class SnippetList(APIView):
    """List all snippets, or create a new snippet."""

    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class SnippetDetail(APIView):
    """
    - Retrieve
    - Update
    - Delete a snippet instance.
    """

    def get_object(self, pk):
        try:
            return Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    def post(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        snippet = self.get_object(pk)
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

기본 튜토리얼을 실습해보며 생각해볼 거리가 있었다.

1. 암묵적인 것보다 명시적인 것이 낫다

아래와 같이 GET 메서드 요청 즉, 조회에 대해 암묵적인 응답 상태코드는 200이다. 하지만 암묵적인 것보다 명시적인 것이 낫다는 파이썬의 선처럼,
실무에서는 단 1%의 오해나 실수가 생기지 않도록 명시적으로 쓰는 편이 낫겠다는 생각을 하고 그렇게 해오고 있다.

	# 전
    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)
        
    # 후
    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data, status=status.HTTP_200_OK)

2. 관심사 분리

get_object 라는 이름의 메서드를 생성함으로써 디테일 API에서 이 메서드를 재사용하고 있다.(DRY)
3년 전 처음 튜토리얼을 했을 당시에 이런 디테일이 눈에 보이지 않았었다.
실무를 하면 할수록, 관심사 분리가 잘 된 코드는 가독성이 좋고 이는 결국 확장성 그리고 유지보수에도 유리하다는 것을 느끼게 됐다.

3. 내 약점 파악

serialzier를 통한 update 메서드를 사용하는 법을 다시 상기시킬 수 있었다. 비즈니스 도메인 로직을 services.py에 이전하다보니, serialzier에서 update하는 방법을 잊고 있었다.

snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
    serializer.save()

시리얼라이저에 업데이트 할 snippet 인스턴스와 바디 데이터를 넘겨준다.
유효성 검증 후 저장하는 모습이다.


2. mixins 적용

CBV 기반 API의 장점은 mixins로 재사용 가능한 동작을 쉽게 구현할 수 있다는 것이다.
무슨 말일까? API 기능의 메타 행동은 CRUD다.
다양한 API들은 이러한 반복되는 패턴들의 특징을 가지고 있는데, 이를 쉽게 구현할 수 있게 하는 것이 mixins다.

from rest_framework import mixins
from rest_framework import generics

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


class SnippetList(
    mixins.ListModelMixin,
    mixins.CreateModelMixin,
    generics.GenericAPIView,
):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class SnippetDetail(
    mixins.RetrieveModelMixin,
    mixins.UpdateModelMixin,
    mixins.DestroyModelMixin,
    generics.GenericAPIView,
):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

mixins으로 .retrieve(), .update() and .destroy() actions. 기능을,
GenericAPIView로 중심 기능들을 구현했다.


3. generic class-based views 적용

mixins 마저 복잡하다는 걸까?
사실 불필요한 부분이 있긴 했다.
get, put, delete 메서드가 비슷한 형태로 반복되고 있다.

FBV -> APIView -> mixins 까지 진화했지만, 한 걸음 더 나아간다.
mixins 마저 이미 적용된 generic views를 도입하기에 이르른다.
코드의 양은 더 줄어들게 된다.

from rest_framework import generics

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

비즈니스 요구사항이 복잡한 경우 generic class-based 뷰를 도입하기 쉽지 않다.
하지만 간단한 로직에는 충분히 적용할 수 있다.

0개의 댓글