REST API概念


10

我有三个关于REST API设计的问题,希望有人能对此有所启发。我已经不懈地搜索了多个小时,但没有在任何地方找到我的问题的答案(也许我只是不知道要搜索什么?)。

问题1

我的第一个问题与动作/ RPC有关。我已经开发了REST API已有一段时间了,我习惯于从集合和资源的角度思考事物。但是,我遇到了一些似乎没有适用该范式的案例,并且我想知道是否有一种方法可以将其与REST范式进行协调。

具体来说,我遇到一种情况,即修改资源会导致生成电子邮件。但是,稍后用户可以特别指出他们想重新发送之前发送的电子邮件。重新发送电子邮件时,不会修改任何资源。状态没有改变。这只是一个需要发生的动作。该操作与特定的资源类型相关。

将某种动作调用与资源URI(例如/collection/123?action=resendEmail)混合使用是否合适?指定动作并将资源ID传递给它(例如/collection/resendEmail?id=123)会更好吗?这是错误的做法吗?传统上(至少使用HTTP)执行的动作是请求方法(GET,POST,PUT,DELETE),但实际上不允许使用资源进行自定义动作。

问题2

我使用URL的querystring部分来过滤查询集合时返回的资源集(例如/collection?someField=someval)。然后,在我的API控制器中,我确定将与该字段和值进行哪种比较。我发现这真的行不通。我需要一种允许API用户指定他们想要执行的比较类型的方法。

到目前为止,我想到的最好的主意是允许API用户将其指定为字段名称的附录(例如/collection?someField:gte=someval-表示它应返回someField大于或等于(> =)someval的资源。这是一个好主意吗?是一个坏主意?如果是,为什么?有没有更好的方法可以让用户指定要对给定字段和值执行的比较类型?

问题3

我经常看到URI的那样子像/person/123/dogs拿到person小号dogs。我通常避免这样的事情,因为最后我发现通过创建类似的URI,您实际上只是在访问dogs按特定personID 过滤的集合。等同于/dogs?person=123。真的有充分的理由使REST URI的深度超过两个级别(/collection/resource_id)吗?


10
你有三个问题。为什么不分别发布它们?
anaximander 2013年

3
最好将其分解为3个独立的问题。观看者可能能够很好地回答一个问题,但不能回答所有问题。

2
我认为它们都相关。标题有点高级,但是这个问题会帮助很多人,并且在SE搜索中很容易找到。添加足够的选票和内容后,该问题应成为Community Wiki。我花了几周时间研究这些东西。
Andrew T Finnell

1
最好单独发布它们,IDK。但是,正如@AndrewFinnell所述,我认为将这些问题放在一起是一个好主意,因为这些问题是我遇到过的最棘手的与REST相关的问题,对于其他人能够找到答案也很好一起。
贾斯汀·沃肯汀

Answers:


11

将某种动作调用与资源URI(例如/collection/123?action=resendEmail)混合使用是否合适?指定动作并将资源ID传递给它(例如/collection/resendEmail?id=123)会更好吗?这是错误的做法吗?传统上(至少使用HTTP)执行的动作是请求方法(GET,POST,PUT,DELETE),但实际上不允许使用资源进行自定义动作。

我宁愿用一种不同的方式来建模,用一组资源来表示要发送的电子邮件。服务内部将在适当的时候处理发送,这时相应的资源将被删除。(或者,用户可以及早删除资源,从而导致取消发送请求。)

无论您做什么,都不要在资源名称中添加动词!那就是名词(查询部分是形容词集)。名词动词使REST怪异!

我使用URL的querystring部分来过滤查询集合时返回的资源集(例如/collection?someField=someval)。然后,在我的API控制器中,我确定将与该字段和值进行哪种比较。我发现这真的行不通。我需要一种允许API用户指定他们想要执行的比较类型的方法。

到目前为止,我想到的最好的主意是允许API用户将其指定为字段名称的附录(例如/collection?someField:gte=someval-表示它应返回someField大于或等于(>=)的资源someval。这是一个好主意吗?是一个坏主意吗?如果是这样,为什么呢?有没有更好的办法让用户指定要对给定字段和值执行的比较类型?

我宁愿指定一个通用过滤子句,并将其作为对任何请求的可选查询参数,以获取集合的内容。然后,客户端可以准确指定如何以您希望的任何方式限制返回的集合。我还要担心筛选器/查询语言的可发现性;您赚的越丰富,任意客户发现的难度就越大。至少在理论上处理该可发现性问题的替代方法是允许制作集合的限制子资源,客户端通过发布描述对集合资源的限制的文档来获得该资源。它仍然是一种轻微的滥用,但是至少它是您可以明确发现的一种滥用!

这种可发现性是我发现对REST最不满意的一件事。

我经常看到URI,它看起来像是/person/123/dogs要弄人的狗。我通常避免这样的事情,因为最后我发现通过创建这样的URI,您实际上只是在访问按特定人员ID过滤的dogs集合。等同于/dogs?person=123。真的有充分的理由使REST URI的深度超过两个级别(/collection/resource_id)吗?

当嵌套集合确实是外部集合的成员实体的子功能时,将它们构造为子资源是合理的。“子功能”是指类似UML组成关系的东西,其中破坏外部资源自然意味着破坏内部集合。

可以将其他类型的集合建模为HTTP重定向。因此/person/123/dogs确实可以通过重定向到307来响应/dogs?person=123。在这种情况下,集合实际上不是UML组成,而是UML聚合。差异很重要;这很重要!


2
您总体上有扎实的分数。但是,虽然resendEmail可以通过创建集合并将其发布来处理该动作,但这似乎不太自然。实际上,当重新发送电子邮件时(不需要),我不会在数据库中存储任何内容。不会修改任何资源,因此仅是成功或失败的操作。我无法返回调用生命周期之外存在的资源ID,从而使这种实现成为骇客行为,而不是RESTful。这根本不是CRUD操作。
贾斯汀·沃肯汀

3

有点困惑,这是可以理解的,因为我已经看到大型公司设计其REST API的所有方式,如何正确使用REST。

您认为REST是资源收集系统是正确的。它代表代表性状态转移。如果问我,这不是一个很好的定义。但是主要概念是4个HTTP VERB,它们是无状态的。

需要注意的重要一点是,您只有4个带REST的VERBS。这些是GET,POST,PUT和DELETE。您的resend示例将向REST添加新动词。这应该是一个危险信号。

问题1

重要的是要认识到REST API的调用者不必知道PUT对集合执行会导致生成电子邮件。这对我来说有点泄漏。他们所知道的是,执行a PUT可能会导致额外的任务,他们以后可以查询。他们将通过GET对最近创建的资源执行来知道这一点。这GET将返回资源以及Task与之关联的所有资源ID。然后,您以后可以查询这些任务以确定它们的状态,甚至可以提交新的任务Task

您有几种选择。

REST-基于任务资源的方法

创建一个tasks资源,您可以在其中将特定任务提交到系统中以执行操作。然后GET,您可以根据ID返回的任务确定其状态。

或者,您可以混合使用SOAP over HTTPWeb Service以便向体系结构中添加一些RPC。

查询特定资源的所有任务

GET http://server/api/myCollection/123/tasks

{ "tasks" :
    [ { "22333" : "http://server/api/tasks/223333" } ] 
}

任务资源示例

PUT http://server/api/tasks

{ 
    "type" : "send-email" , 
    "parameters" : 
    { 
         "collection-type" : "foo" , 
         "collection-id" : "123" 
    } 
}

==>返回任务的ID

223334

GET http://server/api/tasks/223334

{ 
    "status" : "complete" , 
    "date" : "whenever" 
}

REST-使用POST触发动作

您始终可以将POST其他数据添加到资源。我认为这将违反REST的精神,但仍会合规。

您可以执行类似以下的POST:

POST http://server/api/collection/123

{ "action" : "send-email" }

您将使用其他数据更新集合中的资源123。这些额外的数据实际上是告诉后端发送该资源电子邮件的操作。

我的问题是GET资源上的a 将返回此更新的数据。但是,这可以解决您的需求,并且仍然是RESTful。

SOAP-接受从REST获得的资源的Web服务

创建一个新的WebService,您可以在其中基于REST API中的先前资源ID发送电子邮件。我不会在此详细讨论SOAP,因为最初的问题是关于REST的,因此不应将这两个概念/技术进行比较,因为它们是Apples和Oranges

问题2

您在这里也有一些选择:

似乎许多发布REST API的大公司都公开了一个search集合,这实际上只是一种传递查询参数以返回资源的方式。

GET http://server/api/search?q="type = myCollection & someField >= someval"

它将返回完全合格的REST资源的集合,例如:

{
    "results" : 
       { [ 
             "location" : "http://server/api/myCollection/1",
             "location" : "http://server/api/myCollection/9",
             "location" : "http://server/api/myCollection/56"
         ]
       }
}

或者,您可以允许像MVEL这样的查询参数。

问题3

我更喜欢这些子级别,而不是必须回去使用查询参数来查询其他资源。我不认为一种方法会一成不变。您可以同时实现这两种方式,并允许调用者根据他们首次进入系统的方式来决定哪种更为合适。

笔记

我不同意其他人的可读性评论。尽管有人可能会认为REST仍不供人类使用。用于机器消耗。如果我想查看自己的推文,请使用Twitter的常规网站。我不使用其API执行REST GET。如果我想以编程方式对自己的推文进行操作,则可以使用其REST API。是的,API应该是可以理解的,但是您gte还不错,只是不直观。

REST的另一个主要优点是,您应该能够在API中的任意给定位置开始并导航至所有其他关联的资源,而无需提前知道其他资源的确切URL。GETREST中VERB 的结果应返回其引用的资源的完整REST URL。因此,查询Person将返回完全合格的URL(例如),而不是返回对象ID的查询http://server/api/people/13。这样,即使URL更改了,您也可以始终以编程方式导航结果。

回应评论

实际上,在现实世界中,有些事情需要发生,而不是创建,读取,更新或删除(CRUD)资源。

可以对资源采取其他措施。典型的关系数据库支持存储过程的概念。这些是可以对一组数据执行的附加命令。REST本质上没有这个概念。而且没有理由应该这样做。这些类型的操作非常适合RPC或SOAP Web服务。

这是我在使用REST API时遇到的一般问题。开发人员不喜欢围绕REST的概念限制,因此他们将其调整为可以执行自己想要的任何事情。但这使它脱离了RESTful服务。从本质上讲,这些URL成为GET对类似伪REST的servlet的调用。

您有几种选择:

  • 创建任务资源
  • 支持将POST其他数据添加到资源以执行操作。
  • 通过SOAP Web服务添加其他命令。

如果您使用查询参数,将使用哪个HTTP VERB重新发送电子邮件?

  • GET-这会重新发送电子邮件并返回资源的数据吗?如果系统缓存了该URL并将其视为该资源的唯一URL,该怎么办。每次他们访问URL时,都会重新发送电子邮件。
  • POST -您实际上并没有向资源发送任何新数据,只是向附加查询参数发送了数据。

根据所有给定的要求,POST对带有action fieldas POST数据的资源执行会解决此问题。


3
尽管通过HTTP实现的REST为您提供了这4个动词,但我不认为这些动词应该以它结尾。实际上,在现实世界中,有些事情需要发生,而不是创建,读取,更新或删除(CRUD)资源。重新发送电子邮件就是其中之一。我不需要在数据库中存储或修改任何内容。这只是一个成功或失败的动作。
贾斯汀·沃肯汀

@JustinWarkentin我了解您的需求。但这并不能使REST变得不是。向URL添加新动词违反REST体系结构。我将更新答案,以提供另一种替代方法,即RESTful。
Andrew T Finnell

@JustinWarkentin在我的答案中查看“ REST-使用POST触发操作”。
Andrew T Finnell

0

问题1:将某种动作调用与资源URI混合使用是否合适?或者指定动作并将资源ID传递给它会更好?

好问题。在这种情况下,我建议您使用后一种方法,即指定操作并将资源ID传递给它。这样,在第一次修改资源时,它依次将/sendEmail操作(单独注意:无需将其“重新发送”)作为单独的RESTful请求调用(以后您可以一次又一次地调用,而与修改的资源无关) )。

问题2:关于像这样使用比较运算符:/collection?someField:gte=someval

从技术上讲这是可以的,但这可能不是一个好主意。REST的主要原则之一是可读性。我建议您简单地将比较运算符作为另一个参数传递给它,例如:/collection?someField=someval&operator=gte并且当然要设计您的API,使其适合默认情况(如果该operator参数不包含在URI中)。

问题3:是否真的有充分的理由使REST URI的深度超过两个级别?

对; 用于抽象。我已经看过几个REST API,它们通过多个URI级别利用抽象层,/vehicles/cars/123或者:或/vehicles/bikes/123依次使您能够处理与集合/vehicles/vehicles/bikes集合有关的有用信息。话虽如此,我不是这种方法的忠实拥护者。实际上,您几乎不需要这样做,并且很可能可以重新设计API以仅使用2个级别。

是的,正如上面的评论所建议的那样,将来最好将您的问题分成几个单独的帖子;)


我认为问题2的示例过于简单。我需要为用于过滤集合的每个字段指定一个比较运算符,而不仅仅是一个,因此在您的示例中,它必须类似/collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq
贾斯汀·沃肯汀

0

对于问题2,另一种替代方法可能更灵活:将每个搜索都考虑为用户在使用之前构建的资源。

假设您有一个“搜索”容器,POST /api/searches/然后对内容的查询规范进行操作。它可能是JSON,XML甚至是SQL文档,对您来说更容易。如果查询正确解析,那么将使用自己的URI将新搜索创建为新资源,例如/api/searches/q123/

然后,客户端可以简单GET /api/searches/q123/地检索查询结果。

最后,您可以要求客户端删除查询,或者在关闭会话后清除查询。


0

将某种动作调用与资源URI混合使用是否合适(例如/ collection / 123?action = resendEmail)?指定操作并将资源ID传递给它(例如/ collection / resendEmail?id = 123)会更好吗?这是错误的做法吗?传统上(至少使用HTTP)执行的动作是请求方法(GET,POST,PUT,DELETE),但实际上不允许使用资源执行自定义动作。

不,这是不合适的,因为IRI只是用于标识资源而不是用于操作(但是,如果不支持使用非POST和GET方法,则ppl会暂时使用此方法替代方法)。您可以做的是寻找合适的HTTP方法,或者创建一个新的HTTP方法。在这些情况下,POST可以成为您的朋友(如果他们找不到合适的方法并且请求无法检索,请使用ppl)。另一种从电子邮件发送中获取资源的方法,因此POST /emails可以在不创建实际资源的情况下发送邮件。顺便说一句。URI结构不包含语义,因此从REST的角度来看,使用哪种URI并不重要。重要的是分配给您发送给客户端的链接的元数据(例如,链接关系)。

到目前为止,我想到的最好的主意是允许API用户将其指定为字段名称的附录(例如/ collection?someField:gte = someval-指示它应返回someField大于或等于(> =)某种程度,这是一个好主意吗?是一个坏主意吗?如果是这样,为什么呢?有没有更好的方法允许用户指定要对给定字段和值执行的比较类型?

您不必创建自己的查询语言。我宁愿使用一个已经存在的查询并将一些查询描述添加到链接元数据中。您可能应该使用RDF媒体类型(例如JSON-LD)来执行此操作,或者使用自定义MIME类型(afaik没有支持该功能的非RDF格式)。使用现有标准,可以使客户端与服务器脱钩,这就是统一接口约束的含义。

相当于/ dogs?person = 123。真的有充分的理由使REST URI的深度超过两个级别(/ collection / resource_id)吗?

如前所述,从REST的角度来看,URI结构并不重要。您可以使用/x71fd823df2例如。对于客户端来说仍然是有意义的,因为它们检查分配给链接的元数据,而不是URI结构。URI的主要目的是识别资源。在URI标准中,它们声明路径包含层次结构数据,而查询包含非层次结构数据。但是什么是层次结构可能是非常主观的。这就是为什么您遇到多个级别的深层URI和带有长查询的URI的原因。

我已经不懈地搜索了多个小时,但没有在任何地方找到我的问题的答案(也许我只是不知道要搜索什么?)。

您至少应该阅读Fielding论文中REST约束HTTP标准以及Markus的第三代Web API

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.