一个好的 API 设计应该是 “版本化” 的:变更和新的功能应该在 API 新版本中实现,而不是在一个版本上持续更改。与Web应用程序不同,您可以完全控制客户端和服务器端 代码,API 是为了给超出控制的客户端使用。
因此,应该尽可能的保持向后兼容性,如果有一些变化不能向后兼容,你应该在新版本的 API 中采用它同时增加版本号。
现有客户端可以继续使用旧版本的 API,新的或升级的客户端可以在新的 API 版本中获得新的功能。
关于如何实现 API 版本,一个常见的做法是在 API 的 URL 中嵌入版本号。例如:
http://example.com/v1/users
代表/users
版本 1 的 API。
另一种 API 版本化的方法, 最近用的非常多的是把版本号放入 HTTP 请求头,通常是通过Accept
头,如下:
// 通过参数
Accept: application/json; version=v1
// 通过vendor的内容类型
Accept: application/vnd.company.myapp-v1+json
这两种方法都有优点和缺点, 而且关于他们也有很多争论。 下面我们描述在一种 API 版本混合了这两种方法的一个实用的策略:
v1
,v2
)。 自然,API 的 url 将包含主要的版本号。Accept
HTTP 请求头 确定小版本号编写条件代码来响应相应的次要版本。为每个模块提供一个主要版本, 它应该包括资源类和控制器类为特定服务版本。
更好的分离代码, 你可以保存一组通用的基础资源和控制器类, 并用在每个子类版本模块。 在子类中, 实现具体的代码例如Model::fields()
。
你的代码可以类似于如下的方法组织起来
api/
├──common/ # 公共基础层(跨版本复用资源)
│ ├──controllers/ # 基础控制器(抽象类/通用方法)
│ │ ├──BaseUserController.php# 用户相关基础控制逻辑
│ │ └──BasePostController.php# 内容相关基础控制逻辑
│ ├──models/ # 基础数据模型(共享属性/方法)
│ │ ├──BaseUser.php # 用户模型基类(含通用字段验证)
│ │ └──BasePost.php # 内容模型基类(含通用数据处理)
│ └──services/ # 公共服务层(跨版本业务逻辑)
│ ├──UserService.php# 用户相关通用服务(如权限校验)
│ └──PostService.php# 内容相关通用服务(如格式转换)
│
├──modules/ # 版本模块层(按API版本隔离)
│ ├──v1/ # v1版本API(独立部署/迭代)
│ │ ├──controllers/ # 版本专属控制器(继承基础控制器)
│ │ │ ├──UserController.php# v1用户接口实现
│ │ │ └──PostController.php# v1内容接口实现
│ │ ├──models/ # 版本专属模型(继承基础模型)
│ │ │ ├──User.php # v1用户模型(扩展字段/逻辑)
│ │ │ └──Post.php # v1内容模型(扩展字段/逻辑)
│ │ ├──services/ # 版本专属服务(业务逻辑隔离)
│ │ │ ├──UserService.php# v1用户业务实现
│ │ │ └──PostService.php# v1内容业务实现
│ │ └──Module.php # v1版本模块配置(路由/依赖)
│ │
│ └──v2/ # v2版本API(结构同v1,独立迭代)
│ ├──controllers/
│ │ ├──UserController.php
│ │ └──PostController.php
│ ├──models/
│ │ ├──User.php
│ │ └──Post.php
│ ├──services/
│ │ ├──UserService.php
│ │ └──PostService.php
│ └──Module.php
│
└──routes/ # 路由配置层(统一入口管理)
├──v1.php # v1版本路由定义
└──v2.php # v2版本路由定义
你的应用程序配置应该这样
return [
'modules' => [
'v1' => [
'class' => 'app\modules\v1\Module',
],
'v2' => [
'class' => 'app\modules\v2\Module',
],
],
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']],
['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']],
],
],
],
];
因此,
http://example.com/v1/users
将返回 版本1 的用户列表, 而http://example.com/v2/users
将返回 版本2 的用户。
使用模块,将不同版本的代码隔离。通过共用基类和其他类跨模块重用代码也是有可能的。
services
层剥离业务逻辑,使控制器专注于请求响应处理,模型专注于数据交互,符合 "单一职责原则"。BaseXXX
基类实现跨版本代码复用,版本专属类仅扩展差异逻辑,减少重复代码。