在URL中不包含ID的RESTful API

24

我一直在与我的同事讨论这种做法的最佳方式

以下是一个示例场景:

我正在尝试获取ID为1234的Customer的所有订单。

考虑以下端点:

/customer/orders

使用GET请求,并附带以下标头:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

带有Authorization标头,我们的身份验证机制将此请求标识为ID为1234的客户的请求,我们的服务返回所需订单。

他试图说服我这是正确的,因为一旦登录了顾客,只有他们才会请求他们的订单(在本例中,属于顾客1234的订单)-因此在URL中传递ID是不必要的。

然而... 对我来说,这不符合RESTful的规范(我可能是错的)

在我看来,它应该像这样:

/customer/1234/orders

带有标头(仅作为示例)

  

Authorization:Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

使用授权标头,我们验证用户是否有权限检索这些订单...如果有,则返回它们,否则返回401。

我的问题是,哪种方法更受欢迎?

我当然可以看到第一种方法的好处,但为了保持我们的API符合RESTful规范,我的内心告诉我应该选择第二种方法...


你最终采用了什么方法? - Phalgun
最终采用了使用身份验证标头的方法。效果很好。 - Alex
4个回答

18

资源的标识是REST的一个关键约束。你的订单和我的订单不是同一个资源,因此它们应该(而不是必须)具有不同的标识符。

将它们赋予唯一标识符(例如/customers/1234/orders)的实际原因是它会更容易支持HTTP缓存。HTTP缓存中间件主要使用URI作为缓存键。虽然可以使用其他标头(如身份验证)作为缓存键的一部分,但这种机制的支持要少得多/不可靠。

即使今天你没有使用HTTP缓存的计划,也最好安全起见并确保你的URI被设计为允许在将来需要时使用。

在进行客户端URI构建时,不需要在URI中包含客户ID,这样有些好处。然而,RESTful系统应该通过将这些URI嵌入到返回的表示中来为客户提供要遵循的URI。客户端不需要构建这些URI,因此使用带有ID的URI与使用不带ID的URI相比,对于客户端来说没有ZERO额外的工作量。如果你准备接受超媒体,那么不包含ID是没有优势的。


谢谢你的回答。如果可能的话,能否详细解释一下“如果你准备好接受超媒体”的评论? - Alex
1
超媒体药丸是遵循RESTful实现的“Hypermedia作为引擎”特性。(来源:维基百科) 然而,如果达瑞尔(Darrel)的论点是客户端将盲目地跟随服务器的超链接响应,那么没有理由不包括客户ID,同样可以反过来争辩 - 包括客户ID是不必要的,因为客户端将盲目地遵循超链接。在这种情况下,我会说为什么要费心制定标准呢? - isick
1
通过在URI中包含ID,您可以使用与您的订单不同的URI来处理我的订单。因此,缓存可以使用URI作为缓存键。 - Darrel Miller
1
@isick 在RESTful系统中,URI设计没有标准。 URI应对客户端完全不透明。 URI设计仅是服务器端路由的一种便利。 - Darrel Miller
1
@DarrelMiller 我理解你的意思,但我认为你探讨的层次过深。Alex 请求的资源是“订单”。该资源的范围受到客户 ID 的限制,但是您的客户 ID 并不能识别您请求的资源(即订单)。因此,在这种情况下,无论是违反 RESTful 资源标识的要求还是遵守该要求都不成立。 - isick
1
@isick,你对资源的概念想得太多了。唯一的URI标识了一个资源。“orders”不是一个资源,也不是“customer”。 “http://example.org/customer/1234/orders”标识了一个资源,“http://example.org/customer/5678/orders”标识了另一个资源。资源的概念更接近于对象实例而不是类。 - Darrel Miller

13

我认为第二个实现方案(包括客户ID)只有在允许客户检索其他客户的订单数据时才是必需的。换句话说,如果客户4321将能够查看客户1234的订单历史记录,则绝对需要发送该信息。

我的猜测是他们不能这样做。这意味着如果您包含该信息,您很可能会忽略授权代码而选择它。

如果是这种情况,那么我建议不要发送它。

如果可以让您感到更好,LinkedIn的REST API也反映了这种行为。任何用户都可以请求任何其他用户的个人资料 但是 如果未提供id,则假定当前用户正在请求自己的个人资料。 点击此处阅读更多

话虽如此,只要您理解它并记录它,无论哪种方式都应该没问题。


3
REST并没有明确规定资源的URI中必须显式地包含一个id。URI本身可以被视为标识符,其中包含的任何id都可以作为进一步的范围信息。 - Marjan Venema

8

两者都不是RESTful的。在RESTful应用程序中,URI的语义应该与客户端无关。

REST是基于Web本身的架构风格。想一想,当你访问Stack Overflow时,你唯一关心的URI是 stackoverflow.com。一旦你登录成功,你会被重定向到你的主页,此时你的主页的URI是无关紧要的。现在我在我的菜单栏上有一个链接,指向我的主页,你也有一个相同的链接,指向你的主页,在我们点击它时,我们不关心URI是什么,对吧?在我们的主页上,我们看到问题和答案,我们也不关心它们的URIs,只关心它们的标题和描述。

现在,让我们反过来想。REST是Web的架构风格,如果Web就像你认为的REST那样,当你加入stackoverflow.com时,你不会得到一个好的链接,而只会得到一个文本,上面写着“你的ID是131809,请将URI模式/users/(id)替换为这个数字,将其粘贴到地址栏中,然后你就可以进入你的主页了”,你会想,“为什么这些白痴不给个链接呢?”

听起来很荒谬,但这就是大多数人认为REST是什么。 REST API必须受到超文本驱动。

这意味着,与其担心用户的订单应该在/customer/(customer_id)/orders,还是只在/customer/orders,甚至只在/orders,并依赖于他的凭据,迫使客户端了解所有这些URI模式,你应该在API入口点给他一个根文档,考虑到他的凭据,并以标准的标题和描述给出URIs,就像任何网站在你登录时所做的那样!

因此,假设你使用JSON,你的API是myapi.com。那应该是客户端知道的唯一URI。当他用正确的授权头部发送GET请求到该根文档时,他应该得到一个JSON文档,如下所示:

{"orders": "http://myapi.com/customer/123456/orders"}

显然,他不需要知道“orders”价值是多少,只需要知道它是一个有效的URI,可以引导他到另一个资源,包含他的订单集合。该URI可以是任何东西,语义并不重要。它可能是你朋友建议的:

{"orders": "http://myapi.com/orders"}

或者其他任何可以随时更改的内容:

{"orders": "http://myapi.com/this/is/a/meaningless/uri"}

当然,你应该付出一些努力来获得清晰而有意义的URI,但这并不会使你的API更或者更少RESTful。在这个问题上的关键是,让你的客户端如何获取URI,如果你的客户端在文档中阅读URI模式而不是在其他资源中获得链接,则你的API就不是RESTful的。简单来说,这就是区别。


最后一种情况更像是OPTIONS请求的内容,而不是GET请求。 - felixfbecker

1
如果您在其他地方使用了/customer/[id]模式中的id,那么仅因为您认为现在没有其他人会看到订单,就省略它对于订单来说是奇怪和不一致的。如果客户只能看到自己的订单,为什么不直接使用/orders呢?这样,至少可以避免这种不一致性。但我更喜欢显式添加id。如果将来有客户支持人员想要查看订单,您将不得不更改URL或仅为此目的创建一个新的URL。我建议严格将资源的标识与访问权限考虑分开。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接