假设我有三个相关的资源,如下所示:上面描述了这些资源之间的关系,如下所示:每个祖父母都可以映射到一个或几个父母。每个父母可以映射到一个或几个孩子。我希望能够支持对子资源的搜索,但要符合过滤条件:

如果我的客户向我传递了对祖父母的ID引用,我只想对作为该资源的直接后代的孩子进行搜索祖父母。

如果我的客户向我传递了父母的ID参考,我只想搜索父母直接后代的孩子。

我想到了一些像这样:分别满足上述要求的

Grandparent (collection) -> Parent (collection) -> and Child (collection)




GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}




但是我也可以做这样的事情:

我的问题是:

1)哪种API设计更RESTful,为什么?在语义上,它们的含义和行为方式相同。 URI中的最后一个资源是“子级”,有效地暗示了客户端正在使用子级资源。

2)从客户端的角度看,每种方法在可理解性方面有何利弊,

3)除了对资源进行“过滤”外,查询字符串还真正用于什么目的?如果采用第一种方法,则将filter参数作为路径参数而不是查询字符串参数嵌入到URI本身中。

谢谢!

评论

您的问题的标题对浏览此文件的任何人都应该非常困惑。 URI的有效段定义为:// @ / ; /#(尽管<不建议使用password>)“查询字符串”是URI的有效组成部分,因此标题中的“ vs”简直是疯了。

您的意思是我只想搜索那些祖父母的间接后代的孩子。 ?根据您的结构,祖父母没有直子。

子女与父母有什么区别?如果父母没有孩子,父母是父母吗?设计错误的气味

回复:潜在的设计缺陷,如果您掌握有关某个人的信息,但没有有关其父母的信息,那么他们是否有资格当孩子? (例如Adam和Eve):)

#1 楼

根据RFC 3986§3.4(统一资源标识符§(语法组件)|查询


3.4 Query

组件包含非分层数据,以及路径组件中的
数据(第3.3节),用于标识URI方案和命名权限
(如果有)中的
资源)。


查询组件用于检索非分层数据;本质上,没有什么比族谱更具分层性了!因此,无论您是否认为它是“ REST- y”或“否”-为了符合Internet上的系统的格式,协议和框架以及用于开发Internet上的系统,您不得使用查询字符串来标识此信息。

REST没什么可做的

在解决您的特定问题之前,“ search”的查询参数命名不正确,最好将查询段视为键值对字典。

您的查询字符串可以更恰当地定义为

?first_name={firstName}&last_name={lastName}&birth_date={birthDate}等。

回答您的特定问题


1)哪种API设计更RESTful,为什么?在语义上,它们的含义和行为方式相同。 URI中的最后一个资源是“ children”,有效地暗示客户端正在使用children资源。


我认为这并不像您认为的那么明确。

这些资源接口都不是RESTful的。 RESTful体系结构样式的主要前提是必须从服务器以超媒体的形式传递应用程序状态转换。人们已经在URI的结构上进行了努力,以使其以某种方式成为“ RESTful URI”,但是有关REST的正式文献实际上对此却很少说。我个人的观点是,有关REST的许多元错误信息是为了打破旧的不良习惯而发布的。 (构建一个真正的“ RESTful”系统实际上是一项相当大的工作。整个行业都陷入了“ REST”的困境,并以毫无意义的条件和限制回填了一些正交的问题。)确实是说如果要使用HTTP作为应用程序协议,则必须遵守该协议规范的形式要求,并且不能“随手编写HTTP并仍然声明您正在使用http”;如果要使用URI标识资源,则必须遵守有关URI / URL规范的形式要求。

您的问题由RFC3986§3.4直接解决,我已在上面进行了链接。关于此问题的底线是,即使符合标准的URI不足以认为API是“ RESTful”,但如果您希望系统实际上是“ RESTful”并且您使用的是HTTP和URI,那么您将无法通过查询字符串,因为:


3.4查询

查询组件包含非分层数据


...它是



2)从客户的角度看,可理解性以及从设计者的角度看,可维护性在每个方面都有优缺点。


前两个的“优点”在于它们走在正确的道路上。第三个的“缺点”似乎完全是错误的。

就您的可理解性和可维护性而言,这些绝对是主观的,并且取决于客户开发人员的理解水平和设计人员的设计思想。 URI规范是有关应该如何格式化URI的明确答案。层次数据应该在路径上并使用路径参数表示。非分层数据应该在查询中表示。该片段更加复杂,因为其语义特别取决于所请求表示形式的媒体类型。因此,为了解决您问题的“可理解性”部分,我将尝试准确翻译您的前两个URI实际所说的内容。然后,我将尝试代表您说的用有效URI完成的内容。

逐字URI转换为其语义的含义
/myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}
这对父母说的祖父母中,发现他们的孩子有search={text}
只有在搜索祖父母的兄弟姐妹时,您使用URI所说的才是一致的。与您的“祖父母,父母,子女”一起,您会发现“祖父母”在他们的父母中成长了一代,然后又通过观察父母的子女而回到了“祖父母”中。

/myservice/api/v1/parents/{parentID}/children?search={text}
这说,对于由{parentID}标识的父母,找到他们的孩子有?search={text}可能可以用来为您的整个API建模。要以这种方式进行建模,将负担加在客户端上,以使其意识到,如果他们具有“ grandparentId”,则他们拥有的ID与希望看到的族图的一部分之间将存在一层间接层。要通过“ grandparentId”查找“子代”,可以调用/parents/{parentID}/children服务,然后为返回的每个子代,在其子代中搜索您的人员标识符。

以URI的形式实现您的需求
如果您想对可以遍历树的更具扩展性的资源标识符进行建模,我可以想到几种实现此目标的方法。

1)第一个,我已经提到过。将“人物”图表示为复合结构。每个人都通过其“父母”路径提及其上方的一代,并通过其“子女”路径对其下方的一代进行引用。

/Persons/Joe/Parents/Mother/Parents
将是抓住乔的外祖父母的一种方式。

/Persons/Joe/Parents/Parents
将是抓住所有乔的外祖父母的一种方式。 >
/Persons/Joe/Parents/Parents?id={Joe.GrandparentID}
会抓住乔的祖父母的标识符。由于“父母/父母/父母”模式中缺少分支标识,因此在服务器上强制使用dfs。)您还可以从支持任意数量的世代中受益。如果出于某种原因,您希望查找8代,则可以将其表示为





2)使用路径参数“查询层次结构”您可以开发以下结构来帮助减轻使用者的负担,并且仍然具有有意义的API。

要回顾147代,用路径参数表示此资源标识符,您可以执行以下操作

可以向下查看该图以了解Joe的ID的已知代数。
/Persons/Joe/Parents/Parents/Parents/Parents/Parents/Parents/Parents/Parents?id={Joe.NotableAncestor}

这些方法的主要注意事项是,在标识符和请求中没有更多信息时,您应该期望第一个URI正在检索具有Joe.NotableAncestor标识符的Joe的147人。您应该期望第二个检索到Joe。假设您真正想要的是呼叫客户端能够检索整个节点集及其在根Person和URI最终上下文之间的关系。您可以使用相同的URI(带有一些其他修饰)并在请求中设置/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor}的接受(这是/Persons/JoesGreatGrandparent/Children;generations=3?id={Joe.Id}图形表示形式的IANA注册媒体类型)来执行此操作。这样,使用HTTP请求标头将URI更改为

text/vnd.graphviz
,您可以让客户端很清楚地传达他们想要的消息。乔和147代之间的世代层次结构的有向图,其中第147世代包含一个被标识为乔的“著名祖先”的人。

我不确定text / vnd.graphviz是否有任何预为其片段定义了语义;在搜索指令时找不到任何语义。如果该媒体类型确实确实具有预定义的片段信息,则应遵循其语义来创建一致的URI。但是,如果未预先定义这些语义,则URI规范会声明片段标识符的语义不受限制,而是由服务器定义,从而使此用法有效。



3)除了对资源进行“过滤”外,查询字符串还真正用于什么目的?如果采用第一种方法,则将filter参数作为路径参数而不是查询字符串参数嵌入到URI本身中。


我相信我已经彻底击败了这一点,但是查询字符串不是用于“过滤”资源的。它们用于从非分层数据中识别您的资源。如果您要通过路径来深入研究层次结构
.dot,并且希望标识一个特定的子集或一组特定的子集,则可以使用适用于要标识的子集的某些属性,并将其包含在其中查询。

评论


RFC仅涉及层次结构,因为它定义了用于解析相对URI引用的语法和算法。您能否详细说明或引用一些资料,以解释原始帖子中的示例为何不一致?

–user2313838
15年6月30日在13:58

家族树不是图,不是树,也不是完全分层的。考虑多名父母,离婚和再婚等

– Myster
16年2月18日在1:49

@RobertoAloi当HTTP已经有一个定义时,通过空集来传达您自己的“未找到项目”接口对我来说似乎违反直觉。基本原则是,您要服务器返回“事物”,并且如果没有“事物”要返回,则服务器将其与“ 404-Not Found”进行通信,这有什么反常理?

– K. Alan Bates
17年1月30日在17:24



我一直相信404表示未找到资源的根URL,即整个集合。因此,如果您查询/ books?author = Jim并且没有jim的书,那么您会收到一个空集[]。但是,如果您查询/ articles?author = Jim,但文章资源甚至不存在,因为集合404将有助于表明根本没有用在寻找任何文章。

– ADJenks
18-10-24在9:29



@adjenks您没有从正式规范中学到这一点。从结构上讲,如果可以帮助您推断组成部分的用途,则可以认为url的组成部分包含“根”,但是最终查询字符串不是针对通过路径标识的资源的显示过滤器。查询字符串是网址的一等公民。当您在服务器上找不到与您的URL(包括查询字符串)匹配的资源时,404就是在http中定义的用于传达此信息的手段。您构成的交互模型引入了差异而没有差异。

– K. Alan Bates
18-10-24在13:33

#2 楼

这是您弄错的地方:


如果我的客户向我传递了ID参考


在REST系统中,永远不要打扰客户与ID。客户端应该知道的唯一资源标识符应该是URI。这就是“统一接口”的原则。

考虑客户端如何与您的系统进行交互。假设用户开始浏览祖父母列表,他选择了祖父母的孩子之一,将他带到/grandparent/123。如果客户端应该能够搜索/grandparent/123的子级,则根据“ HATEOAS”,对/grandparent/123进行查询时返回的所有内容都应将URL返回到搜索界面。该URL应该已经具有需要由其嵌入的当前祖父母过滤的任何数据。

根据REST,链接看起来像/grandparent/123?search={term}还是/parent?grandparent=123&search={term}/parent?grandparentTerm=someterm&someothergplocator=blah&search={term}都是无关紧要的。请注意,尽管所有这些URL使用不同的条件,但它们具有相同数量的参数{term}。您可以在任何这些URL之间进行切换,也可以根据特定的祖父母来混合使用这些URL,并且客户端不会中断,因为即使底层实现可能有显着差异,资源之间的逻辑关系也是相同的。

如果您创建的服务相反,则当您选择一种方法时需要/grandparent/{grandparentID}?search={term},而当您选择另一种方法时则需要/children?parent={parentID}&search={term} a},这将导致过多的耦合,因为客户端将必须知道在不同关系上插值不同的事物在概念上相似。

究竟使用/grandparent/123?search={term}还是/parent?grandparent=123&search={term}只是一个品味问题,无论哪种实现方式现在对您来说都更容易。重要的是,如果您更改URL策略或对不同的父子关系使用不同的策略,则无需修改客户端。

#3 楼

我不确定为什么人们会认为将ID值放在URL中意味着REST API,REST是关于处理动词,传递资源的。

因此,如果您想放置新用户,则必须发送大量数据,并且POST http请求是理想的,因此尽管您可以发送密钥(例如,用户ID) ,您将以POST数据的形式发送用户数据(例如,姓名,地址)。规范的“如果不在URI中,则不是REST”。请记住,REST的原始论点并没有真正提到http,它是在客户端服务器中处理数据的一种习语,而不是对http的扩展(尽管很明显,http是实现REST的主要形式) 。

例如,菲尔丁以学术论文的要求为例。您想要检索“喝啤酒的约翰博士论文”资源,但您可能还需要初始版本或最新版本,因此资源标识符可能不是一个可以轻易引用为单个ID的东西。放在URI中。 REST允许这样做,其背后的明确意图是:


REST依靠作者
选择最适合所识别概念性质的资源标识符
/>

因此,没有什么可以阻止您使用静态URI来检索您的父母,而是在查询字符串中传入搜索字词来标识您要寻找的确切用户。在这种情况下,您标识的“资源”是一组祖父母(因此URI包含“祖父母”作为URI的一部分。REST包含“控制数据”的概念,该概念旨在确定您的哪种表示形式)资源将被检索-这些示例中给出的示例是缓存控制,还有版本控制-因此,我可以通过将版本作为控制数据而不是URI的一部分来传递对John博士出色论文的要求。

我认为通常不提及的REST接口示例是SMTP,在构造邮件时,您会向邮件中发送动词(FROM,TO等)以及邮件各部分的资源数据。尽管它不使用http谓词,但它使用了自己的集合。

所以...虽然您确实需要在URI中具有一些资源标识,但它不一定是您的id引用,可以很高兴地将其作为控制数据,查询字符串甚至是POST数据进行发送。您的REST API是您正在抚养孩子,而您已经在URI中了。

所以在我看来,阅读REST的定义时,您正在请求子资源,传入控制数据(以querystring的形式)以确定要返回的资源。结果,您无法为祖父母或父母提出URI请求。您希望孩子返回,因此术语“父母/祖父母”或“ id”绝对不应使用此类URI。孩子应该是。

评论


实际上,URI作为概念是REST的核心。 URI语法并不重要。正确的REST接口必须使用通用语法标识资源。在REST原则的基础上,使用JSON对象而不是RFC 3986 URI标识复杂的资源并不一定很坏,尽管如果这样做,则应对整个API使用JSON RI。

– Lie Ryan
16年6月12日在2:03



#4 楼

很多人已经在谈论REST的含义,等等。但是似乎没有一个真正的问题可以解决:您的设计。

为什么祖父母与父亲有什么不同?他们俩都有孩子,可能会有孩子……

最终,他们都是“人类”。您的代码中也可能包含该代码。因此使用:

GET /<api-endpoint>/humans/<ID>


将返回一些有关人类的有用信息。 (名称和内容)

GET /<api-endpoint>/humans/<ID>/children


显然将返回一组子代。如果不存在子代,则可能要使用一个空数组。

为了简化起见,您可以添加一个标志“ hasChildren”。或'hasGrandChildren'。


更聪明一点就更难


评论


被低估的答案

– Vitim.us
20年6月19日在18:40

#5 楼

以下是更完整的RESTfull,因为每个grandparentID都有自己的URL。通过这种方式,可以以独特的方式识别资源。

GET /myservice/api/v1/grandparents/{grandparentID}
GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}


查询参数搜索是从该资源的上下文执行搜索的好方法。 >
当一个家族越来越大时,您可以使用开始/限制作为查询选项,例如:

具有唯一URL / URI的资源。我认为,只有在它们也可能被忽略时,才应使用查询参数。

也许这是一本不错的书,网址为http://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling以及Roy T Fielding的原始博士学位论文https:// /www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf很好且完整地解释了这些概念。

#6 楼

我将使用

/myservice/api/v1/people/{personID}/descendants/2?search={text}


在这种情况下,每个前缀都是有效资源:



/myservice/api/v1/people:所有人

/myservice/api/v1/people/{personID}:一个人的信息全面(包括祖先,兄弟姐妹,后裔)

/myservice/api/v1/people/{personID}/descendants:一个人的后代

/myservice/api/v1/people/{personID}/descendants/2:一个人的孙子

也许不是最好的答案,但至少对我来说很有意义。

#7 楼

在我之前的许多人已经指出,URL格式在RESTful服务的上下文中是无关紧要的。我的两分钱将是...最初撰写中提倡的REST的一个重要方面是“资源”的概念。 。当您设计URL时,您可能需要牢记的一个方面是,“资源”不是单个表中的行(尽管可以)。而是一种表示形式。 .and可用于更改该表示形式的状态(并有效地更改存储的底层介质)。并且给定资源仅在您的业务环境中有意义。例如,

在您的示例
/ myservice / api / v1 / grandparents / {grandparentID} / parents / children?search = {text}

如果缩短此时间可能是更有意义的资源
/ myservice / api / v1 / siblingsOfGrandParents?search = ..

在这种情况下,您可以将'siblingsOfGrandParents'声明为资源。这简化了URL

出普遍存在一种误导性的观念,即您需要以URL中更明确的形式适应域对象之间的每种类型的层次关系。.在域对象和资源之间进行一对一映射并表示所有它们之间的关系...我应该相信这个问题...是否需要通过URL公开这种关系...特别是路径段...也许没有。

评论


当您花费宝贵的时间降低答案的等级时,如果您可以说出来并说出自己的理由,将会很有帮助。

– Senthu Sivasambu
17年12月1日下午14:51

我不完全确定为什么要根据您的回答投票否决。我同意你的看法。我想到的一些直接的事情是,您在OP询问层次结构关系时通过提及“兄弟姐妹”提供了一个切线的示例。也许您的写作风格也可以得到改善。您使用很多“ ...”,我们在正式,专业或指导性写作中往往不使用“ ...”。还有一点冗余(例如,您提到,然后在示例中说)。也许您的答案可能更简洁,并且可以更直接地解决OP的问题。

–肯尼·卡森(KennyCason)
19年2月5日在18:21