好RESTful API的设计原则

640?wx_fmt=jpeg

作出一个好的API设计很难。API表达的是你的数据和你的数据使用者之间的契约。打破这个契约将会招致不少愤怒的邮件,和一大堆伤心的用户-由于他们手机上的App不工做了。而文档化只能达到一半的效果,而且也很难找到一个愿意写文档的程序员。
html

你所能作的最重要一件事来提升服务的价值就是建立一个API。由于随着其余服务的成长,有这样一个API会使你的服务或者核心应用将有机会变成一个平台。环顾一下现有的这些大公司:Facebook,Twitter,Google, Github,Amazon,Netflix等。若是当时他们没有经过API来开放数据的话,也不可能成长到现在的规模。事实上,整个行业存在的惟一目的就是消费所谓平台上的数据。程序员

你的API越容易使用,那么就会有越多的人去用它。web

本文提到的这些原则,若是你的API能严格按照这些原则来设计,使用者就能够知道它接下来要作什么,而且能减小大量没必要要的疑惑或者是愤怒的邮件。我已经把全部内容都整理到不一样的主题里了,你无需按顺序去阅读它。数据库

定义

这里有一些很是重要的术语,我将在本文里面一直用到它们:json

  • 资源:一个对象的单独实例,如一只动物api

  • 集合:一群同种对象,如动物浏览器

  • HTTP:跨网络的通讯协议缓存

  • 客户端:能够建立HTTP请求的客户端应用程序服务器

  • 第三方开发者:这个开发者不属于你的项目可是有想使用你的数据网络

  • 服务器:一个HTTP服务器或者应用程序,客户端能够跨网络访问它

  • 端点:这个API在服务器上的URL用于表达一个资源或者一个集合

  • 幂等:无边际效应,屡次操做获得相同的结果

  • URL段:在URL里面已斜杠分隔的内容

数据设计与抽象

规划好你的API的外观要先于开发它实际的功能。首先你要知道数据该如何设计和核心服务/应用程序会如何工做。若是你纯粹新开发一个API,这样会比较容易一些。但若是你是往已有的项目中增长API,你可能须要提供更多的抽象。

有时候一个集合能够表达一个数据库表,而一个资源能够表达成里面的一行记录,可是这并非常态。事实上,你的API应该尽量经过抽象来分离数据与业务逻辑。这点很是重要,只有这样作你才不会打击到那些拥有复杂业务的第三方开发者,不然他们是不会使用你的API的。

固然你的服务可能不少部分是不该该经过API暴露出去的。比较常见的例子就是不少API是不容许第三方来建立用户的。

动词

显然你了解GET和POST请求。当你用浏览器去访问不一样页面的时候,这两个是最多见的请求。POST术语如此流行以致于开始侵扰通俗用语。即便是那些不知道互联网如何工做的人们也能“post”一些东西到朋友的Facebook墙上。

这里至少有四个半很是重要的HTTP动词须要你知道。我之因此说“半个”的意思是PATCH这个动词很是相似于PUT,而且它们俩也经常被开发者绑定到同一个API上。

  • GET (选择):从服务器上获取一个具体的资源或者一个资源列表。

  • POST (建立): 在服务器上建立一个新的资源。

  • PUT (更新):以总体的方式更新服务器上的一个资源。

  • PATCH (更新):只更新服务器上一个资源的一个属性。

  • DELETE (删除):删除服务器上的一个资源。

还有两个不经常使用的HTTP动词:

  • HEAD : 获取一个资源的元数据,如数据的哈希值或最后的更新时间。

  • 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的根地址很重要。当一个开发者接手了一个旧项目(如进行代码考古时)。而这个项目正在使用你的API,同时开发者还想构建一个新的特性,但他们彻底不知道你的服务。幸运的是他们知道客户端对外调用的那些URL列表。让你的API根入口点保持尽量的简单是很重要的,由于开发者极可能一看到那些冗长而又复杂的URL就转身而走。

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

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

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

若是你的应用很庞大或者你预期它将会变的很庞大,那么将API放到子域下一般是一个好选择。这种作法能够保持某些规模化上的灵活性。

但若是你以为你的API不会变的很庞大,或是你只是想让应用安装更简单些(如你想用相同的框架来支持站点和API),将你的API放到根域名下也是能够的。

让API根拥有一些内容一般也是个好主意。Github的API根就是一个典型的例子。从我的角度来讲我是一个经过根URL发布信息的粉丝,这对不少人来讲是有用的,例如如何获取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请求头里同样。(下面的URL就不翻译了,我以为没啥必要翻^_^)

  • 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共有的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的标准。不少的网络设备均可以识别这些状态码,例如负载均衡器可能会经过配置来避免发送请求到一台web服务器,若是这台服务器已经发送了不少的50x错误回来。这里有大量的HTTP状态码能够选择,可是下面的列表只给出了一些重要的代码做为一个参考:

  • 200  OK – [GET]

    客户端向服务器请求数据,服务器成功找到它们

  • 201  CREATED – [POST/PUT/PATCH]  

    客户端向服务器提供数据,服务器根据要求建立了一个资源

  • 204  NO CONTENT – [DELETE]  

    客户端要求服务器删除一个资源,服务器删除成功

  • 400  INVALID REQUEST – [POST/PUT/PATCH]  

    客户端向服务器提供了不正确的数据,服务器什么也没作

  • 404  NOT FOUND – [*]  

    客户端引用了一个不存在的资源或集合,服务器什么也没作

  • 500  INTERNAL SERVER ERROR – [*]  

    服务器发生内部错误,客户端没法得知结果,即使请求已经处理成功

状态码范围

1xx范围的状态码是保留给底层HTTP功能使用的,而且估计在你的职业生涯里面也用不着手动发送这样一个状态码出来。

2xx范围的状态码是保留给成功消息使用的,你尽量的确保服务器总发送这些状态码给用户。

3xx范围的状态码是保留给重定向用的。大多数的API不会太常使用这类状态码,可是在新的超媒体样式的API中会使用更多一些。

4xx范围的状态码是保留给客户端错误用的。例如,客户端提供了一些错误的数据或请求了不存在的内容。这些请求应该是幂等的,不会改变任何服务器的状态。

5xx范围的状态码是保留给服务器端错误用的。这些错误经常是从底层的函数抛出来的,而且开发人员也一般无法处理。发送这类状态码的目的是确保客户端能获得一些响应。收到5xx响应后,客户端没办法知道服务器端的状态,因此这类状态码是要尽量的避免。

预期的返回文档

当使用不一样的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一样适用这样的场景。不管你选择哪一个方法,请确保它为多种不一样语言/平台上的库提供了一些通用的而且设计良好文档,由于你的用户可能会使用这些语言和平台来编写客户端。

内容类型

目前,大多数“精彩”的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就是一个请求/响应协议;它不支持“推送”模式(服务器直接发送数据给客户端),除非你采用其余协议,如Websockets。

当你设计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
}

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

相关文章
相关标签/搜索