Spring 의 Controller 와 Service 부분을 Django 에 맞게 바꿔보자!
Spring 의 경우 Service 와 Controller 를 나누어 비즈니스 로직을 따로 구분하였지만
우선 Django 에 익숙한 상태가 아니기 때문에 일단은 View 에 로직을 모두 구현하고
리팩토링을 하면서 분리해보자!
class FixExtensionView(APIView):
def post(self, request, extension):
extension_data = {'extension': extension, 'checked': True}
serializer = ExtensionRequestSerializer(data=extension_data)
if serializer.is_valid():
extension_instance, created = Extension.objects.get_or_create(extension=extension)
extension_instance.checked = True
extension_instance.save()
response_serializer = ExtensionResponseSerializer(extension_instance)
return Response(response_serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, extension):
try:
extension_instance = Extension.objects.get(extension=extension)
extension_instance.checked = False
extension_instance.save()
response_serializer = ExtensionResponseSerializer(extension_instance)
return Response(response_serializer.data, status=status.HTTP_200_OK)
except Extension.DoesNotExist:
return Response({'error': 'Extension not found'}, status=status.HTTP_404_NOT_FOUND)
def get(self, request):
extensions = Extension.objects.filter(checked=True)
serializer = ExtensionResponseSerializer(extensions, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
APIView 를 통해 HTTP 메서드에 따라 메서드 구현을 하고 메서드에 맞는 매핑이 가능하다.
(이와 비슷하게 @api_view [여기서는 @를 데코레이션이라고 부른다] 를 사용할 수도 있다 )
from django.urls import path
from .views import FixExtensionView
urlpatterns = [
path('api/fix-extension/', FixExtensionView.as_view(), name='fix-extension-list'),
path('api/fix-extension/<str:extension>/', FixExtensionView.as_view(), name='fix-extension-detail'),
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls'))
]

위의 에러가 뜬다면 장고 앱을 만든 뒤 settings.py 에 만든 app 을 추가 하지 않았기 떄문이므로
이를 추가해준다.


정상적으로 서버 띄우기 성공!

여기서도 Spring 때와 마찬가지로 어김없이 CORS 에러가 발생한다
def post(self, request, extension):
print(f"Received POST request with extension: {extension}")
extension_data = {'extension': extension, 'is_checked': True}
serializer = ExtensionRequestSerializer(data=extension_data)
if serializer.is_valid():
extension_instance = Extension.objects.get(extension=extension)
extension_instance.is_checked = True
extension_instance.save()
response_serializer = ExtensionResponseSerializer(extension_instance)
return Response(response_serializer.data, status=status.HTTP_200_OK)
print(f"Serializer errors: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
위의 코드를 살펴볼 경우 그냥그냥 이상적으로 보인다.
extension 을 받아서 requestSerializer 를 통해 dto 형태로 만들고
만약 그 값이 유효하다면 (== is_vaild) 해당 extension 에 해당하는 값을 가져오고
check를 True 로 바꾸고 responseSerializer 에 맞게 반환한다.
그러나 위의 코드를 실행하고 위의 api 를 수행한다면

이러한 에러가 발생한다. 이미 존재하는 extension 이기 때문에 에러가 발생한다는 것이다.
분명 로직은 이상적인 것 같은데 왜일까?
그 이유는 다음과 같다.
is_vaild() 를 통해 생성한 serializer 를 유효한지 확인하는 작업은
해당 데이터가 데이터베이스에서 유효한지를 살펴보게 된다.
고정 확장자의 경우 이미 DB 에 해당 값들이 모두 저장되어 있는 상태이다!
(”고정” 이기 때문에 DB 설계 시 이미 테이블에 들어있다고 가정하였다.)
그러므로 만약 이미 bat 가 존재하는데 bat 가 DB 에서 유효하냐를 물어보게 된다면
당연히 에러가 발생한다. extension 값은 unique 하기 때문이다!!
따라서 이를 위해서는 is_vaild 를 통한 유효 검증 후 값 조회가 아닌 값 조회를 우선한 뒤
이후 경우에 대한 예외처리를 수행하는 것이 올바른 로직이 된다!!!!
def post(self, request, extension):
print(f"Received POST request with extension: {extension}")
try:
# 이미 존재하는 extension 객체를 찾는다.
extension_instance = Extension.objects.get(extension=extension)
extension_instance.is_checked = True
extension_instance.save()
response_serializer = ExtensionResponseSerializer(extension_instance)
return Response(response_serializer.data, status=status.HTTP_200_OK)
except Extension.DoesNotExist:
# 존재하지 않으면 새로운 객체를 생성한다.
print(f"잘못된 고정 확장자 요청입니다.")
return Response(response_serializer.data, status=status.HTTP_400_BAD_REQUEST)
print(f"Serializer errors: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
try except 를 통해 처리를 하게 되고 위의 설명 과정 처럼
먼저 extension 에 대한 조회를 해보고 존재하는 경우 (정상 동작)
만약 존재하지 않는 extension 에 대해서는 에러를 내보낸다.
(단순히 4xx 이 아닌 새로 확장자를 생성한다는 등의 작업이 가능하지만 일단은 임시로 에러를
반환하도록 한다.)


정상 동작한다!
일단은 커스텀 확장자 부분은 놔두고 고정 확장자에 대한 Spring boot 코드를
Django 를 이용해 구현하는 것을 수행해보았다!
Spring 의 Controller/Service 와 DTO, Entity 등이 어떻게 Django 에서는 이루어지는지
알 수 있는 재밌는 경험이었다 🫠
(물론 Spring 에서 추상 클래스라던지 인터페이스 부분은 어떻게 할지 결정하지 못했지만…)
결론은 쓰는 프레임워크가 다를 뿐 API 요청을 처리하기 위한 고민은 매한가지라는 것이다.
url 을 어떤 app 으로 나누어 경로를 매핑할지
Model, Template, View 로 구분하고 Json 데이터 처리를 위한 객체로 만들고 (Serializer)
API 응답을 위한 비즈니스 로직은 어떻게 구현해하는지 (DB 조회 시, is_vaild 문제, try-catch)
결국 백엔드가 해야할 일을 온전히 하기 위해 고민하는 것들은 어떤 프레임워크든 비슷한 것 같다!!
그러니까 많이 경험하면서 프레임워크에 종속되지 않는 느슨한 결합을 가진
개발자가 되어야 할 것 같다! 😎
p.s View 에서 비즈니스 로직을 분리하는 것과 추상 클래스라던지 인터페이스도 한번 수정해보자