FBV 방식이 아닌 CBV 방식으로 API를 만들 수 있다.
CBV로 구현을 하면 공통된 기능을 재사용할 수 있고 이는 결국 DRY(Don't Repat Yourself)를 가능하게 한다.
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)
기본 튜토리얼을 실습해보며 생각해볼 거리가 있었다.
아래와 같이 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)
get_object 라는 이름의 메서드를 생성함으로써 디테일 API에서 이 메서드를 재사용하고 있다.(DRY)
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 인스턴스와 바디 데이터를 넘겨준다.
유효성 검증 후 저장하는 모습이다.
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
로 중심 기능들을 구현했다.
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 뷰를 도입하기 쉽지 않다.
하지만 간단한 로직에는 충분히 적용할 수 있다.