Django Rest Framework를 이용한 빠른 REST API 구현

박지인·2021년 10월 2일
1

인생은 선택의 연속이다. 오늘 아침에 일어나 아침밥을 먹을지 말지, 오늘 귀찮은데 운동을 할지 말지 등등. 인생은 수많은 선택의 연속이다. 개발도 인생과 마찬가지이다. 우리는 문제를 해결하기 위해 수많은 선택의 순간에 놓이게 된다. 같은 것을 구현하려고 해도 수많은 프레임워크와 라이브러리들을 고를 수 있다.

나는 이번 년도 5월 17일 입대했다. 지금은 일병 약장을 달고 나름 하루가 보람차게 생활하고 있다. 입대 후 발견한 국방오픈소스아카데미라는 사이트에서 개발 공부를 하고 있는데, 얼마 전부터 이 곳에서 해커톤을 진행하여 즐거운 마음으로 참여하고 있다. 개발 진행 중 REST API를 구현해야할 필요가 생겼는데, 나는 그것의 구현을 위해 Django Rest Framework를 선택했다.

Django Rest Framework

Django Rest Framework는 웹 프레임워크인 Django의 기능을 확장하여 REST API를 설계할 수 있도록 도와준다. 나는 시간이 없다. 어느 해커톤이 안 그러겠냐마는, 군대에서의 해커톤은 무엇보다 시간 싸움이다. DRF는 Django 특유의 높은 생산성과 더불어 DRF가 자체적으로 지원하는 ViewSets등의 기능을 통해 빠른 REST API 구현을 가능토록 할것이다.

API 설계

ERD Cloud를 사용하여 그렸다.

규모가 큰 프로젝트가 아니기에 ERD까지 그리는건 낭비가 아닌가 싶기도 하다. 무엇보다 Django가 기본적으로 역참조를 지원하기에 위의 ERD는 실재 DB의 구조를 잘 반영하지도 못한다.

그러나 해커톤은 팀 프로젝트이다. 팀 프로젝트에서 팀원 모두가 한 사람처럼 생각한다는 것은 상상 이상으로 어려운 일이며, 그렇기에 ERD는 팀원들 모두가 같이 볼 자료가 되어준다는 점에서 충분한 가치를 제공한다. 추가로 해당 ERD가 실재 관계형 DB의 구조를 잘 나타낼 수 없다고 해도, Django Models간의 관계와 필드들, 그리고 필드의 종류들을 한 눈에 파악할 수 있다는 점에서 ERD는 시간을 투자할 만큼 충분한 가치가 있다.

구현

Django는 Model, Template, View의 MTV Pattern을 가진다. DRF는 이와 유사한 Model, Serializer, View로 구성되어 있다.

Model은 Django로 개발할때와 동일한 기능을 하며, 원래와 동일하게 작성하면 된다. View는 이제 Template이 아니라 Serializer를 위해 데이터를 처리한다는 것을 제외하면 비슷하다. Serializer는 encode와 decode를 수행하는 객체로, 데이터가 어떤 형태로 드나드는지를 정의한다. 그러나 Template과는 달리 입력한 데이터를 기반으로 create, update등을 하기 위한 함수들을 작성할 수 있다는 점에서 Template보다는 어느 정도 비즈니스 로직을 다룬다고 볼 수 있다.

Model

모델의 구현은 기존의 Django를 사용할때와 같으니 생략한다.

Serializer

우리가 하나하나 Serializer를 바닥부터 만들어 설정할 수도 있지만, DRF는 빠른 API 구현을 위해 다양한 종류의 미리 만들어진 Serializer를 제공한다. 그 중 나는 HyperlinkedModelSerializer를 이용하기로 했다. 해당 Serializer를 이용하면 기존의 id(fk)값을 사용한 모델간의 관계를 해당 모델 인스턴스의 정보를 얻을 수 있는 엔드포인트로 보여주게 된다. 빠른 시간 안에 RESTful한 API를 구현하기 위한 최고의 선택이라고 생각한다.

# Portfolio 객체를 위한 Serializer 예시.
class PortfolioSerializer(serializers.HyperlinkedModelSerializer):
    portfolio_items=PortfolioItemSerializer(many=True)
    specification_cards=SpecificationCardSerializer(many=True)

    class Meta:
        model=Portfolio
        fields='__all__'
        read_only_fields=['created_at', 'updated_at']

    def create(self, validated_data):
        portfolio_items_data=validated_data.pop('portfolio_items')
        specification_cards_data=validated_data.pop('specification_cards')

        portfolio=Portfolio.objects.create(**validated_data)

        for portfolio_item_data in portfolio_items_data:
            PortfolioItem.objects.create(portfolio=portfolio, **portfolio_item_data)
        for specification_card_data in specification_cards_data:
            SpecificationCard.objects.create(portfolio=portfolio, **specification_card_data)

        return portfolio

Nested Serializer

위의 예시를 보면 portfolio_items, specification_cards 등의 필드를 다른 Serializer로 지정한 것을 볼 수 있다. 이는 Nested Serializer라고 불리는 구조로, 해당 필드는 각 요소들의 세부 정보를 얻어올 수 있는 엔드포인트가 아닌 해당하는 요소들의 정보를 포함한 상태로 요청과 응답을 처리한다. 그러나 해당 필드는 기본적으로 read_only 값이다. 나는 PortfolioSerializer를 통해 해당하는 객체들도 생성하기를 원하기 때문에 create 메소드를 오버라이딩 했다.

View

View는 Django의 그것과 같이 함수형으로 하나하나 작성할 수도 있다. 그러나 DRF에서 기본적으로 제공하는 Serializer들과 같이, View도 빠른 구현을 위해 DRF가 제공하는 것들이 있다. Generic ViewViewSet이 그것이다. 그 중 빠른 구현을 위해 ViewSet를 사용하기로 결정했다.

ViewSet

ViewSet은 DRF가 제공하는 여러 CBV들 중 가장 높게 추상화 되어있으며, 가장 적은 코드로 빠르게 API를 구현할 수 있는 방법이다. 사용 방법은 아래와 같다.

# Portfolio 객체를 위한 ViewSet 예시.

from portfolio.models import Portfolio
from portfolio.serializers import PortfolioSerializer

class PortfolioViewSet(viewsets.ModelViewSet):
    queryset=Portfolio.objects.all()
    serializer_class=PortfolioSerializer

끝이다. 이 정도의 코드만 작성하면 list, create, update, delete등의 기능들을 직접 구현할 필요 없이 DRF가 처리해준다.

ViewSet은 이토록 높은 수준의 추상성을 띄기에 빠르고 쉽게 API를 구현할 수 있다는 장점이 있지만, 이는 곧 프로그래머가 View의 작동에 개입하기 위해 거쳐야 할 층이 하나 더 생김을 의미하기도 한다. View를 구현하기 위한 많은 장단점들을 고려하여 적절한 방법을 취하면 될 것이다.

Routing

이제 기초적인 작업들은 끝났다. 남은건 우리가 구현한 API를 URL을 통해 접근할 수 있도록 라우팅 하는 것이다. 나는 위에서 ViewSet을 사용하였기 때문에 라우팅도 한결 쉽게 할 수 있다.

# config/urls.py

from django.contrib import admin
from django.urls import path, include

from rest_framework.routers import DefaultRouter

from users.views import UserViewSet ,UserReviewViewSet
from mentoring.views import MentoringViewSet, AssignmentViewSet
from portfolio.views import PortfolioViewSet, PortfolioItemViewSet, SpecificationCardViewSet
from questions.views import QuestionViewSet, QuestionCommentViewSet
from tags.views import TagViewSet


router=DefaultRouter(trailing_slash=False)
# user
router.register('user', UserViewSet)
router.register('user-review', UserReviewViewSet)
# mentoring
router.register('mentoring', MentoringViewSet)
router.register('assignment', AssignmentViewSet)
# portfolio
router.register('portfolio', PortfolioViewSet)
router.register('portfolio-item', PortfolioItemViewSet)
router.register('specification-card', SpecificationCardViewSet)
# questions
router.register('question', QuestionViewSet)
router.register('question-comment', QuestionCommentViewSet)
# tags
router.register('tag', TagViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api-auth/', include('rest_framework.urls')),
    path('', include(router.urls)),
]

ViewSet을 불러와 router에 register만 해주면 나머지는 DRF가 알아서 처리해준다.

마무리

끝났다! 이제 runserver를 한 다음 순식간에 만들어낸 API를 감상하자.

0개의 댓글