Programming/Python

[Django] DRF Nested Router

True or False 2023. 6. 13. 14:42

URL 을 설계할 때 Nested Resource 에 대해 설정하려고 하면 코드가 복잡해지는 경우가 있다.

일단 Django 에서 DRF View 를 사용한다면 해당하는 부분에서 분기점을 나누어줘야 한다.

PK 가 있는 경우에 동작과 없는 경우에 동작으로 말이다.

 

urlpattenrs = [
	path('repositories/<int:repository_id>/folders/<int:id>', FolderView.as_view({'get': 'detail'})),
	path('repositories/<int:repository_id>/folders', FolderView.as_view({'get': 'list'})),
]

위의 코드를 보면 현재 두 개의 자원에 대한 키를 받고 있다.

하지만 만약에 키가 늘어나게 될 경우에는 복잡도가 커져 오히려 안 쓰는 것만 못 할 수도 있다.

그렇기에 해당 nested 관계를 표현하는 부분을 본인이 만들거나 패키지를 쓰는 것이 합리적이다.

거기에 있어 drf-nested-router 패키지가 적합하다.

 

일단 drf 페이지에서도 drf-nested-router 에 대해서도 소개하고 있으며 사용적인 측면에서도 편하다.

사용법은

# repository/urls.py
from django.urls import path, include
from rest_framework_nested import routers
from authentication.views import RepositoryView

router = routers.SimpleRouter()
router.register(r"repositories", RepositoryView)

repository_router = routers.NestedSimpleRouter(
    router, r"repositories", lookup="repository"
)


urlpatterns = [
    path(r"", include(router.urls)),
]

# folder/urls.py
repository_router.register(r"folders", FolderView)

urlpatterns = [
    path(r"", include(repository_router.urls)),
]
# folder/views.py
class FolderView(ModelViewSet):
    queryset = Folder.objects.all()
    serializer_class = FolderSerializer

    def get_queryset(self):
        return self.queryset.filter(repository_id=self.kwargs["repository_pk"])

 

이렇게 하면 간단하게 nested resource 를 가지는 router 를 생성할 수 있게 된다.

만약에 뎁스를 늘리고 싶은 경우에는 아래와 같이

folder_router = routers.NestedSimpleRouter(
    repository_router, r"folders", lookup="folder"
)

folder_router.register(r"items", FolderItemView)

urlpatterns = [
    path(r"", include(folder_router.urls)),
]

설정해주면 뎁스를 늘릴 수 있다.

 

주의할 점

일단 가장 중요한 것은 설계이다.

우후죽순 URL을 만들 수는 없기 때문이다.

그렇기에 요구를 파악해 필요한 것만 만들어야 한다.

이러한 부분에서 제대로 하지 못한다면 안하니마니 못하다.

path variable 영역과 query parameter 영역을 잘 나누어서 사용하는 것이 필요하다.

 

예시로 들자면 이전에 서비스적으로 문제가 생겼던 부분인데

백엔드에서 bulk_delete 라는 부분의 기능이 추가되었다.

그런데 해당하는 bulk_delete 의 경우에는

/items?key=value&key1=value

이렇게 필터를 걸어서 동작을 하는 부분이었다.

그렇다면 items 에 query_parameter 가 없다면 어떻게 되나? 전체 삭제를 한다.

이게 기획에서 올라왔고 개발을 해야한다고 해서 의문을 제시했지만 묵살당하고 개발을 했다.

일단 문제는 발생했다.

 

백하고 프론트에서 테스트를 하고 실서버에 올렸고 문제가 없이 지나가다가

중간에 프론트에서 query parameter 에 대한 부분을 변경하면서 전체적으로 query parameter 가

빠지면서 리퀘스트가 되는 경우가 생겼다.

그때 누군가는 bulk_delete를 실행했고 문제는 터졌다.

기존 예상치는 100건의 데이터가 삭제되어야하는데 약 6만건 정도의 데이터가 삭제된것이다.

그렇게 보고 받은 후 해당하는 데이터는 어찌저찌 살렸다.

 

이때 여러가지의 문제를 생각할 수도 있다.

기획에 대한 문제, validation 에 대한 문제, 등등… 이 있지만

나는 그 중에서 URL items 가 가지고 있는 역할이 너무 컸다는 것도 문제의 요인이 되었던 것 같다.

 

결국에 items 로 처리하는 View 는 하나이다.

문제는 언제든지 일어날 수 있다는 것이다.

만약에 벌크 딜리트를 하는 url이 따로 있었다면? 해당하는 문제를 막을 수 있었을 수도 있다.

 

그때 당시에는 유저 작업물에 대해서 검수 후 완료 혹은 재작업, 파기 였는데

한 유저에 대해서 작업을 파기해야했기에 유저를 특정하는 것이 가능했기에 만약 url 로 표현하면

URL user/<user_id>/items 이렇게 되었을 것이다. 이렇게 했으면 bulk_delete 가 되더라도 무조건 해당 user_id에 국한이 되기에 잘못이 발생해도 리스크를 최소화할 수 있지만 items 는 그냥 전체에 대한 것이기에 문제가 발생한다.

하지만 이 부분도 결국에는 기획의 부재에서 일어난 것이기에 애매할 수도 있다.