文字
分享

版本

版本

你的API应该是版本化的。不像你完全控制在客户端和服务器端Web应用程序代码, 对于API,您通常没有对API的客户端代码的控制权。 因此,应该尽可能的保持向后兼容性(BC),如果一些不能向后兼容的变化必须引入 APIs,你应该增加版本号。你可以参考Semantic Versioning 有关设计的API的版本号的详细信息。

关于如何实现API版本,一个常见的做法是在API的URL中嵌入版本号。 例如,http://example.com/v1/users代表/users版本1的API. 另一种API版本化的方法最近用的非常多的是把版本号放入HTTP请求头,通常是通过Accept头, 如下:

1

2

3

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">// 通过参数</span><span style="box-sizing: border-box;">Accept:</span> application/json; version=v1

<span style="box-sizing: border-box;">// 通过vendor的内容类型</span><span style="box-sizing: border-box;">Accept:</span> application/vnd.company.myapp-v1+json

</code>

这两种方法都有优点和缺点, 而且关于他们也有很多争论。 下面我们描述在一种API版本混合了这两种方法的一个实用的策略:

  • 把每个主要版本的API实现在一个单独的模块ID的主版本号 (例如 v1v2)。 自然,API的url将包含主要的版本号。
  • 在每一个主要版本 (在相应的模块),使用 Accept HTTP 请求头 确定小版本号编写条件代码来响应相应的次要版本.

为每个模块提供一个主要版本, 它应该包括资源类和控制器类 为特定服务版本。 更好的分离代码, 你可以保存一组通用的 基础资源和控制器类, 并用在每个子类版本模块。 在子类中, 实现具体的代码例如 Model::fields()

你的代码可以类似于如下的方法组织起来:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;">api/

    common/

        controllers/

            UserController<span style="box-sizing: border-box;">.php</span>

            PostController<span style="box-sizing: border-box;">.php</span>

        models/

            User<span style="box-sizing: border-box;">.php</span>

            Post<span style="box-sizing: border-box;">.php</span>

    modules/

        v1/

            controllers/

                UserController<span style="box-sizing: border-box;">.php</span>

                PostController<span style="box-sizing: border-box;">.php</span>

            models/

                User<span style="box-sizing: border-box;">.php</span>

                Post<span style="box-sizing: border-box;">.php</span>

        v2/

            controllers/

                UserController<span style="box-sizing: border-box;">.php</span>

                PostController<span style="box-sizing: border-box;">.php</span>

            models/

                User<span style="box-sizing: border-box;">.php</span>

                Post<span style="box-sizing: border-box;">.php</span></code>

你的应用程序配置应该这样:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">return</span> [

    <span style="box-sizing: border-box;">'modules'</span> => [

        <span style="box-sizing: border-box;">'v1'</span> => [

            <span style="box-sizing: border-box;">'basePath'</span> => <span style="box-sizing: border-box;">'@app/modules/v1'</span>,

        ],

        <span style="box-sizing: border-box;">'v2'</span> => [

            <span style="box-sizing: border-box;">'basePath'</span> => <span style="box-sizing: border-box;">'@app/modules/v2'</span>,

        ],

    ],

    <span style="box-sizing: border-box;">'components'</span> => [

        <span style="box-sizing: border-box;">'urlManager'</span> => [

            <span style="box-sizing: border-box;">'enablePrettyUrl'</span> => <span style="box-sizing: border-box;">true</span>,

            <span style="box-sizing: border-box;">'enableStrictParsing'</span> => <span style="box-sizing: border-box;">true</span>,

            <span style="box-sizing: border-box;">'showScriptName'</span> => <span style="box-sizing: border-box;">false</span>,

            <span style="box-sizing: border-box;">'rules'</span> => [

                [<span style="box-sizing: border-box;">'class'</span> => <span style="box-sizing: border-box;">'yii\rest\UrlRule'</span>, <span style="box-sizing: border-box;">'controller'</span> => [<span style="box-sizing: border-box;">'v1/user'</span>, <span style="box-sizing: border-box;">'v1/post'</span>]],

                [<span style="box-sizing: border-box;">'class'</span> => <span style="box-sizing: border-box;">'yii\rest\UrlRule'</span>, <span style="box-sizing: border-box;">'controller'</span> => [<span style="box-sizing: border-box;">'v2/user'</span>, <span style="box-sizing: border-box;">'v2/post'</span>]],

            ],

        ],

    ],

];

</code>

因此,http://example.com/v1/users将返回版本1的用户列表,而 http://example.com/v2/users将返回版本2的用户。

使用模块, 将不同版本的代码隔离。 通过共用基类和其他类 跨模块重用代码也是有可能的。

为了处理次要版本号, 可以利用内容协商 功能通过 yii\filters\ContentNegotiator 提供的行为。contentNegotiator 行为可设置 yii\web\Response::acceptParams 属性当它确定 支持哪些内容类型时。

例如, 如果一个请求通过 Accept: application/json; version=v1被发送, 内容交涉后,yii\web\Response::acceptParams将包含值['version' => 'v1'].

基于 acceptParams 的版本信息,你可以写条件代码 如 actions,resource classes,serializers等等。

由于次要版本需要保持向后兼容性,希望你的代码不会有 太多的版本检查。否则,有机会你可能需要创建一个新的主要版本。