RESTful API - URI 结构建议

Posted

技术标签:

【中文标题】RESTful API - URI 结构建议【英文标题】:RESTful API - URI Structure Advice 【发布时间】:2014-09-03 06:26:56 【问题描述】:

我的 REST API URL 结构类似于:

/api/contacts                 GET          Returns an array of contacts
/api/contacts/:id             GET          Returns the contact with id of :id
/api/contacts                 POST         Adds a new contact and return it with an id added
/api/contacts/:id             PUT          Updates the contact with id of :id
/api/contacts/:id             PATCH        Partially updates the contact with id of :id
/api/contacts/:id             DELETE       Deletes the contact with id of :id

我的问题是:

/api/contacts/:id             GET

假设除了通过 ID 获取联系人之外,我还想通过唯一别名获取它。

如果我希望能够通过 ID 或别名获取联系人,那么 URI 结构应该是什么?

【问题讨论】:

【参考方案1】:

如果您的别名不是数字,我建议您使用相同的 URI 结构并确定它是您的 ID 还是别名。就像 Facebook 使用用户名和 user_id 一样。 facebook.com/user_id 或 facebook.com/username。

另一种方法是让客户端使用带有一些额外 GET 参数的 GET /contacts 作为过滤器,首先搜索联系人,然后从该响应中查找 ID。

我认为最后一个选项是使用 GET /contacts/alias/:alias 之类的结构。但这有点暗示别名是联系人的子资源。

【讨论】:

好答案。但是,如果无法以这种方式将 ID 与别名区分开来,您会建议什么? 我没有看到任何其他不会破坏 REST 结构的方法,除了可能执行 GET /contacts/alias/:alias 之类的操作。但这有点暗示别名是联系人的子资源 “但这有点暗示别名是联系人的子资源。” - 是的,但谁需要知道呢?我的意思是只有 API 开发人员需要这些信息来为传入消息构建链接和路由器。其他人只会使用链接并通过检查链接关系来决定导航...所以在我看来,IRI 结构是无关紧要的,IRI 不应该成为您文档的一部分。如果他们是 ppl 可以编写容易破坏的客户端,这些客户端正在自己构建 IRI...【参考方案2】:

IRI 的路径和查询部分由您决定。路径用于分层数据,如api/version/module/collection/item/property,查询用于非分层数据,如?display-fields="id,name,etc..."?search="brown teddy bear"&offset=125&count=25等...

您必须牢记的是,您使用的是资源而不是操作。所以 IRI 是资源标识符,如 DELETE /something,而不是操作标识符,如 POST /something/delete。您不必遵循 IRI 的任何结构,例如,您可以简单地使用 POST /dashuif328rgfiwa。服务器会理解,但是为这种 IRI 编写路由器会困难得多,这就是我们使用好的 IRI 的原因。

一个 IRI 始终只属于一个资源,这一点很重要。所以你不能用GET /cats/123 读取cat 属性,用PUT /cats/123 写入dog 属性。人们通常不明白,一个资源可以有多个 IRI,例如/cats/123/cats/name:kitty/users/123/cats/kittycats/123?fields="id,name" 等...可以属于同一个资源。或者,如果您想为某物(活猫,而不是描述它的文档)提供 IRI,那么您可以使用 /cats/123#thing/users/123#kitty 等...您通常在 RDF 文档中这样做。

如果我想获取联系人,URI 结构应该是什么 通过 ID 或别名?

它可以是/api/contacts/name:name,例如/api/contacts/name:John,因为它显然是分层的。或者您可以检查参数是否在/api/contacts/param 中包含数字或字符串。

您也可以使用查询,但我不建议这样做。例如,以下 IRI 可以有 2 个不同的含义:/api/contacts?name="John"。您想要列出姓名为 John 的每个联系人,或者您想要一个确切的联系人。因此,您必须在服务器端应用程序的路由器中对此类请求做出一些约定。

【讨论】:

谢谢,/api/contacts/name:name 看起来非常适合我想要的。您能否指出任何使用类似方法的 API? ___ 另外,您对“单个资源可以有多个 IRI”的评论,这是一个好习惯吗?我的意思是单个资源具有多个 URI 是完全自然的吗?您提供的示例很有道理,我只是想知道这种方法是否不违反 REST API 的最佳实践。 只是对不同IRI的解释,例如/users/users?page=6/users?search="Susanne"。现在这些是 3 种不同的 IRI。那么我们这里有 3 种不同的资源还是只有一个?你可以说,/users 是一个集合资源,/users?page=6 是一个页面资源,/users?search="Susanne" 是一个 mapreduced 集合资源,但这对我来说没有任何意义。从我的角度来看,/users 是一个集合资源,所有的 IRI 都属于它。分页和搜索是查询的典型用法。 通过分页,您可以使用范围标题作为替代方案。所以/usersContent-Range: 26-50/1234。这应该返回与/users?offset=25&count=25 几乎相同的返回值,除了下一个和上一个链接 ofc。现在不同的页面是否属于不同的资源?我不这么认为。通过范围标头,它们属于同一资源......范围标头和查询分页参数都是 REST 最佳实践。例如通过分页:youtu.be/5WXYw4J4QOU?t=1h6s,但是你可以找到很多关于如何通过 REST 分页的文章。 ***.com/questions/1929347/…【参考方案3】:

当您尝试使用别名解析资源时,我会考虑添加“搜索”资源:

GET /api/contacts/:id

GET /api/contacts?alias=:alias

GET /api/contacts/search?q=:alias

【讨论】:

【参考方案4】:

首先,URL 中的“ID”不必是您的数据库生成的数字 ID。您可以在 URL 中使用任何数据(包括别名),只要它是唯一的。当然,如果您在任何地方都使用数字 ID,那么在您的联系人 API 中执行相同操作会更加一致。但是您可以选择使用别名而不是数字 ID(只要它们始终是唯一的)。

另一种方法是,正如Stromgren 建议的那样,在 URL 中同时允许数字 ID 和别名:

/api/contacts/123
/api/contacts/foobar

但是,如果别名可以是数字,这显然会导致问题,因为那样您将无法区分 ID 和(数字)别名。

最后但同样重要的是,您可以实现一种过滤完整集合的方法,正如shlomi33 已经建议的那样。我不会引入 search 资源,因为这不是真正的 RESTful,所以我会选择其他解决方案:

/api/contacts?alias=foobar

这应该返回所有以foobar 为别名的联系人。由于别名应该是唯一的,这将返回 1 或 0 个结果。

【讨论】:

以上是关于RESTful API - URI 结构建议的主要内容,如果未能解决你的问题,请参考以下文章

RESTful API URI 设计

API 端点是不是可以根据用户凭据 RESTful 和良好的 URI 设计来区分要返回的资源?

架构师之路 — API 经济 — RESTful API 设计规范原则

RESTful API 设计 - 使用资源 URI 与 ID

架构师之路 — API 经济 — RESTful API 设计规范原则

RESTful URL 设计:公共与私有 API、分层 API 设计模式、URI 与 URL 设计?