44

所以在谷歌上搜索似乎普遍的共识是在 REST URI 中嵌入版本号是一种不好的做法和一个坏主意。

即使在 SO 上,也有强烈的支持者支持这一点。
例如API 版本控制的最佳实践?

我的问题是关于如何完成建议的解决方案,即在 django-rest-framework 中使用接受标头/内容协商来完成此任务。

它看起来像框架中的内容协商,
http ://django-rest-framework.org/api-guide/content-negotiation/ 已经配置为根据接受的 MIME 类型自动返回预期值。如果我开始对自定义类型使用 Accept 标头,我将失去框架的这种好处。

在框架中是否有更好的方法来实现这一点?

4

3 回答 3

49

更新:

现在正确支持版本控制。


您的链接中有一些答案:

我们发现将版本放在 URL 中是实用且有用的。它可以让您一目了然地知道您正在使用什么。正如公认的答案所暗示的那样,我们将 /foo 别名为 /foo/(最新版本)以便于使用、更短/更清晰的 URL 等。永远保持向后兼容性通常成本高昂和/或非常困难。我们更愿意提前通知弃用、此处建议的重定向、文档和其他机制。

所以我们采用了这种方法,并允许客户端在请求标头中指定版本(X-Version),我们是这样做的:

API 应用程序内部的结构:

.
├── __init__.py
├── middlewares.py
├── urls.py
├── v1
│   ├── __init__.py
│   ├── account
│   │   ├── __init__.py
│   │   ├── serializers.py
│   │   └── views.py
│   └── urls.py
└── v2
    ├── __init__.py
    ├── account
    │   ├── __init__.py
    │   ├── serializers.py
    │   └── views.py
    └── urls.py

项目 urls.py:

url(r'^api/', include('project.api.urls', namespace='api')),

api 应用级 urls.py:

from django.conf.urls import *

urlpatterns = patterns('',
    url(r'', include('project.api.v2.urls', namespace='default')),
    url(r'^v1/', include('project.api.v1.urls', namespace='v1')),
)

版本级 urls.py

from django.conf.urls import *
from .account import views as account_views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('account', account_views.AccountView)
router.register('myaccount', account_views.MyAccountView)
urlpatterns = router.urls

通过更改 path_info 创建一个中间件以切换到正确的代码,请注意项目级 url 中定义的命名空间('api')不灵活,需要在中间件中知道:

from django.core.urlresolvers import resolve
from django.core.urlresolvers import reverse


class VersionSwitch(object):

    def process_request(self, request):
        r = resolve(request.path_info)
        version = request.META.get('HTTP_X_VERSION', False)
        if r.namespace.startswith('api:') and version:
            old_version = r.namespace.split(':')[-1]
            request.path_info = reverse('{}:{}'.format(r.namespace.replace(old_version, version), r.url_name), args=r.args, kwargs=r.kwargs)

示例网址:

curl -H "X-Version: v1" http://your.domain:8000/api/myaccount/
于 2014-02-17T21:42:45.153 回答
35

One way of doing this is to have the versioning specified as part of the media type.

This is what GitHub currently do for their API.

You can also include media type parameters in your accept headers, eg Accept: application/json; version=beta, which will successfully match against JSONRenderer. You can then code your view to behave differently depending on the accepted media type, see here.

There's lots of different patterns for versioning in APIs, and I wouldn't say there's any great consensus around the right approach yet, but that'd be one reasonable possibility.


Update Jan 2015: Better versioning support will be incoming in the 3.1.0 release. See [this pull request]

Update March 2015: Docs for the versioning API are now available.

(https://github.com/tomchristie/django-rest-framework/pull/2285) for more details.

于 2013-01-17T13:17:22.653 回答
1

@James Lin 给出了很好的答案。在对答案@Mar0ux 的评论中询问如何处理损坏的HyperlinkedRelatedField字段。

我通过更改HyperlinkedRelatedFieldSerializerMethodField并调用来解决这个问题reverse,非常不明显,将额外的参数传递current_app给它。

例如,我有一个应用程序“fruits_app”,命名空间版本为“v1”、“v2”。我有水果模型的序列化器。所以要序列化 ​​url 我创建了一个字段

url = serializers.SerializerMethodField()

以及对应的方法:

def get_url(self, instance):
    reverse.reverse('fruits_app:fruit-detail',
        args=[instance.pk],
        request=request,
        current_app=request.version)

使用嵌套命名空间,您需要将这些命名空间添加到 current_app。例如,如果您有一个应用程序“fruits_app”,其命名空间版本为“v1”、“v2”和实例命名空间“bananas”,则序列化 Fruit url 的方法如下所示:

def get_url(self, instance):
    reverse.reverse('fruits_app:fruit-detail',
        args=[instance.pk],
        request=request,
        current_app='bananas:{}'.format(request.version))
于 2021-04-13T14:50:54.070 回答