[DRF] Versioning

강민성·2024년 9월 28일

DRF API Guide

목록 보기
19/28

버전 관리(Versioning)

버전 관리된 인터페이스는 배포된 클라이언트를 정중하게 종료하는 방법일 뿐이다.
— Roy Fielding

API 버전 관리를 통해 서로 다른 클라이언트 간의 동작을 변경할 수 있습니다. REST 프레임워크는 여러 가지 다른 버전 관리 방식들을 제공합니다.

버전 관리는 들어오는 클라이언트 요청에 따라 결정되며, 요청 URL이나 요청 헤더에 기반할 수 있습니다.

버전 관리에는 여러 가지 적절한 접근 방식이 있습니다. 버전 관리가 없는 시스템도 적합할 수 있으며, 특히 다수의 클라이언트가 있고 그들의 통제가 불가능한 장기적인 시스템을 개발하는 경우에는 더욱 그렇습니다.

REST 프레임워크에서의 버전 관리

API 버전 관리가 활성화되면, request.version 속성은 들어오는 클라이언트 요청에서 요청된 버전에 해당하는 문자열을 포함하게 됩니다.

기본적으로 버전 관리는 비활성화되어 있으며, request.version은 항상 None을 반환합니다.

버전에 따른 동작 변경

API의 동작을 어떻게 변경할지는 전적으로 사용자에게 달려 있지만, 일반적으로 새로운 버전에서 다른 직렬화 스타일을 사용하는 예를 들 수 있습니다. 예를 들어:

def get_serializer_class(self):
    if self.request.version == 'v1':
        return AccountSerializerVersion1
    return AccountSerializer

버전 관리된 API에서 URL을 역방향으로 찾기

REST 프레임워크에 포함된 reverse 함수는 버전 관리 체계와 연결됩니다. 현재 요청을 키워드 인자로 포함하는 것을 잊지 마세요.

from rest_framework.reverse import reverse

reverse('bookings-list', request=request)

위 함수는 요청된 버전에 맞는 URL 변환을 적용합니다. 예를 들어:

  • 만약 NamespaceVersioning을 사용하고 있으며 API 버전이 'v1'이라면, URL 조회는 'v1:bookings-list'가 될 것이고, 이는 http://example.org/v1/bookings/ 같은 URL로 변환될 수 있습니다.
  • 만약 QueryParameterVersioning을 사용하고 있으며 API 버전이 1.0이라면, 반환된 URL은 http://example.org/bookings/?version=1.0과 같이 나타날 수 있습니다.

버전 관리된 API와 하이퍼링크 직렬화기

URL 기반 버전 관리 체계와 함께 하이퍼링크 직렬화 스타일을 사용할 때, 시리얼라이저에게 requestcontext로 포함시키는 것이 중요합니다.

def get(self, request):
    queryset = Booking.objects.all()
    serializer = BookingsSerializer(queryset, many=True, context={'request': request})
    return Response({'all_bookings': serializer.data})

이렇게 하면 반환된 URL에 적절한 버전 관리 정보가 포함됩니다.

버전 관리 체계 구성

버전 관리 체계는 DEFAULT_VERSIONING_CLASS 설정 키에 의해 정의됩니다.

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning'
}

명시적으로 설정되지 않은 경우, DEFAULT_VERSIONING_CLASS의 값은 None이 됩니다. 이 경우 request.version 속성은 항상 None을 반환하게 됩니다.

개별 뷰에서 버전 관리 체계를 설정할 수도 있습니다. 하지만 일반적으로는 전체적으로 동일한 버전 관리 체계를 사용하는 것이 더 합리적입니다. 만약 개별적으로 설정해야 할 경우, versioning_class 속성을 사용합니다.

class ProfileList(APIView):
    versioning_class = versioning.QueryParameterVersioning

기타 버전 관리 설정

다음 설정 키들도 버전 관리를 제어하는 데 사용됩니다:

  • DEFAULT_VERSION: 버전 관리 정보가 없을 때 request.version에 사용할 값입니다. 기본값은 None입니다.
  • ALLOWED_VERSIONS: 설정된 경우, 이 값은 버전 관리 체계에서 반환할 수 있는 버전의 집합을 제한하며, 제공된 버전이 이 집합에 포함되지 않은 경우 오류를 발생시킵니다. DEFAULT_VERSION 설정에 사용된 값은 항상 ALLOWED_VERSIONS 집합에 포함됩니다 (None이 아닌 경우). 기본값은 None입니다.
  • VERSION_PARAM: 미디어 타입이나 URL 쿼리 파라미터에 사용되는 버전 관리 파라미터의 문자열입니다. 기본값은 'version'입니다.

자신만의 버전 관리 체계를 정의하고 개별 뷰나 뷰셋에서 default_version, allowed_versions, version_param 클래스 변수를 설정하여 사용할 수도 있습니다. 예를 들어, URLPathVersioning을 사용하려면 다음과 같이 할 수 있습니다:

from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView

class ExampleVersioning(URLPathVersioning):
    default_version = ...
    allowed_versions = ...
    version_param = ...

class ExampleView(APIView):
    versioning_class = ExampleVersioning

API 레퍼런스

AcceptHeaderVersioning

이 방식은 클라이언트가 Accept 헤더의 미디어 타입에 버전을 포함시켜 명시하도록 요구합니다. 버전은 미디어 타입 파라미터로 포함되며, 주된 미디어 타입을 보충하는 역할을 합니다.

다음은 Accept 헤더 버전 관리 스타일을 사용하는 HTTP 요청의 예입니다.

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0

위 요청에서 request.version 속성은 문자열 '1.0'을 반환하게 됩니다.

Accept 헤더에 기반한 버전 관리는 일반적으로 모범 사례로 간주되지만, 클라이언트 요구 사항에 따라 다른 스타일이 적합할 수도 있습니다.

벤더 미디어 타입과 함께 Accept 헤더 사용
엄격히 말하자면, json 미디어 타입은 추가 파라미터를 포함하도록 명시되어 있지 않습니다. 공용 API를 잘 정의된 형태로 구축하려는 경우, 벤더 미디어 타입을 사용하는 것이 좋습니다. 이를 위해, JSON 기반 렌더러를 사용자 정의 미디어 타입으로 구성할 수 있습니다.

class BookingsAPIRenderer(JSONRenderer):
    media_type = 'application/vnd.megacorp.bookings+json'

이제 클라이언트 요청은 다음과 같이 보일 것입니다:

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/vnd.megacorp.bookings+json; version=1.0

URLPathVersioning

이 방식은 클라이언트가 URL 경로의 일부로 버전을 명시하도록 요구합니다.

GET /v1/bookings/ HTTP/1.1
Host: example.com
Accept: application/json

URL 설정에는 버전 관리 체계에서 사용할 수 있도록 'version' 키워드 인자를 사용해 버전과 일치하는 패턴이 포함되어야 합니다.

urlpatterns = [
    re_path(
        r'^(?P<version>(v1|v2))/bookings/$',
        bookings_list,
        name='bookings-list'
    ),
    re_path(
        r'^(?P<version>(v1|v2))/bookings/(?P<pk>[0-9]+)/$',
        bookings_detail,
        name='bookings-detail'
    )
]

NamespaceVersioning (네임스페이스 버전 관리)

클라이언트 입장에서는 이 방식이 URLPathVersioning(경로 기반 버전 관리)과 동일합니다. 유일한 차이점은 URL 키워드 인자 대신 URL 네임스페이싱을 사용하여 Django 애플리케이션에서 설정하는 방식입니다.

GET /v1/something/ HTTP/1.1  
Host: example.com  
Accept: application/json

이 방식에서는 request.version 속성이 들어오는 요청 경로와 일치하는 네임스페이스를 기준으로 결정됩니다.

다음 예에서는 두 가지 다른 네임스페이스를 사용하여 동일한 뷰 세트에 두 가지 URL 접두사를 제공하고 있습니다.

# bookings/urls.py
urlpatterns = [
    re_path(r'^$', bookings_list, name='bookings-list'),
    re_path(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail')
]

# urls.py
urlpatterns = [
    re_path(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
    re_path(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]

URLPathVersioning과 NamespaceVersioning 둘 다 간단한 버전 관리 체계가 필요한 경우에 적절합니다. URLPathVersioning 방식은 소규모의 애드혹(ad-hoc) 프로젝트에 더 적합할 수 있고, NamespaceVersioning 방식은 대규모 프로젝트에서 관리하기 더 쉬울 것입니다.


HostNameVersioning (호스트 이름 버전 관리)

호스트 이름 버전 관리 방식에서는 클라이언트가 URL의 호스트 이름의 일부로 요청된 버전을 지정해야 합니다.

예를 들어, 다음은 http://v1.example.com/bookings/ URL에 대한 HTTP 요청입니다:

GET /bookings/ HTTP/1.1  
Host: v1.example.com  
Accept: application/json

기본적으로 이 구현은 호스트 이름이 다음과 같은 간단한 정규 표현식과 일치하기를 기대합니다:

^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$

여기서 첫 번째 그룹이 괄호로 묶여 있으며, 이는 호스트 이름에서 일치하는 부분을 나타냅니다.

호스트 이름 버전 관리 방식은 일반적으로 디버그 모드에서 사용하기 까다로울 수 있습니다. 디버그 모드에서는 보통 127.0.0.1과 같은 원시 IP 주소에 접근하기 때문입니다. 이 경우, localhost를 사용자 지정 서브도메인으로 액세스하는 방법에 대한 여러 온라인 튜토리얼이 도움이 될 수 있습니다.

호스트 이름 기반 버전 관리는 버전에 따라 요청을 다른 서버로 라우팅해야 하는 요구 사항이 있는 경우 특히 유용할 수 있습니다. 이 방식에서는 API 버전별로 다른 DNS 레코드를 설정할 수 있기 때문입니다.


QueryParameterVersioning (쿼리 파라미터 버전 관리)

이 방식은 URL에 버전을 쿼리 파라미터로 포함하는 간단한 스타일입니다. 예를 들어:

GET /something/?version=0.1 HTTP/1.1  
Host: example.com  
Accept: application/json

Custom versioning schemes (커스텀 버전 관리 체계)

사용자 정의 버전 관리 체계를 구현하려면, BaseVersioning을 서브클래싱하고 .determine_version 메서드를 재정의하면 됩니다.

예시

다음 예시는 요청된 버전을 결정하기 위해 사용자 정의 X-API-Version 헤더를 사용하는 방법입니다.

class XAPIVersionScheme(versioning.BaseVersioning):
    def determine_version(self, request, *args, **kwargs):
        return request.META.get('HTTP_X_API_VERSION', None)

버전 관리 체계가 요청 URL을 기반으로 하는 경우, 버전 관리된 URL이 어떻게 결정되는지도 수정해야 합니다. 이를 위해서는 클래스의 .reverse() 메서드를 재정의해야 합니다. 예시를 보려면 소스 코드를 참조하세요.

Reference

DRF API Guide - Versioning

profile
Back-end Junior Developer

0개의 댓글