REST API最佳实践:如何接受参数值列表作为input
我们正在推出一个新的REST API,我希望有一些关于如何input参数格式化的最佳实践的社区意见:
现在,我们的API非常以JSON为中心(只返回JSON)。 关于我们是否需要返回XML的争论是一个单独的问题。
由于我们的API输出是以JSON为中心的,所以我们一直在走下一条路,在这条路上我们的input是以JSON为中心的,而且我一直认为对于一些人来说可能是很方便的,但总的来说很奇怪。
例如,要获得一些产品的细节,可以一次拉多个产品,我们目前有:
http://our.api.com/Product?id=["101404","7267261"]
我们是否应该简化为:
http://our.api.com/Product?id=101404,7267261
还是有方便的JSONinput? 更多的痛苦?
我们可能想要同时接受这两种风格,但这种灵活性是否会导致更多的混乱和头痛(可维护性,文档等)?
更复杂的情况是我们想要提供更复杂的input。 例如,如果我们想在search时允许多个filter:
http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}
我们不一定要将filtertypes(例如productType和颜色)作为请求名称,如下所示:
http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]
因为我们想把所有的filterinput分组在一起。
最后,这真的很重要吗? 可能有这么多JSON实用程序在那里inputtypes只是没有多大关系。
我知道我们的JavaScript客户端对API进行AJAX调用可能会喜欢JSONinput,使他们的生活更轻松。
退一步
首先,REST将URI描述为通用唯一的ID。 有太多的人抓住了URI的结构,哪些URI比其他的更“宁静”。 这个说法是荒谬的,因为说一个人叫“鲍勃”比称呼他“乔”更好 – 两个人都得到了“识别一个人”的工作。 URI不过是一个普遍唯一的名字。
所以在REST的眼中,争论是否?id=["101404","7267261"]
比?id=101404,7267261
或\Product\101404,7267261
更宁静。
现在,已经说了很多次,URI是如何构build的,通常可以作为RESTful服务中其他问题的良好指标。 一般来说,你的URI和问题有几个红旗。
build议
-
同一资源和
Content-Location
多个URI我们可能想要同时接受这两种风格,但这种灵活性是否会导致更多的混乱和头痛(可维护性,文档等)?
URI标识资源。 每个资源应该有一个规范的URI。 这并不意味着你不能有两个URI指向相同的资源, 但有明确的方法去做。 如果您决定使用JSON和基于列表的格式(或任何其他格式),则需要决定哪些格式是主要的规范 URI。 对指向相同“资源”的其他URI的所有响应应该包括
Content-Location
标头 。使用名称类比来说,拥有多个URI就像拥有人的昵称一样。 这是完全可以接受的,而且往往是相当方便的,但是如果我使用昵称,我仍然可能想知道他们的全名 – “正式”的方式来引用这个人。 这样当有人用全名“Nichloas Telsa”提到某人的时候,我知道他们正在谈论我称为“尼克”的同一个人。
-
在你的URI中“search”
更复杂的情况是我们想要提供更复杂的input。 例如,如果我们想要允许多个filtersearch…
我的一般经验法则是,如果你的URI包含一个动词,这可能是一个表明某事是closures的。 URI标识一个资源,但是他们不应该指出我们对这个资源做了什么 。 这就是HTTP的工作,或者说是平静的界面。
要打败这个名字比喻死亡,在URI中使用动词就像当你想与他们交互时改变某人的名字。 如果我和Bob互动,Bob的名字就不会成为“BobHi”,当我想对他说嗨。 同样,当我们要“search”产品时,我们的URI结构不应该从“/ Product / …”更改为“/ Search / …”。
回答你的初始问题
-
关于
["101404","7267261"]
和101404,7267261
:我的build议是为了简单起见避免使用JSON语法(也就是说,当你不必要的时候,不要求你的用户做URL编码)。 这将使您的API更有用。 更好的是,正如其他人所build议的,使用标准的application/x-www-form-urlencoded
格式,因为它可能是最终用户最熟悉的(例如?id[]=101404&id[]=7267261
)。 它可能不是“漂亮”,但漂亮的URI不一定意味着可用的URI。 不过,重申一下我的初衷,最后在谈到REST时,没关系。 不要太在意。 -
您的复杂searchURI示例可以以与您的产品示例非常相似的方式解决。 我build议再次使用
application/x-www-form-urlencoded
格式,因为它已经是很多人熟悉的标准了。 另外,我会build议合并这两个。
你的URI …
/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}
URI编码后的URI
/Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D
可以转化为…
/Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red
除了避免URL编码的要求,让事情看起来更加标准化之外,现在它已经对API进行了一些均一化处理。 用户知道,如果他们想要检索产品或产品列表(两者都被认为是RESTful术语中的单一“资源”),他们对/Product/...
URI感兴趣。
将值列表作为URLparameter passing的标准方法是重复它们:
http://our.api.com/Product?id=101404&id=7267261
大多数服务器代码会将其解释为值列表,尽pipe许多服务器代码都具有单一值简化的function,所以您可能需要查看。
分隔值也是可以的。
如果你需要发送JSON到服务器,我不喜欢在URL中看到它(这是一种不同的格式)。 特别是URL有一个大小限制(实际上如果不是理论上的话)。
我看到有些人用RESTfully做了一个复杂的查询,分两步进行:
-
POST
您的查询要求,接收一个ID(实质上创build一个search条件资源) -
GET
search,引用上面的ID - 如果需要,可以select删除查询需求,但是请注意它们的需求可供重用。
第一:
我认为你可以做到这两点
http://our.api.com/Product/<id>
:如果你只是想要一个logging
http://our.api.com/Product
:如果你想要所有的logging
http://our.api.com/Product/<id1>,<id2>
:正如詹姆斯所build议的,因为产品标签是一个参数
或者我最喜欢的是:
您可以使用超媒体作为 RestFul WS 的应用程序状态 (HATEOAS)属性的引擎,并调用http://our.api.com/Product
来返回http://our.api.com/Product/<id>
的等效url http://our.api.com/Product/<id>
并在此之后调用它们。
第二
当你不得不对url调用进行查询时。 我会build议再次使用HATEOAS。
1)打电话给http://our.api.com/term/pumas/productType/clothing/color/black
2)打个电话http://our.api.com/term/pumas/productType/clothing,bags/color/black,red
3)(使用HATEOAS)打电话给` http://our.api.com/term/pumas/productType/ – >收到所有可能的url – >打电话给你想要的(衣服和包) – >接收可能的颜色URL – >调用你想要的
你可能想看看RFC 6570 。 这个URI模板规范显示了许多URL如何包含参数的例子。
第一种情况:
正常的产品查找将如下所示
http://our.api.com/product/1
所以我认为最好的做法是你做到这一点
http://our.api.com/Product/101404,7267261
第二种情况
用查询string参数search – 很好这样。 我会试图将术语与AND和OR相结合,而不是使用[]
。
PS这可能是主观的,所以做你觉得舒服的。
将数据放入url的原因是链接可以粘贴在用户的网站上/共享。 如果这不是一个问题,通过一切手段,而不是使用JSON / POST。
编辑:在反思我认为这种方法适合一个实体复合关键,但不是多个实体的查询。
我会和nategood的答案一起,因为它是完整的,它似乎有你的需要。 尽pipe如此,我想添加一条关于如何识别多个(1个或多个)资源的评论:
http://our.api.com/Product/101404,7267261
这样做,你:
通过强迫他们把你的回答作为一个数组来解释客户端 ,这对我来说是非常直观的,如果我提出以下要求: http://our.api.com/Product/101404
: http://our.api.com/Product/101404
用一个API 创build多余的 API来获取所有产品,以及获得一个或多个API。 由于您不应该为了用户体验而向用户展示超过1页的细节,我相信超过1个ID将无用而纯粹用于过滤产品。
这可能不会有问题,但是您必须通过返回一个实体(通过validation您的响应是否包含一个或多个实体)或让客户pipe理它来自己处理服务器端。
例
我想从Amazing订购一本书。 我确切地知道它是哪本书,并且在导航恐怖书籍时在列表中看到它:
- 10 000个惊人的线,0惊人的testing
- 惊人的怪物的回报
- 让我们复制惊人的代码
- 结束的惊人的开始
select第二本书后,我被redirect到一个详细列出书籍部分的页面:
-------------------------------------------- Book #1 -------------------------------------------- Title: The return of the amazing monster Summary: Pages: Publisher: --------------------------------------------
或在一个页面给我的书只有完整的细节?
--------------------------------- The return of the amazing monster --------------------------------- Summary: Pages: Publisher: ---------------------------------
我的想法
当得到这个资源的细节时,我build议在保证unicity的时候在pathvariables中使用ID。 例如,下面的API提供了多种方法来获取特定资源的详细信息(假设产品具有唯一的ID,并且该产品的规格具有唯一的名称,您可以自上而下导航):
/products/{id} /products/{id}/specs/{name}
当你需要超过1资源,我会build议从一个更大的集合过滤。 对于同样的例子:
/products?ids=
当然,这是我的意见,因为它不是强加的。