注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

逍遥子 曰:

得失失得 何必患得患失 舍得得舍 不妨不舍不得

 
 
 

日志

 
 

[译]优秀RESTful API的设计原则(一)  

2017-01-10 19:09:46|  分类: 架构设计 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

原文来自:https://codeplanet.io/principles-good-restful-api-design/

 

设计优秀的REST风格API非常困难!API是服务提供方和使用方之间的契约,打破该契约将会给服务端开发人员招来非常大的麻烦,这些麻烦来自于使用API的开发人员,因为对API的改动会导致他们的移动app无法工作。一个好的文档对于解决这些事情能起到事半功倍的作用,但是绝对多数程序员都不喜欢写文档。

如果想让服务端的价值更好的体现出来,就要好好设计API。通过这些API,你的服务/核心程序将有可能成为其他项目所依赖的平台;目前的大公司:FacebookTwitterGoogleGithubAmazonNetflix等等无不依赖API,如果没有精心设计的API对外开发它们的数据,这些公司也就不会像今天这么强大。事实上,整个产业存在的目的就是消费上述平台提供的数据。

你提供的API越易用,就会有越多人愿意使用它。

如果在设计API时能遵循本文档提出的原则,那么你设计出的API就能让调用方更容易理解和使用,也能大幅减少调用方对你的抱怨;我已将文档内容按主题分别进行详细描述,读者可选择自己感兴趣的主题,而无需顺序阅读。

本文档中所使用的术语及其含义如下:

l   Resource(资源):单个实例对象,例如animal(一只动物);[逍遥子笔记:Roy Thomas FieldingREST风格的提出者)如此解释资源的含义:一个资源可以是一份文档、一张图片、一个与时间相关的服务(例如:洛杉矶今天的天气)等等,资源是实体的概念性映射,而不是实体本身;个人理解:一张图片是一个资源,但是在我本机中有一个的名字叫做“jason.txt”的、实实在在的文件实体,就不能称作资源,它是资源实体,简单而言:“资源”与“资源实体”之间犹如类和对象的关系。]

l  Collection(集合):集合是一组同类的实例对象,例如animals(一群动物)。

l  HTTP:一种在网络上传输的通信协议;

l  Consumer(使用方、用户):能够发送http请求的客户端程序;[逍遥子笔记:这里Consumer实际是指API的调用方,如果直译成消费者更让人困惑]

l  Third Party Developer(第三方开发人员):不是你项目项目团队的成员,但希望使用你的服务的那些开发人员;

l  Server(服务器):能够被Consumer通过网络访问的HTTP服务器/程序;

l  Endpoint(端点):服务器提供的一个URL,它标识了一个资源(Resource)或者集合(Collection);[逍遥子笔记:这里理解为URL最后面的那个字段更合适,例如URL: https://api.example.com/vi/zoos的端点就是:/zoos]

l  Idempotent(幂等):多次重复操作得到的结果一样;

l  URL SegmentURL片段):从某个URL中取出的一小部分片段;

数据设计和抽象

规划API的展示形式可能比你想象的要简单,首先要确定你的数据是如何设计以及核心程序是如何工作的?在新开发项目中进行API设计会比较容易,如果要对一个已经存在的项目进行修改使之符合REST风格,那么你就需要在抽象方面多下功夫了。[逍遥子笔记:按照Roy Thomas FieldingREST的设计,REST风格更适用于以数据为中心的架构,而非以计算为中心的架构]

有时,集合(Collection)可以表示数据库里的一张表,资源(Resource)表示表中的一行。[逍遥子笔记:这里的比喻感觉有些不恰当,数据库的一行对应的是一个资源实体,而非资源!]但是多数情况不是这样简单。事实上,你的API应该是对数据和业务逻辑的“尽可能的”抽象。非常重要的一点是:复杂的应用数据将会让第三方开发人员理解和使用起来非常困难,如果你这么做了,他们就不想使用你的API了。[逍遥子笔记:设计API时,参数和返回值的数据不应太复杂,否则开发人员发起调用和处理返回结果时都要处理半天,非常麻烦!]

有些情况下,服务的数据不能通过API暴露出来。一个常见的例子就是许多API都不允许第三方开发人员创建用户。[逍遥子笔记:设计API时,需要提供什么功能时就提供什么API出来,不要过早、过多暴露不必要的API接口,我们开发过程中通常会遇到这种情况:无论能否用到,先把自己服务的所有功能暴露出来再说,说不定就用上了,到时候就省得再修改了!]

动作(Verbs

你肯定知道HTTPGETPOST请求,这是两个通过浏览器访问各种网页时最常用的请求。术语POST甚至变成一个人们的常用语,即使不知道互联网如何工作的用户也知道能POST信息到朋友的Facebook留言板里。

你需要了解这里列出的4.5个非常重要的HTTP动作,这里的0.5个是指PATCH,因为它在功能上与PUT非常类似,剩下4个通常被API开发人员两两结合使用[逍遥子笔记:例如GETPOSTPUTDELETE]。这里是这些动作以及它们对应的数据库调用(我认为大多开发人员更熟悉数据库操作而不是设计API)。[逍遥子笔记:正式基于作者的这个理解,所以本文的很多地方都是用数据库来解释API,其实这个也非常恰当,因为REST架构风格是基于资源的,而数据库也是资源的一种形式。下面这些解释中,括号里的内容就是与该HTTP动词类似的数据库操作]

l  GETSELECT):从服务器获取一个指定资源或一个资源集合;

l  POSTCREATE):在服务器上创建一个资源;

l  PUTUPDATE):更新服务器上的一个资源,需要提供整个资源;

l  PATCHUPDATE):更新服务器上的一个资源,只提供资源中改变的那部分属性;

l  DELETEDELETE):移除服务器上的一个资源;

还有两个不常见的HTTP动作:

l  HEAD – 获取一个资源的元数据,例如一组hash数据或者资源的最近一次更新时间;

l  OPTIONS – 获取当前用户(Consumer)对资源的访问权限;

一个优秀的API将会充分利用这4.5HTTP动作让第三方开发人员与自己的数据交互,并且URL中决不包含动作/动词。[逍遥子笔记:URL是对资源描述的抽象,资源的描述一定是名词,如果引入了动词,那这个URL就表示了一个动作,而非一个资源,这样就偏离了REST的设计思想]

通常,GET请求能够被浏览器缓存(而且通常都会这么做),例如,当用户发起第二次POST请求时,缓存的GET请求(依赖于缓存首部)能够加快用户的访问速度。一个HEAD请求基本上就是一个没有返回体的GET请求,因此也能被缓存。

版本控制

无论你在设计什么系统,也不管你事先做了多么详尽的计划,随着时间的推移和业务的发展,你的程序总会发生变化,数据关系也会发生变化,资源可能会被添加或者删除一些属性。只要软件还在生存期内并且还有人在用它,开发人员就得面对这些问题,对于API设计来说,尤其如此。[逍遥子笔记:根据Roy Thomas Fielding对资源的解释:资源描述和资源实体是分开的,而设计REST API是基于资源描述,当资源实体发生变更时,只要修改资源描述和资源实体的映射,就能保证资源描述不变,进而保证所设计的API不变,所有使用API的第三方程序也不需要做任何修改,因此,REST风格就是用于解耦这种服务端和客户端的关系]

API是一份调用方(Consumer)和服务器之间已达成的契约,更改服务器的API必然会影响其向后兼容性,对契约的破坏将会招致使用方(Consumer)的抱怨,如果你改动很大,他们可能会放弃使用你的服务。为了确保服务器程序能够进化升级,同时能够让使用方感到满意,你需要在引入新版本API的同时继续让旧版本的API正常工作。

注意,如果你只是简单地为API增加一些新特性,例如为资源增加新属性(这些新属性并非必须的,没有它们资源也能工作),或者新增了端点,那就不需要升级API的版本号,因为这些变化并不会破坏向后的兼容性。当然,你还是需要更新API设计文档(你和调用方的契约)。[逍遥子笔记:文档对于API来说太重要了,没有好的文档必然没有好的API,因此API和它的文档一定同步修改,甚至要先修改文档]

过一段时间之后,你可以告诉调用方不建议(deprecate)使用旧版本[逍遥子笔记:就像java里面的depredated注释一样,用于告诉使用方,我不建议你使用它了,过一段时间之后我可能就不支持它了]。不建议使用一个API并不意味着马上就要关闭它或者降低它的服务质量,而是告诉你的API使用人员他们需要版本升级,旧的版本将在未来一段时间之后被停止服务。[逍遥子笔记:通过过渡期来提醒用户升级API,明确告诉调用方什么API处在过渡期,过渡期内新老版本同时都能工作,但老版本在过渡期之后就会被去掉]

URL中加入版本号是一个优秀的API设计,当然还有另一个常用的解决办法就是把版本号放在请求首部中[逍遥子笔记:HTTP请求的accept字段],根据多年与第三方开发人员打交道的经验,我可以告诉你把版本号放在URL里要比放在请求的首部中更容易实现和使用。[逍遥子笔记:对这种方法还有一些疑问,个人理解,版本应该标识资源,也就是版本是针对URL尾部的端点,例如https//api.example.com/vi/zoos中的/zoos,按照这种思路,当一个项目包含很多类型的资源,这些资源需要要分级进行展示,此时版本号放在URL里可能遇到很多问题,例如:有个即时通信项目IM,它包含user_info(用户信息),user_rel(用户关系),message三个资源大类,其中user_info又分为公开信息public和私有信息private两个类型,public类型包括个人简介的二维码信息self_infoprivate类型包括个人的应用锁信息lock user_rel(用户关系)类型包括好友关系friends和群组groups两个类型,groups包含群成员member和群信息info两个子类型资源,messge包含新聊天消息new和历史聊天消息history,这些资源组织成树状结构后如下图所示:

假设我的URL根(在本文后面将会介绍URL的根)为:https//test.jason.com/im/*,那么上述资源对应的URL为:

(1)       https//test.jason.com/im/user_info/public/self_info 

URL表示资源:用户个人简介的二维码信息;

(2)       https//test.jason.com/im/user_info/private/lock

URL表示资源:用户的应用锁信息;

(3)       https//test.jason.com/im/user_rel/friends

URL表示资源:用户的好友信息

(4)       https//test.jason.com/im/user_rel/groups/member

URL表示资源:群组的群成员

(5)       https//test.jason.com/im/user_rel/groups/info

URL表示资源:群组的信息

(6)       https//test.jason.com/im/message/new

URL表示资源:用户新聊天消息

(7)       https//test.jason.com/im/message/history

URL表示资源:用户的历史聊天消息

问题是:在这种情况下版本号应该放在URL的那个地方?

如果版本号是针对IM整个项目,例如这里的IM项目整体分为v1v2两个大版本,此时的URL首部就能改成:https//test.jason.com/im/v1/*https//test.jason.com/im/v2/*,实际的URL资源将变成:https//test.jason.com/im/v1/message/new

如果版本号是针对IM项目中的某个子类,例如这里的message类型分为v1v2两个版本,那么消息类的URL根就会变成:https//test.jason.com/im/message/v1/*https//test.jason.com/im/message/v2/*,实际的URL资源将会变成:https//test.jason.com/im/message/v1/new

如果版本号针对的资源类型更详细,那么版本号就会更靠后。在一些负责类型的项目中,资源的类型也会非常复杂,层级也更深,按照本文作者建议的方法就很难确定v1的位置]

分析

跟踪各版本/端点的API被调用的情况[逍遥子笔记:由此可见版本号对应URL末尾的“端点”]。可以通过在数据库中为每个API增加一个计数器来实现,来一个请求就将对应的使用计数加1。统计各API的调用情况会带来很多好处,例如,优化调用频度最高的API

为了构建第三方开发人员喜欢的API,最重要事情是确定何时不建议(deprecate)用户使用旧版本API,你可以使用这些不建议的(deprecatedAPI来告知第三方开发人员,这是在你废掉旧版本之前提醒他们的一个好途径。

通知第三方开发人员的过程可以自动化完成,例如,每调用10000deprecated API就给相应开发人员发一个邮件提醒。

API的根URL

无论你是否相信,API的根设计非常重要。当开发人员接手一个使用你的API所开发的旧项目,并且需要为它增加新特性的时候,他可能完全不了解你的服务,或许他们知道的就是所调用的一系列URL。重要的是你APIURL根应该尽可能简单,一个又长又复杂的URL看起来就吓人,它很可能就把这些第三方开发人员吓跑了。

这里有两个普通的URL根:

l  https//example.org/api/vi/*

l  https//example.com/vi/*

如果你的应用程序很庞大,或者未来它可能变得很庞大,你可以把API放在各自的子域内,这么做可以让你的程序在以后的发展中更灵活、更容易扩展。[逍遥子笔记:这里作者想表达的意思好像是把URL所表示的资源进行分类、分层级,不同的资源放在不同的类中]

如果你的程序不会变得这么大,或者你想简化程序的使用(例如,你想通过一个框架同时提供网站和API服务),就把你的API放在URL的根域(例如:/api/)之后。

最好让你API的根也包含内容。例如,访问github API的根就会得到一个端点(端点代表资源)列表。我更偏好使用根URL获取那些对“正在迷茫中的”开发人员来说有用的信息,例如:怎么获取API的开发者文档。

注意使用HTTPS前缀,一个好的RESTful API必须使用HTTPS作为前缀。[逍遥子笔记:例如:https://api.example.com/v1/zoos]


  评论这张
 
阅读(187)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017