Restful API设计指南

这篇博文讲前后端交互的Restful API设计考虑。API一定是基于HTTP协议并尽量靠拢Restful规范设计的。如果不了解HTTP协议,可以参考我的博文HTTP必知必会。关于Restful是怎么回事,可以考虑先参考阮一峰的博文理解RESTful架构;嫌麻烦的可以略过直接阅读我的这篇博文。

基于资源的API设计

所谓资源,是一种对事物的抽象。一个人、一头牛、一张电影票、一句话、一个想法都可以抽象为资源。

资源的URL表示

URL是资源的定位,是一种映射关系,即一个URL一定定位到最多不超过一个的资源。根据URL布局的特点,分为三种类型的资源:

  1. 集合资源

    对于形如/users的资源称为集合资源,它表示一群用户。

  2. 个体资源

    对于形如/users/u123的资源称为个体资源,它表示一个用户,它是通过一个唯一标识的id(即u123)来确定这个用户。

  3. 子集合资源

    对于形如/users/u123/messages的资源称为子集合资源,它表示某个用户的一群消息,这个用户是通过唯一标识的id(即u123)来确定的。

补充:关于id

资源的id对于同种类型的资源来说一定是唯一的。也就是说,确定一个个体资源只需要通过下面的方式即可

/messages/m123

而不必

/users/u123/messages/m123

资源的表述

资源本身是一种抽象,它必须要转化为一种机器可读的形式才能进行传输和处理。表述方式的例子如表单形式、XML、JSON等。可以通过HTTP头Content-Type来指定资源的表述形式,常见的例子:

  • 纯文本:text/plain
  • HTML文档:text/html
  • 表单格式:application/x-www-form-urlencoded
  • XML文档:text/xml
  • JSON文档:application/json

这里的设计中只考虑一种表述形式,即JSON。希望这一点不会给API的调用带来困扰。

资源的操作

基本的增删改查

对于的基本的增删改查操作,除了定位到资源以外,只需要附加一个HTTP方法够了。根据Restful规范,方法对于集合的意义一般是:

  1. POST:创建
  2. GET:查询
  3. PUT:完整更新
  4. PATCH:部分更新
  5. DELETE:删除

这些方法适用于不同的URL类型,总结如下:

  1. GET+(子)集合资源

    • GET /users 返回用户集合
    • GET /users/u123/messages 返回指定用户的消息集合
  2. POST+(子)集合资源

    • POST /users 创建一个新用户
    • POST /users/u123/messages 为指定用户创建一个新消息
  3. GET+个体资源

    • GET /users/u123 返回指定用户
  4. PUT/PATCH+个体资源

    • PUT /users/u123 更新指定用户(完整)
    • PATCH /users/u123 更新指定用户(部分)
  5. DELETE+个体资源

    • DELETE /users/u123 删除指定用户

其他的动作

当对资源的操作跳出了基本的增删改查范畴,就在资源的URL表示之后增加动作的名称即可。这可以通过两个典型的例子来阐述。

例子一:用户登录

这个属于对集合资源进行的操作。其接口表示可设定为:

  • POST /users/login

只需在集合资源的URL表示(即/users)之后附加动作名(即/login)即可。在这种情况下,POST是典型方法。

例子二:书籍标星和去标

这是一对接口,其中一个是对一本书进行标星操作,另外一个是对其进行取消标星操作。这两个接口都属于对个体资源的操作。其接口表示可分别设定为:

  • POST /books/b123/star
  • DELETE /books/b123/star

首先,需要在个体资源的URL表示(即/books/b123)之后附加动作名(即/star)。其次,这里用到对立的两个方法POST和DELETE。

例子三:查询的count

一个查询接口会返回一个资源数组,但有时我们需要知道满足某个查询条件的资源总数。这个在集合资源的URL之后附加动作count即可。例如:

  • GET /books/count 返回书籍资源总数
  • GET /users/count?age=18 返回年龄18岁的用户资源总数

注意,这时分页控制无效。

操作的参数

在参数设计里,一个基本的设计原则是要保证简洁,避免嵌套过深。参数的格式可以根据实际情况限定为几种或不限格式。不过,至少保证表单格式和JSON格式这两种格式的支持。其中JSON格式支持任意的嵌套;表单格式对嵌套的支持不理想。

参数分为一般参数控制参数。从形式上看,控制参数以下划线开头。这么设计是为了与一般参数进行区分。一般参数例如name、age等,控制参数例如_sort等。

通用参数总结

筛选

这里的筛选只支持完全匹配,适用于GET集合资源的接口。筛选的方式是简单直接的,直接是字段名=值的形式即可。例如下面的接口返回年龄为18的所有男性用户:

GET /users?age=18&gender=male

排序(_sort)

排序参数_sort是逗号分隔的字段列表,以+/-表示升/降序。

  • age: age升序
  • +age: age升序,同上
  • -age: age降序
  • -age,name: 先以age降序,age相等的以name升序

分页(_range)

分页参数_range的构建也是直接的,以下划线分隔它的起始和结束资源的序号。资源计数从1开始,这点要尤其注意。

  • 10_100 : 返回第10个到第100个资源集合
  • _100 : 返回第1个到第100个集合资源
  • 1_ : 返回第1个到最后一个集合资源
  • _ : 返回所有的集合资源

内嵌(_embed)

说清楚内嵌的用法,先定义一个资源Book:

{
    id: 'b123',
    title: 'xxx',
    author: {
        id: 'u123',
        name: 'yyy'
    }
}

通过GET接口获取的资源表述中,对于内嵌的资源默认只会返回其id,而不会返回其完整表述。例如调用GET /books/b123返回的数据是:

{
    id: 'b123',
    title: 'xxx',
    author: {
        id: 'u123'
    }
}

这个时候,想要获取完整的author资源,需要再调用GET /users/u123

然而,通过_embed参数,可以一步就直接返回包含完整User资源的author链接,就像我们一开始定义的那样。调用GET /users/u123?\_embed=author,返回以下的结果:

{
    id: 'b123',
    title: 'xxx',
    author: {
        id: 'u123',
        name: 'yyy'
    }
}

多个内嵌资源用逗号分隔即可。

操作的返回值

操作的返回内容,它的表述格式是可选的。只限定为JSON格式是一种可选的简洁方案。

基于操作类型的参数和返回值模式

不同的操作类型,其参数和返回值模式是有一定规律性的。现总结如下:

GET+(子)集合资源

这个操作请求的是一个资源集合,返回的是一个JSON数组。

其参数能包括:筛选、排序、分页、内嵌。

GET+个体资源

这个操作请求的是一个个体资源,返回的是一个JSON对象。

其参数只包括:内嵌。

POST+(子)集合资源

这个操作请求创建一个新资源,返回的是一个JSON对象,表述新创建的资源。

其参数就是该资源的表述。

PUT/PATCH+个体资源

这个操作请求更新一个旧资源,返回的是一个JSON对象,表述更新后的资源。

其参数就是该资源的表述。

DELETE+个体资源

这个操作请求删除一个资源,无返回值,无参数。

其他操作

其他操作视情况而定,一般无返回值,无参数。

操作的错误

任何操作都会发生出错情况。出错的原因有很多种,有源于客户端的,也有源于服务器的。有预料中的,也有预料之外的。对于出错情况,服务器要向客户端发送一个状态码及附加一个JSON格式的错误消息(这里像返回值的考虑一样也只考虑JSON格式了)。一个例子就是:

401 Unauthorized
{
    message: "没登录"
}

这里的message字段作为错误状态的描述,作为解释出错的原因。如果表述的合适,可以作为最终用户的提示信息,直接在弹框中显示。

这里面没有考虑加入更多的字段了,比如一个code字段来更细致的区别错误的类型。这种考虑是基于客户端对于接口调用出错无能为力,一般能做的就两种:

  1. 显示错误信息;如果是幂等的接口,(可选地)重传。
  2. 如果是授权相关错误,跳转到登录逻辑并可选地重传。

补充

验证信息

关于用户的授权在HTTP和Restful中的方案,市面上有很多种。这里介绍的是一种Token机制。很简单,用户通过某个接口(例如/users/login)获取授权的Token,然后每次调用接口时将这个Token附加到Header Access-Token中。

_token伪id

用户的Token不仅用于授权,也用于标识用户的身份。当需要传递用户id的时候,往往发现用户的id信息可以从Token中获取。举个例子,id为u123的用户调用如下接口时:

GET /users/u123
Access-Token: 包含u123的授权信息

其中Token信息可以从相应的Header中获取,所以此时明确标明id u123没有必要。此时可以用一个占位符()占位一下即可。

GET /users/_token
Access-Token: 包含u123的授权信息
posted @ 2015-11-09 19:34  starok  阅读(400)  评论(0)    收藏  举报