第一
根据RFC 3986§3.4(统一资源标识符§(语法组件)|查询
3.4查询
查询组件包含非分层数据,以及路径组件中的数据(第3.3节),用于标识URI方案和命名权限(如果有)范围内的资源。
查询组件用于检索非分层数据;在自然界中,没有什么比家谱更有层次感了!因此-不管您是否认为它是“ REST-y”-为了符合Internet上的系统的格式,协议和框架并用于开发Internet上的系统,不得使用查询字符串来标识此信息。
REST与该定义无关。
在解决您的特定问题之前,您对“搜索”的查询参数的命名不正确。最好将您的查询段视为键值对字典。
您的查询字符串可以更恰当地定义为
?first_name={firstName}&last_name={lastName}&birth_date={birthDate}
等等
回答您的特定问题
1)哪种API设计更RESTful,为什么?在语义上,它们的含义和行为方式相同。URI中的最后一个资源是“子代”,有效地暗示客户端正在子代资源上运行。
我认为这并不像您认为的那么明确。
这些资源接口都不是RESTful的。RESTful体系结构样式的主要前提是必须从服务器以超媒体的形式传递应用程序状态转换。人们已经在URI的结构上进行了努力,以使其以某种方式成为“ RESTful URI”,但是有关REST的正式文献实际上对此没有什么可说的。我个人的观点是,有关REST的许多元错误信息是为了打破旧的不良习惯而发布的。(构建一个真正的“ RESTful”系统实际上是一项相当大的工作。该行业陷入了“ REST”的困境,并以毫无意义的条件和限制回填了一些正交的问题。)
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代,则可以将其表示为
/Persons/Joe/Parents/Parents/Parents/Parents/Parents/Parents/Parents/Parents?id={Joe.NotableAncestor}
但这导致通过路径参数来表示此数据的第二个主要选择。
2)使用路径参数“查询层次结构”您可以开发以下结构来帮助减轻使用者的负担,并且仍然具有有意义的API。
要回顾147代,用路径参数表示此资源标识符,您可以
/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor}
要从他的曾祖父母那里找到乔,您可以在图表上向下看乔的ID的已知代数。
/Persons/JoesGreatGrandparent/Children;generations=3?id={Joe.Id}
这些方法的主要注意事项是,在标识符和请求中没有更多信息时,您应该期望第一个URI正在使用Joe.NotableAncestor标识符从Joe检索一个147代的Person。您应该期望第二个检索到Joe。假设您真正想要的是呼叫客户端能够检索整个节点集及其在根Person和URI最终上下文之间的关系。您可以使用相同的URI(带有一些其他修饰)并text/vnd.graphviz
在您的请求中设置“接受” 来完成此操作,这是IANA注册的.dot
图形表示形式的媒体类型。这样,将URI更改为
/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor.Id}#directed
使用HTTP请求标头
Accept: text/vnd.graphviz
,您可以让客户很清楚地传达信息,即他们希望Joe和147世代之间的世代层次结构的有向图,在此之前的第147世代包含一个被标识为Joe的“著名祖先”的人。
我不确定text / vnd.graphviz的片段是否具有任何预定义的语义;在搜索指令时找不到任何语义。如果该媒体类型确实确实具有预定义的片段信息,则应遵循其语义来创建一致的URI。但是,如果未预先定义这些语义,则URI规范会声明片段标识符的语义不受限制,而是由服务器定义,从而使此用法有效。
3)除了对资源进行“过滤”外,查询字符串的真正用途是什么?如果采用第一种方法,则将filter参数作为路径参数而不是查询字符串参数嵌入到URI本身中。
我相信我已经彻底击败了这一点,但是查询字符串不是用于“过滤”资源的。它们用于从非分层数据中识别您的资源。如果您已准备向下钻取层次结构与您的路径
/person/{id}/children/
,并且您希望确定一个特定的孩子或特定组的孩子,你可以使用适用于您的识别一组某属性,包括它的查询中。