设计一个REST API并不容易。那些声称没有设计一个不同,没有一个适度复杂的系统设计,或者是一种罕见的天才,从来没有发现任何挑战性的在他们的生活中。一个可以设置决心坚持REST设计原则在客户端用户可以直观地找到他们所需要的端点,所有“对象”系统表示为资源,可以采取行动的四个HTTP操作(POST、GET、PUT、删除)和用户可以做任何他们想实现以尽可能少的电话。但挑战将会出现。立即。
几年前在《美国医学会杂志》,我们开始着手建立开云官网手机网页版入口一个新的最终取代我们所有的SOAP和REST APIDWR接口。两个关键的设计挑战:
- 健谈和肥大的API设计
- 资源与业务流程建模
第二个挑战是很困难的,这是一个我可以整天谈论(也许我将在后面的文章)。但是,今天我要关注健谈和厚实的问题。
健谈和厚实的api
不久之后人们开始写作对初始迭代的API,讨论“爽直”了。API开发人员一直在交谈这概念所有在的网络,但它的精髓是:用户需要的数据在尽可能少的电话后。如果他们有太多小打电话让他们寻求的所有数据,然后API太“健谈”他们的需求。另一方面,如果太大API调用,并返回比需要更多的数据,API也可以被认为是“粗”。这光谱的两端也被称为细粒度和粗粒度api。用户调用将会看起来像这样:
让我们举个例子来说明这一点。说我有一个API调用来检索用户评论的博客。评论可以在一篇博客文章,所以,如果他们,他们应该有一个页面他们出现在属性来表示页面。他们也应该有一个作者字段指示作出了评论。
用户调用将会看起来像这样:
GET /评论? = 27页
和JSON数据的响应可能会看起来像这样:
[{" id ": " 796 ", "作者":“23”,“页面”:“27”、“createdDate”:“2016 - 04 - 08 - t09:15:00”、“文本”:“这个博客页面改变了我。我从来没有读过。”}, { "id": "1097", "author": "1", "page": "27", "createdDate": "2016-04-08T16:15:00", "text": "I agree! Thanks for posting!", "inReplyTo": "796" } ]
这个例子是过于简化,但你可以看到在评论,作者属性的值23
和1
你可以认为是作者的独特的用户id。同样,博客页面这些评论被其称为页面的ID27
。第二个评论也在回复别人的评论,这样inReplyTo
属性的值为796年
参考另一个评论ID。
这种负载非常简短,但它提出了一些问题如果API用户感兴趣了解作者不仅仅是作者的用户ID(至少可以肯定的是,他们想知道作者的名字!)
首先,如果你是这个API的用户,你没有给出任何提示,如何检索完整的用户信息与用户ID相关联1
,也没有完整的信息内容页ID27
。这是一个问题,“发现”。
但即使发现问题已经解决了,你仍然需要一个单独的API调用来检索用户。进一步说,如果你是检索数百条评论的集合,您可能需要让用户呼吁每个评论你检索。你需要让数百调用API来获取你正在寻找的信息。因此,术语“健谈”。
但让我们看看另一个极端。API会将所有对象属性的完整的信息附加到响应和你会得到这样的:
[{"作者":{" id ":“23”,“活动”:“真正的”,“firstName”:“丽莎”,“姓”:“龟”、“avatarUrl”:“http://base_url.com/lisa.jpg”,“registrationDate”:“2012 - 04 - 19 t09:16:00”、“爱好”:“这是海龟一路”},“页面”:{" id ":“27”,“标题”:“生命的意义”,“createdDate”:“2016 - 04 - 07年t14:07:00”,“作者”:{…另一个用户对象……}……和so on... }, "createdDate": "2016-04-08T09:15:00", "text": "This blog page changed me for the better. I've never read anything quite like it." }, { "author": { "id": "1", "active": "true", "firstName": "Jason", "lastName": "Goetz", "avatarUrl": "//www.gotfaux.com/app/uploads/2016/04/FEAT-Jason.jpg", "registrationDate": "2004-02-19T07:16:00", "hobbies": "Public debate, dancing, skeet shooting" }, "page": { "id": "27", "title": "The Meaning of Life", "createdDate": "2016-04-07T14:07:00", "author": { ...another user object... } ...and so on... }, "createdDate": "2016-04-08T16:15:00", "text": "I agree! Thanks for posting!", "inReplyTo": { ...the first comment data repeated again?... } } ]
作为API的用户,您现在有你需要的所有信息。全部作者信息是可用的,你知道这个博客页面的全部细节发表评论,甚至可以看到整个其他评论这个评论是在直接回复inReplyTo
价值。
但是你有无数的新问题。
第一个是返回的有效载荷的规模。这是一个相当简单的例子。用户和页面对象可能比这些例子显示更多的属性。如果你是检索数百条评论,你得到一个完整的对象为每个属性在每个评论,这将是很多的数据。作为客户端用户,你可能没有带宽担心跨线这么多数据,但它确实可以组装所有数据服务器更多的时间,和长期运行的事务是很难在一个服务器上的CPU和内存。特别是当服务器处理多个并发请求。
还应该注意,如果检索结果是99的100的评论都是由相同的用户,所有的评论中出现在相同的页面上,然后大部分的用户和页面对象在你的结果将会是多余的。所花费的时间服务器组装用户和页面数据主要是浪费。
数据不一致性也一定会出现。在这种情况下,如果没有任何限制多少嵌入回答你在评论部分可以吗?如果有人回复第一个评论,然后有人回复,回复,然后有人回复,回复…你懂的。这些数据应如何表示?你可以选择去一层深但API用户可能会困惑的API决定切断的进一步数据,和如何编写一个客户端来使用它。
这些问题的API。
API也可以尝试找一些妥协和附加只有部分数据。而完整的作者的信息可以从一些用户电话,可收回评论有效载荷可能只包含作者的用户ID,名字和姓氏。
但是,这种方法有问题(这一切听起来这么消极!)。不一致的部分对象很难直观地使用API。上面描述的相同的矛盾与inReplyTo示例应用。同时,API可能无法提供数据首先你正在寻找。如果你只是希望作者的名字和《阿凡达》,但《阿凡达》没有提供,你仍然需要一个单独的调用来获取完整的用户对象是数据。
所以,最好的方法是什么呢?
解决方案
当设计决策和面临这样的光谱,光谱的两端提供自己的挑战,我们只能努力找到平衡,是切实可行的。我们需要找到一个解决的API设计问题,同时避免向下的API的路线。我们想保持简单,干净和宁静的。我们也想让我们的API足够灵活,允许用户来满足自己的需求的健谈,粗壮的连续体。
而“部分数据”上面的例子试图找到一个妥协的厚实,我已经指出的一些问题与妥协的方法。而不是妥协,如果我们坚持一切我们喜欢的API模型,但是给用户额外的设施将数据添加到他们的反应?
让我们看看另一种方法。使用这种方法,调用用户请求评论,但指定他们想作者字段包括:
GET /评论? = 27包括= data.author页
的反应是这样的:
{"链接":{”数据。作者": { "type": "user", "href": "http://base_url.com/comments/{data.author}" }, "data.page": { "type": "page", "href": "http://base_url.com/comments/{data.page}" }, "data.inReplyTo": { "type": "comment", "href": "http://base_url.com/comments/{data.inReplyTo}" } }, "linked": { "user": { "1": { "id": "1", "active": "true", "firstName": "Jason", "lastName": "Goetz", "avatarUrl": "//www.gotfaux.com/app/uploads/2016/04/FEAT-Jason.jpg", "registrationDate": "2004-02-19T07:16:00", "hobbies": "Public debate, dancing, skeet shooting" }, "23": { "id": "23", "active": "true", "firstName": "Lisa", "lastName": "Turtle", "avatarUrl": "http://base_url.com/lisa.jpg", "registrationDate": "2012-04-19T09:16:00", "hobbies": "It's turtles all the way down" } } }, "data": [ { "id": "796", "author": "23", "page": "27", "createdDate": "2016-04-08T09:15:00", "text": "This blog page changed me for the better. I've never read anything quite like it." }, { "id": "1097", "author": "1", "page": "27", "createdDate": "2016-04-08T16:15:00", "text": "I agree! Thanks for posting!", "inReplyTo": "796" } ] }
这是一个很大的摄取,但回报是值得的。你现在可以看到评论载荷是上市数据
。同时,响应中现在有单独的属性链接
和有关
。
的数据
部分是一模一样的给下上面的健谈API示例。但是,与帮助链接
和有关
属性,缺乏相关数据的问题要少得多。
的链接
部分负责我上面提到的“发现”的问题。对于任何属性(如简单地显示一个ID作者
,页面
和inReplyTo
)的链接部分将描述如何填补,ID到一个单独的API调用来检索信息。
的有关
部分是我们真正开始解决这些问题相关的api。在请求,要求包括任何用户
对象引用的任何评论作者
字段。产生的响应现在给的数据存储用户
中的对象有关
为任何指定用户id下部分作者
。这消除了需要做任何进一步的调用用户
端点获取的所有信息。但它也解决了冗余问题,因为每个用户一个用户只出现一次ID。换句话说,你可以有99条评论的地方作者
价值是相同的,但你只有一个包含作者的数据有关
部分。这也为服务器(潜在的)花费更少的时间比完整的附件的组装作者
数据每个评论,因为我们只装和组装作者
数据一旦数据存储。
这个解决方案提供了简单的API。这只是给你你要求的基本信息。但是,它另外给你的可发现性和灵活性要求进一步的数据在同一个请求我们解决的主要问题相关的api。我们设法解决所有这些前面提到的问题:
- 可发现性
- 没有足够的数据即需要重复API调用或爽直
- 大载荷与厚实的api
- 服务器处理时间与厚实的api
- 冗余
在《美国医学会杂开云官网手机网页版入口志》,在设计我们的REST API,我们开始寻找平衡API设计强调易用性,为我们的用户的实用性和灵活性。这个健谈vs的权衡是我们所面临的挑战的一个方面建立一个API,它为我们和我们的用户工作。我们想出一个其他JSON响应数据结构非常类似于上面的例子。它让我们在资源不妥协的清洁和精益,同时仍然允许他们寻求我们的用户检索数据。它是基于一个初始版本的松散JSON API规范,我们觉得它优雅地满足我们和我们的用户的需求。
评论?问题吗?我希望听到你的反馈!
- REST API设计——2016年4月27日
- 开云官网手机网页版入口Jama REST API:正式开放给开发者——2015年10月20日
这种方法是常用的许多其他云api。一个问题,我寻求从rest API支持多种设备。让说你有自由给许多的部分信息在一个网页,你让不同的调用后端服务呈现主页。而对于移动应用程序可以显示的内容不感兴趣。刚刚现场选择特性仍不足以控制调用的数量你在后台。
有趣的文章。我会备注OSLC标准化约定的可发现性,资源“塑造”的属性,和OSLC的查询语言,它提供了一个类似sql或SPARQL-like“选择限制抵消订单”的语法似乎REST url地址这两个健谈/的问题。它在一个标准化的方法。