API设计指南-RestAPI设计最佳实践

类似于阿里巴巴的开发规范或者谷歌的开发规范,API设计是目前很多场景下的基本功,所以这里给出一个笔者的最佳实践。

一、引言

REST,即Representational State Transfer的缩写,关于RESTful架构,可以参考《架构之美》中的定义。

  • 客户端和服务器之间的交互在请求之间是无状态的,每个请求都必须包含理解请求的全部信息。
  • 在此基础上,服务更容易实现分布式、水平扩展、异步处理和可重入(幂等请求)。

REST架构中的基本定义

  • 资源(Resource): 网络上的所有内容都表述成一个资源、一个实体或者一个具体的信息,它可以是一段文本、一张图片、一首歌曲或者一种服务。
  • 统一资源定位符(URI, Universal Resource Identifier): 一个资源的识别符或者说是一个地址,通过URI可以定位到一个唯一的资源,网络上每个资源都有一个唯一的标识符。
  • 状态转换(State Transfer): 所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。客户端和服务器端互动的过程,通常涉及到服务器端数据和状态变化的过程,比如文件被修改、访问数量增加等。
  • 使用基本的HTTP请求方法。

GET: 获取资源

POST: 更新资源

PUT: 更新资源

DELETE: 删除资源

  • Hypermedia: 应用程序状态的引擎,实现hypermedia是API的最佳实践之一。

二、基本的设计原则

1. 协议(Protocol)

API与客户端通信的协议,应该尽量使用HTTPS。除此之外,白名单机制、VPN可以提高更高的安全性。遵循“所有人应该知道他所需要完成工作必备的最少的知识”的原则。而一个接口,只有在访问者必须使用它时,才告诉这个访问者。

2. 域名(Domain)

应该将API部署到专用域名之下,好处显而易见。

  • 更容易做动静分离。
  • 更容易做服务降级和限流。
  • 更容易做到高吞吐量。
  • 更容易做流量分发。
  • 更容易进行之后的水平扩展和服务拆分。

示例:https://api.groupname.domain.io/

更好的实践是将服务分组,并且根据情况进行必要的层次和分组(group)。这里的group可以包含事业部,也可以包含不同的客户,更可以包含不同的层次。

规范:https://api.[service-group].domain.io/

3. 基本URL(Root URL)

基本的URL,在此文中指的是除了服务拆分之外的URL,这里的最佳实践是可以包含环境、版本、分类、层次等,但是一个基本的URL,已经能决定除了接口或者服务粒度以外的所有事情,或者说可以决定由一个作战单元(个人或者小的敏捷团队)日常维护的工作内容了。

示例:https://api-dev.groupname.domain.io/mobile/v1/comment/[...]

  • 此处dev表示开发环境(这里的api默认表示生产环境,api-dev表示开发环境,职责仍然是唯一的)。
  • groupname表示分组(可以包含更多层次信息或者更多分层,但是最佳实践是一般domain不超过两层,也可以默认约定.io域名后缀全部是为api部署服务)。
  • mobile表示面向移动端。
  • v1表示接口版本。
  • comment表示此类服务为评论服务。

4. 环境划分原则(Env)

整体服务架构的划分原则不在此文档讨论范围之内,而一个最佳实践是在domain当中就对环境作区分。

规范:https://api-[env-name].groupname.domain.io/

5. 接口的版本(API Version)

应该将API的版本号放入URL合适的层次。注意这里的Version既不表示客户端的版本,也不表示服务器中服务对应的版本,而是特指该接口的版本。一般用来处理对接口进行升级的情况。

规范:https://api-[env-name].groupname.domain.io/mobile/[version]/

  • v1和v2的区别,应该表示且仅表示接口的区别。
  • 不要发布无版本号的接口。
  • 使用简单的数字。
  • 服务分组加版本两个变量来共同决定接口的实现逻辑。

6. 路径(/端口,EndPoints)命名

提供以下原则供参考:路径表示API的具体URL,每一个URL唯一的表示一种资源,所以网址中不应该有动词,只应该有名词,而且所用的名词往往与代表的对象名称对应,一般来说是某一种记录的集合,所以API名词当中应该使用复数。

  • URL
    /cards/getCardById/{id}
    => HTTP GET: /cards/{id}
  • 如果某些动作是HTTP动词表达不了的,那么应该将动作当成资源去处理。
    POST /accounts/1/transfer/500/to/2
    => POST /transaction?from=1&to=2&amount=500.00
  • 为了保持简单,只对所有资源使用复数。
    /setting => /settings
    /user => /users
  • 资源之间的层级关系应该表述清楚。
    GET /hotels/1312/homes/ 返回酒店1312的所有房间。
    GET /hotels/1312/homes/1209 返回酒店1312的1209房间。

7. HTTP动词(HTTP Verbs)

以下括号中对应SQL的动词:

  • GET(SELECT):从服务器取出对应的资源。
  • POST(CREATE):新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整的资源,所以应该少用)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):删除资源。

最佳实践

  • 此处的最佳实践是约定好动作处理原则,并且在整个系统(组织或者公司)内保持统一。
  • GET方法不应该涉及状态改变。
  • 很多时候我们还需要收集客户端的信息,但是REST本身是无状态的,所以收集的时候应该独立于REST API的设计原则,独立于设计体系来收集类似于client、cookie、ip和device等信息。

8. 不符合CRUD的情况

一般有如下建议:

  • 使用POST重构行为的action。
  • 增加控制参数(整体进行约定)。
  • 将动作转换成资源。

9. 过滤信息(Filtering)

API应该提供参数,过滤返回结果。因为服务器端某个资源数量可能很多,比如用户的订单数、全国的酒店数等。过滤的语义应该包括对数据集合的过滤、排序、选择和分页等功能。

  • ?limit=10
    返回指定条目的数据
  • ?offset=10
    指定返回记录的开始位置
  • ?pageNumber=2&perSize=100
    指定第几页以及每页的记录数量
  • ?sortBy=time&order=desc
    指定排序属性及顺序
  • ?type=1
    指定筛选条件

最佳实践

  • 实际上,我们在处理API业务时,不可能像数据库查询那么简洁容易,所以参数定义应该更加单一职责、更加严谨。
  • 总是可以在输入参数中设计一个客户端需要的attrList,由使用者来指定需要的属性列表。
  • 参数的设计上应该允许冗余。
  • 可以使用HTTP的定制头:X-Total-Count表示资源总数。

10. 状态码(Status Codes)

  • 这里一般实践是包含两层,一层是中间件(比如网关、nginx、tomcat)返回的HTTP请求本身的状态码,另外还包含服务本身返回的状态码设计。
  • 一个好的可以坚持的原则是,服务本身返回的状态码的前缀应该和网关那一层保持一致,这样可以有最好的层次关系。
    比如40001表示请求参数错误,其中400表示INVALID REQUEST,01表示具体的错误为参数错误。

11. 错误处理(Error Handling)

如果状态码不是正确的返回,就应该返回出错信息,尽量使用详细的错误信息,一个好的实践是出错信息应该包含:

  • userMessage:显示给用户的
  • internalMessage:显示给程序员调试用的
  • code:编码
  • guideline:参考解决指南

12. 返回结果(Response)

  • 按照RESTful架构“宽进严出”的原则,返回应该被严格定义。
  • 全部使用JSON返回结构。
  • 基本约定:

GET /collection:返回资源列表

GET /collection/resource:返回单个对象

POST /collection/resource:返回新生成的对象

PUT /collection/resource:返回完整的更新后的资源对象

PATCH /collection/resource:返回完整的更新后的资源对象

DELETE /collection/resource:返回一个空文档

  • 实际执行过程远比这个复杂,但是我们仍然可以有一些基本原则。

13. 使用HATEOAS构建Hypermedia APIs

HATEOAS(Hypermedia as the Engine of Application State),也即超媒体作为应用状态的引擎。超媒体API很可能是RESTful API设计的未来,它们实际上是一个非常惊人的概念,可以追溯到HTTP和HTML的工作原理。我们可以使用HATEOAS in Spring来构建Hypermedia APIs,而在此之前约定更加重要。

一个好的Hypermedia范例是:https://api.github.com/

{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
  "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "label_search_url": "https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}",
  "notifications_url": "https://api.github.com/notifications",
  "organization_url": "https://api.github.com/orgs/{org}",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_teams_url": "https://api.github.com/orgs/{org}/teams",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

14. 标准请求定义

标准的请求定义当中有很多最佳实践,比如Content-Type等。好的实践是我们尽早用较小的代价将Content-Type、language等加入设计当中,可以避免后续很多问题。

15. 认证(Authentication)

认证的时候取决于API的使用者和生产者之间的关系以及需要保护的程度,目前此处的最佳实践是采用OAuth 2.0当中合适的模式来构建。

16. 文档(Documentation)

使用 Swagger API+JSON 进行文档管理和信息描述,定义一个标准的、语言无关的、供人和计算机理解服务的文档,类似于SOAP当中的WSDL。

  • 需要满足API自动生成同步的在线文档。
  • 可以用于API设计review。
  • 方便测试人员了解API定义。
  • 可以作为客户产品文档的一部分进行发布。
  • 可以通过API Swagger文档生成使用者和生产者的骨架代码。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值