Restful API 设计原则

1. post 和 put 的区别

假如是客户端负责决定新资源采用什么URI,那就用PUT,假如是服务器负责新资源采用什么URI,那就用POST。
PUT请求来新建或修改资源 POST请求来新建从属资源。


表1-- PUT和POST动作
URI 向新资源发PUT请求 向已有资源发PUT请求 POST
/weblogs N/A(资源已存在) 无效果 创建一个新博客
/weblogs/myweblog 创建该博客 修改该博客的设置 往博客里添加一篇文章
/weblogs/myweblog/entries/1 N/A(你无法知道这个URI) 编辑该博客的文章 为该博客文章添加评论

2. PUT和DELETE操作具有幂等性

幂等性:可以简单的理解为一次操作和多次操作的结果一样的

3. restful api设计原则

参考来源: http://www.cnblogs.com/moonz-wu/p/4211626.html

URL段 > 添加额外URL段 > POST返回 > 过滤器

要点提示

  • 集合可以表达数据库表,资源可以表达数据库中的某条记录。事实上,API应该尽可能通过抽象来分离数据与业务逻辑。
  • 至少知道4个半HTTP方法 GET/POST/PUT/PATCH/DELETE。
  • API版本化,放在URL段里是不错的选择。
  • 分析API的使用次数和将要抛弃的API的通知(有第三方的应用关注此条)。
  • API根地址放到子域名,API基于HTTPS发布。
  • 通过添加一个额外的URL段就可以实现更多的交互能力,定义关键字也是不错的主意。
  • 能用端点(如GET /zoo/ZID/animals)表示的,不要用过滤器(如GET /animals?zoo_id=ZID)。
  • 负载均衡配合状态码。
  • 客户端创建资源时,它往往不知道新建资源ID(也许还有其他的属性,如创建和修改的时间戳等)。把属性作为POST请求的响应结果来返回。
  • 超媒体API通过请求API的根来获得一个URL的列表,这个列表里面的每一个URL都指向一个集合,并且提供了客户端可以理解的信息来描述每一个集合。
  • 创建一个控制台来让开发者可以立即体验一下API的功能。(时间充裕的情况下考虑)

术语

  • 资源:一个对象的单独实例,如一只动物
  • 集合:一群同种对象,如动物
  • 客户端:可以创建HTTP请求的客户端应用程序
  • 服务器:一个HTTP服务器或者应用程序,客户端可以跨网络访问它
  • 端点:这个API在服务器上的URL用于表达一个资源或者一个集合
  • 幂等:无边际效应,多次操作得到相同的结果
  • URL段:在URL里面已斜杠分隔的内容

正文

  • 数据设计与抽象

通常情况下,集合可以表达数据库表,资源可以表达数据库中的某条记录。事实上,你的API应该尽可能通过抽象来分离数据与业务逻辑。这点非常重要,只有这样做,才能满足复杂业务的第三方开发者,否则他们是不会想用你的API的。

当然,你的有些服务是不应该通过API暴露出去的,例如:很多API不允许第三方来创建用户的。

  • http方法

HTTP方法你至少需要知道四个半。我之所以说“半个”的意思就是PATCH这个方法非常类似于PUT,并且它们俩也常常被开发者绑定到同一个API上。

- GET (获取):从服务器上获取一个具体的资源或者一个资源列表。
- POST (创建): 在服务器上创建一个新的资源。
- PUT (更新):以整体的方式更新服务器上的一个资源。
- PATCH (更新):只更新服务器上一个资源的一个属性。
- DELETE (删除):删除服务器上的一个资源。

还有两个不常用的HTTP方法:
- HEAD : 获取API方法的头部信息,如数据的哈希值或最后的更新时间。
- OPTIONS:获取客户端能对资源做什么操作的信息。

好的RESTful API只允许第三方调用者使用上述四个半的HTTP方法进行数据交互,并且在URL段里面不出现任何其他的方法关键字。

一般来说,GET请求可以被浏览器缓存(主要依赖请求头部),对于post的第二次提交而言也是如此。HEAD请求是基于GET的一个无响应体的请求,也是可以被缓存的。

  • 版本化

即时你规划和准备的很充分,你核心的应用总会发生变化,数据关系也会变化,资源上的属性也会被增加或删除。只要你的项目还在,并且有大量用户在用,这种情况总是会有的。

请谨记一点,API是服务器与客户端之间的公共契约。如果你修改了服务器上的API,更改后无法向后兼容,那么你就打破了这个契约,客户端又会要求你重新支持它。为避免此类事件,你既要确保应用程序的迭代,又要让客户端满意。那么你必须在引入新版本API的同时,保持旧版本API仍然可用。

注:如果你只是简单的增加一个新特性到API上,如资源上的一个新属性或者增加一个新的端点,你不需要增加API的版本。因为这些并不会造成向后兼容性的问题,你只需要修改文档即可。

随着时间的推移,你可能声明不再支持某些旧版本的API。申明不支持一个特性并不意味着关闭或者破坏它。而是告诉客户端旧版本的API将在某个特定的时间被删除,并且建议他们使用新版本的API。

一个好的RESTful API会在URL中包含版本信息。常见的方案是在请求头里面携带版本信息。但是跟很多不同的第三方开发者一起工作后,我可以很明确的告诉你,在请求头里面包含版本信息远没有放在URL里面来的容易。

  • 分析

API分析就是持续跟踪正在使用的API的版本和端点信息,这像每次请求都往数据库增加一个整数那样简单。有很多的迹象表明API跟踪分析优势所在,例如,对使用最广泛的API来说效率是最重要的。

第三方开发者通常会关注API构建目的,其中最重要的一个目的是你决定什么时候不再支持某个版本。但是你要告知开发者使用的那些即将被移除的API特性。当你准备删除旧的API之前可以通过这种方式提醒他们升级。

当然,对于第三方开发者的通知,我们可以限定某种条件下会被自动触发,例如每当在一个过时的特性上发生10000次请求时,就发邮件通知开发者。

  • API根部URL

无论你信不信,API的根地址很重要。当一个开发者接手了一个旧项目(如进行代码review)。而这个项目正在使用你的API,同时开发者还想构建一个新的特性,但他们完全不知道你的服务。幸运的是他们知道客户端对外调用的那些URL列表。让你的API根入口点保持尽可能的简单是很重要的,因为开发者很可能一看到那些冗长而又复杂的URL就转身而走。

这里有两个常见的URL根例子:

https://example.org/api/v1/*
https://api.example.com/v1/*

如果应用很复杂,将API放到子域名下通常是一个不错的选择。这种做法可以保持某些规模化上的灵活性。

如果API不多,或是想让应用安装更简单(使用相同的框架来支持站点和API),将API放到根域名下也是ok的。

让API根拥有一些内容也是不错的,Github的API根就是一个典型的例子。

同样请注意HTTPS前缀,一个好的RESTful API总是基于HTTPS来发布的。

  • 端点

一个端点就是指向特定资源或资源集合的URL。

如果你正在构建一个虚构的API来展现几个不同的动物园,每一个动物园又包含很多动物,员工和每个动物的物种,你可能会有如下的端点信息:

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/animal_types
https://api.example.com/v1/employees

针对每一个端点,你可能想列出所有可行的HTTP方法和端点的组合。如下所示,请注意我把HTTP方法都放在虚构API的前面,正如将同样的注解放在每一个HTTP请求头里一样。

GET /zoos: List all Zoos (ID and Name, not too much detail)
POST /zoos: Create a new Zoo
GET /zoos/ZID: Retrieve an entire Zoo object
PUT /zoos/ZID: Update a Zoo (entire object)
PATCH /zoos/ZID: Update a Zoo (partial object)
DELETE /zoos/ZID: Delete a Zoo
GET /zoos/ZID/animals: Retrieve a listing of Animals (ID and Name).
GET /animals: List all Animals (ID and Name).
POST /animals: Create a new Animal
GET /animals/AID: Retrieve an Animal object
PUT /animals/AID: Update an Animal (entire object)
PATCH /animals/AID: Update an Animal (partial object)
GET /animal_types: Retrieve a listing (ID and Name) of all Animal Types
GET /animal_types/ATID: Retrieve an entire Animal Type object
GET /employees: Retrieve an entire list of Employees
GET /employees/EID: Retreive a specific Employee
GET /zoos/ZID/employees: Retrieve a listing of Employees (ID and Name) who work at this Zoo
POST /employees: Create a new Employee
POST /zoos/ZID/employees: Hire an Employee at a specific Zoo
DELETE /zoos/ZID/employees/EID: Fire an Employee from a specific Zoo

在上面的列表里,ZID表示动物园的ID, AID表示动物的ID,EID表示雇员的ID,还有ATID表示物种的ID。

让文档API有一个关键字是个不错的主意。

为简洁起见,我已经省略了所有API共有的URL前缀。作为沟通方式这没什么问题,但是如果你真要写到API文档中,那就必须包含完整的路径(如,GET http://api.example.com/v1/animal_type/ATID)。

展示数据之间的关系,特别是雇员与动物园之间多对多关系。通过添加一个额外的URL段就可以实现更多的交互能力。当然没有HTTP方法能表示正在解雇一个人,但可以用DELETE一个动物园里的雇员来表示。

  • 过滤器

当客户端创建一个请求来获取一个对象列表,必须要返回给一个符合查询条件的所有对象的列表。这个列表可能很大。但你不能随意给返回数据的数量做限制,因为限制会导致第三方开发者不知道发生了什么。如果他们请求一个确切的集合并且要遍历结果,然而他们发现只拿到了100条数据。接下来他们就不得不去查找这个限制条件的出处。到底是ORM的bug导致的,还是因为网络截断了大数据包?

尽可能减少会影响到第三方开发者的限制,这点很重要,但可以让客户端自己对结果做一些具体的过滤或限制。这么做最重要的一个原因是可以最小化网络传输,并让客户端尽快的得到查询结果。

其次是客户端可能比较懒,如果这时服务器能对结果做一些过滤或分页,对大家都是好事。另外一个不那么重要的原因是(从客户端角度来说),对服务器来说响应请求的负载越少越好。

过滤器是最有效的方式去处理那些获取资源集合的请求。所以只要出现GET的请求,就应该通过URL来过滤信息。以下有一些过滤器的例子,可能是你想要填加到API中的:

?limit=10: 减少返回给客户端的结果数量(用于分页)
?offset=10: 发送一堆信息给客户端(用于分页)
?animal_type_id=1: 使用条件匹配来过滤记录
?sortby=name&order=asc: 对结果按特定属性进行排序

有些过滤器可能会与端点URL的效果重复。例如我之前提到的GET /zoo/ZID/animals。

它也同样可以通过GET /animals?zoo_id=ZID来实现。
独立的端点会让客户端更好过一些,因为他们的需求往往超出你的预期。本文中提到这种冗余差异可能对第三方开发者并不可见。

无论怎么样,当准备过滤或排序数据时,必须明确的将那些可以过滤或排序的数据字段放到白名单中,因为我们不想将任何的数据库错误发送给客户端。

  • 状态码

RESTful API使用HTTP的状态码至关重要,因为它们是HTTP的标准。很多的网络设备都可以识别这些状态码,例如某台服务器已经发送了很多50x错误回来,负载均衡器可能会通过配置来避免继续发送请求到这台服务器上。

  • 预期的返回文档

使用不同的HTTP方法向服务器请求时,客户端需要在返回结果里拿到一系列的信息。下面的列表是非常经典的RESTful API定义:

GET /collection: 返回一系列资源对象
GET /collection/resource: 返回单独的资源对象
POST /collection: 返回新创建的资源对象
PUT /collection/resource: 返回完整的资源对象
PATCH /collection/resource: 返回完整的资源对象
DELETE /collection/resource: 返回一个空文档

当一个客户端创建一个资源时,它往往不知道新建资源ID(也许还有其他的属性,如创建和修改的时间戳等)。这些属性将在随后的请求中返回,并且作为刚才POST请求的一个响应结果。

  • 认证
    服务器在大多数情况下是想确切的知道谁创建了什么请求。当然,有些API是提供给公共用户(匿名用户)的,但是大部分时间里也是代表某人个用户。

OAuth2.0提供了一个非常好的方式去处理这种问题。在每个请求里,可以明确知道哪个客户端创建了请求,哪个用户提交了请求,并且提供了一种标准的访问过期机制或允许用户从客户端注销,所有这些都不需要第三方的客户端知道用户的登陆认证信息。

还有OAuth1.0和xAuth同样适用这种场景。无论选择哪个方法,请确保它为多种不同语言/平台上的库提供了一些通用的并且设计良好文档,因为你的用户可能会使用这些语言和平台来编写客户端。

  • Content Type(内容类型)

目前,大多数“精彩”的API都为RESTful接口提供JSON数据。诸如Facebook,Twitter,Github等等你所知的。XML曾经也火过一把(通常在一个大企业级环境下)。这要感谢SOAP,不过它已经挂了,并且我们也没看到太多的API把HTML作为结果返回给客户端(除非你在构建一个爬虫程序)。

只要你返回给他们有效的数据格式,开发者就可以使用流行的语言和框架进行解析。如果你正在构建一个通用的响应对象,通过使用一个不同的序列化器,你也可以很容易的提供之前所提到的那些数据格式(不包括SOAP)。而你所要做的就是把使用方式放在响应数据的接收头里面。

有些API的创建者会推荐把.json, .xml, .html等文件的扩展名放在URL里面来指示返回内容类型,但我个人并不习惯这么做。我依然喜欢通过接收头来指示返回内容类型(这也是HTTP标准的一部分),并且我觉得这么做也比较友好。

  • 超媒体API

超媒体API很可能就是RESTful API设计的将来。超媒体是一个非常棒的概念,它回归到了HTTP和HTML如何运作的“本质”。

在非超媒体RESTful API的情景中,URL端点是服务器与客户端契约的一部分。这些端点必须让客户端事先知道,并且修改它们也意味着客户端可能再也无法与服务器通信了。你可以先假定这是一个限制。

时至今日,英特网上的API客户端已不仅仅只有那些创建HTTP请求的用户代理了。大多数HTTP请求是由人们通过浏览器产生的。人们不会被哪些预先定义好的RESTful API端点URL所束缚。是什么让人们变的如此与众不同?因为人们可以阅读内容,可以点击他们感兴趣的链接,并浏览一下网站,然后跳到他们关注的内容那里。即使一个URL改变了,人们也不会受到影响(除非他们事先给某个页面做了书签,这时他们回到主页并发现原来有一条新的路径可以去往之前的页面)。

超媒体API概念的运作跟人们的行为类似。通过请求API的根来获得一个URL的列表,这个列表里面的每一个URL都指向一个集合,并且提供了客户端可以理解的信息来描述每一个集合。是否为每一个资源提供ID并不重要(或者不是必须的),只要提供URL即可。

一个超媒体API一旦具有了客户端,那么它就可以爬行链接并收集信息,而URL总是在响应中被更新,并且不需要如契约的一部分那样事先被知晓。如果一个URL曾经被缓存过,并且在随后的请求中返回404错误,那么客户端可以很简单的回退到根URL并重新发现内容。

在获取集合中的一个资源列表时会返回一个属性,这个属性包含了各个资源的完整URL。当实施一个POST/PATCH/PUT请求后,响应可以被一个3xx的状态码重定向到完整的资源上。

JSON不仅告诉了我们需要定义哪些属性作为URL,也告诉了我们如何将URL与当前文档关联的语义。正如你猜的那样,HTML就提供了这样的信息。我们很欣慰看到我们的API走过了完整的周期,并回到了处理HTML上来。想一下我们与CSS一起前行了多远,有一天我们可能再次看到它变成了一个通用实践让API和网站可以去使用相同的URL和内容。

  • 文档

老实说,即使你不能完全遵循指南中的条款,你的API也不会那么糟糕。但是,如果你不为API准备文档的话,没有人会知道怎么使用它,那它真的会成为一个糟糕的API。

让你的文档对那些未经认证的开发者也可用,不要使用文档自动化生成器,即便你用了,你也要保证自己审阅过并让它具有更好的版式。
不要截断示例中请求与响应的内容,要展示完整的东西。并在文档中使用高亮语法。
文档化每一个端点所预期的响应代码和可能的错误消息,和在什么情况下会产生这些的错误消息。

如果时间充足,那就创建一个控制台来让开发者可以立即体验一下API的功能。创建一个控制台并没有想象中那么难,并且开发者们(内部或者第三方)也会因此而欣赏你。

另外确保你的文档能够被打印。CSS是个强大的工具可以帮助到你。而且在打印的时候也不用太担心边侧栏的问题。即便没有人会打印到纸上,你也会惊奇的发现很多开发者愿意转化成PDF格式进行离线阅读。

  • 勘误:原始的HTTP封包

因为上述都是基于HTTP协议,所以我将展示给你一个解析后的HTTP包。我很惊讶的发现,大部分人不知道这些东西。当客户端发送一个请求到服务器时,他们会提供一个键值对集,先是一个头,紧跟着是两个回车换行符,然后才是请求体。所有这些都是在一个封包里被发送。

服务器响应也是同样的键值对集,带两个回车换行符,然后是响应体。HTTP就是一个请求/响应协议;它不支持“推送”模式(服务器直接发送数据给客户端),除非你采用其他协议,如Websocket。

当你设计API时,你应该能够使用工具去查看原始的HTTP包。Wireshark是个不错的选择。同时,你也该采用一个框架/web服务器,使你能够在必要时修改某些字段的值。

Example HTTP Request

POST /v1/animal HTTP/1.1
Host: api.example.org
Accept: application/json
Content-Type: application/json
Content-Length: 24
 
{
  "name": "Gir",
  "animal_type": 12
}

Example HTTP Response

HTTP/1.1 200 OK
Date: Wed, 18 Dec 2013 06:08:22 GMT
Content-Type: application/json
Access-Control-Max-Age: 1728000
Cache-Control: no-cache
 
{
  "id": 12,
  "created": 1386363036,
  "modified": 1386363036,
  "name": "Gir",
  "animal_type": 12
}
原文地址:https://www.cnblogs.com/reamd/p/5208907.html