musicode / test

test
14 stars 1 forks source link

RESTful API #3

Open musicode opened 10 years ago

musicode commented 10 years ago

服务即提供资源。

每个资源都有两种 base url

/dogs      操作集合
/dogs/id   操作元素

base url 不要包含动词

先感受一下动词的可怕:

/newDog
/deleteDog
/getRedDogs
/getSittingDogs
...

这种形式的接口很快就会爆炸,数量会越来越多,到最后即使靠文档都很难保证不会遗漏。

使用 HTTP 动作来操作集合和元素

HTTP 动作包括 POST,GET,PUT 和 DELETE,也就是我们常说的 CRUD(Create-Read-Update-Delete)。

比如新增一个小狗:

POST /dogs { name: '旺财', sex: 'male' }

比如删除一个小狗:

DELETE /dogs/1234

名词的单复数

从流行的 API 来看,单复数都有人使用。从用的最多的 GET 接口来看,复数会比较好理解些

注意:保持接口的单复数一致。这样开发者就不用猜某个接口是单数还是复数,提高易用性。

具体名词好于抽象名词

抽象名词会使开发者搞不清接口到底能做些什么,比如:

/items

具体名词的问题是如果太具体,资源的数量会暴增,比如小狗,具体来说会有红狗、黄狗、绿狗等。

一般资源的总数控制在 12 ~ 24 会比较好。如果超出了这个范围,应该做适度的抽象。

资源的关联性

比如小狗和人这两种资源,会产生小狗是某个人的宠物这种关系。

为了简化接口,我们可以使用查询参数,如下:

GET /dogs?owner=123

友好的错误处理

我们无法保证接口总是能正常调用,所以当程序出现异常时,应该给开发者一个友好的提示。

先来看 2 个例子:

Facebook

// HTTP Status Code: 200
{
    type: "OauthException",
    message: "(#803) Some of the aliases you requested do not exist: foo.bar"
}

无法请求结果如何,统一返回 200 状态码,错误信息放到 HTTP 返回对象中。 比如 #803 error,但是没有具体的错误信息,也没说该怎么处理。

Twilio

// HTTP Status Code: 401
{
    status: "401",
    message: "Authenticate",
    code: 20003,
    moreInfo: "http://www.twilio.com/docs/errors/20003"
}

HTTP 状态码保持一致,做得比较好的是提供了一个错误信息的文档链接,开发者可以在那里获得更多信息。

最佳实践

使用 HTTP 状态码

使用基于标准的 HTTP 状态码会比较易懂。

HTTP 状态码一共有 70 多个,但是大多数开发者都记不住那么多,如果你使用了一些不太常用的状态码,开发者就需要经常去搜某个状态码表示什么意思。

因此,大多数 API 只会使用一个小小的子集,比如 Google GData API 用到 10 个状态码,Netflix 用到 9 个,Digg 只用到 8 个。

Google Gdata

200  201  304  400  401  403  404  409  410  500

Netflix

200  201  304  400  401  403  404  412  500

Digg

200  400  401  403  404  410  500  503

你的 API 会用到多少状态码

接口通常只会返回三种结果:

开始使用以下三个状态码:

如果你需要更多状态码,加入就好了,只是要注意状态码不要超过 8 个

如果你觉得把所有结果都归于以上三类会很不舒服,也可以从下面这 5 个中再挑几个:

注意: 如果有可能返回多种状态码,开发者记得要处理每条分支,这很重要!

错误信息要尽可能详细

机器看是这样的:

200 - OK
401 - Unauthorized

看是这样的:

{
    developerMessage: "Verbose, plain language description of the problem for the app developer with hints about how to fix it.",
    userMessage:"Pass this message on to the app user if needed.",
    errorCode: 12345, 
    moreInfo: "http://dev.teachdogrest.com/errors/12345"
}

总之,错误信息要尽可能友好,方便开发者调试,最好在你的错误描述中增加一个链接,提供更多的信息。

版本化

版本化是设计 Web API 最重要的思考之一。

不要公布没有版本号的 API

来看几个例子:

Twilio              /2010-04-01/Accounts/
salesforce.com      /services/data/v20.0/sobjects/Account
Facebook            ?v=1.0

时间戳方案:通过时间戳找出对应版本的 API v版本号方案:我们通常比较喜欢这个方案,只是不喜欢使用 .0 的版本号,因为看起来接口会比较不稳定 v参数方案:版本号是一个可选项,不加默认使用最新版本

版本号思考

按需返回是指开发者需要什么就返回什么,不返回没用的数据。

看几个例子:

Linkin

/people:(id,first-name,last-name,industry)

Facebook

/joe.smith/friends?fields=id,name,picture

Google

?fields=title,media:group(media:thumbnail)

Google 和 Facebook 比较类似,都有一个叫做 fields 的可选参数,可以指定你需要的字段。

我们一般会用 , 分割字段名。

分页

Facebook 和 LinkedIn 的方式是一样的,即 offset 等价于 start,limit 等价于 count。

为了获取 50 - 75 的记录 (record),三种方式使用如下:

我们推荐使用 offset 和 limit。它更常用,并且能被数据库很好的理解,同时对开发者来说也很简单。

/dogs?offset=50&limit=25

元数据

我们也建议给翻页数据加上元数据,如记录的总数。

默认参数

我的默认参数一般是 offset=0&limit=25

默认参数取决于你的数据量。如果数据很多,你也许会限制 limit 小于 10;如果数据很少,你就可以选择一个较大的 limit。

资源无关的接口

比如语言翻译,单位换算等都是资源无关的,不涉及读写数据库。

使用动词而不是名词

举个例子,把 100 欧元转换成人民币:

/convert?from=EUR&to=CNY&amount=100

支持多种格式

我们建议你支持的格式大于一种 —— 以一种格式输出,但可以允许多种格式的输入。

来看几个例子。

Google Data

?alt=json

Foursquare

/venue.json

Digg

Accept: application/json
?type=json

如果设置了 type 参数,会覆盖 Accept。

我们推荐 Foursquare 的方式。

dogs.json        // 获得 json 格式的集合
/dogs/1234.json  // 获得 json 格式的元素

默认格式

必须是 json 好么。

字段名

返回的数据,如何命名它的属性呢?

先看例子:

Twitter

"created_at": "Thu Nov 03 05:19;38 +0000 2011"

Bing

"DateTime": "2011-10-29T09:35:00Z"

Foursquare

"createdAt": 1320296464

每一种都有不同的编码习惯。

我们认为 Foursquare 的方式是最好的,因为返回的数据要在前端使用,而驼峰形式最符合 js 的编码习惯。

搜索

搜索某种特定资源的 api 相对比较简单,如 dogs/?q=red,但跨资源的搜索则需要不同的设计。

如果你需要跨资源的全局搜索,我们建议模仿 Google 的方式:

全局搜索

/search?q=fluffy+fur

这里,search 是个动词,?q 表示 query。

限定搜索

为了限定搜索的范围,你可以在搜索前面加上范围。

举个例子,搜索 ID 是 5678 的 owner 拥有的狗

/owners/5678/dogs?q=fluffy+fur