支持 REST 的多个可能的标识符

对于我正在开发的站点,我们正在为一种类型的资源改进 URL ——具体来说,就是从数字 ID 转向唯一的描述性字符串。一个类似的例子是从用数字数据库 ID 标识用户切换到用用户名标识用户(不是我们的特定情况,而是类似的)。因此,访问用户信息的 URL 过去看起来是这样的:

/users/48573

现在看起来

/users/thisisausername.

唯一的问题是,我们仍然需要能够以某种方式通过数字 ID 为 API 的遗留使用者获取它们。我们不需要 REST URL 本身来重定向(例如,/users/48573不应该重定向到 /users/thisisausername) ,我们只需要一个方法来使用旧的标识符获得正确的数据。这个解决方案应该提供另一种方法,通过 ID 访问用户信息(方便地包括新的标识符,用户名) ,或者仅通过 ID 访问用户名。一些可能的解决办法可能是:

  • 使用节点指定一些替代的标识方法,例如 /users/byid/48573
  • 使用查询参数指定一些替代的识别方法,例如 /users/48573?fetchby=id/users/48573?byid=true
  • 将用户名按 ID 处理为另一种资源,例如 /identifiers/username/48573

哪一个(如果有的话)最接近正确的 REST? 你将如何处理这个问题?

27242 次浏览

你的第一个选择可能是最好的。

以身份证搜寻使用者:

/users/id/48573

通过简称搜索用户:

/users/name/thisisausername

如果他们省略了 path 参数,您总是可以默认使用新的短用户名格式。

我见过的另一种选择是使用查询参数,如下所示:

/users?id=48573
/users?name=thisisausername

我认为第一个看起来更干净,更易读。

我会考虑用一个可选的后缀来限定字符串:

/users/48573/id


/users/48573/name

如果收到一个没有后缀的字符串:

/users/48573

然后检查字符串,看它是 ID 还是 Name。

如果你只得到一个有效的 ID,而不是一个名字,那么它是一个检索 ID 等价于:

/users/48573/id

如果只返回一个名称,那么它是一个按名称等效的检索:

/users/48573/name

如果您可以通过 ID 或 Name 检索该值,那么您将返回一个300响应错误,并向客户端返回指向这两种可能性的链接:

/users/48573/id


/users/48573/name

遗留使用者继续“按原样”工作,除了偶尔出现重复的 ID/名称对,在这种情况下,它们会收到新的300响应错误。

如果这是一个问题,你的 API 是不 RESTful 的。对 引用罗伊 · 菲尔丁:

REST API 不能定义固定的资源名称或层次结构(客户端和服务器的明显耦合)。服务器必须能够自由地控制自己的名称空间。相反,通过在媒体类型和链接关系中定义这些指令,允许服务器指示客户端如何构造适当的 URI,比如在 HTML 表单和 URI 模板中。[此处的失败意味着客户端由于带外信息而假设资源结构,比如特定于域的标准,这是面向数据的等价于 RPC 的功能耦合]。

除了初始 URI (书签)和一组适合目标受众的标准化媒体类型(即,任何可能使用该 API 的客户端都应该能够理解)之外,不应该预先知道 REST API 的输入。从那时起,所有应用程序状态转换都必须由客户机选择服务器提供的选项驱动,这些选项存在于接收到的表示中,或者由用户对这些表示的操作所暗示。转换可能由客户机对媒体类型和资源通信机制的了解决定(或受到限制) ,这两者都可以在运行中得到改进(例如,按需编码)。[这里的失败意味着带外信息正在驱动交互,而不是超文本。]

我认为添加路径段/前缀是最好的答案。由于这些是唯一的辅助键,因此这与搜索(返回一组条目)不同,所以使用查询参数(没有缓存)似乎不是最佳选择。

就个人而言,我打算使用一个由“ =”分隔的路径段前缀,比如“ name =”或“ email =”:

user/123456
user/name=john.doe
user/email=john.doe@john.doe

这在功能上等同于添加一个路径段(例如“ user/name/john.doe”) ,但我感觉它更贴近概念模型。当然,这是一个无关紧要的细节,因为 RESTful API 无论如何都不应该指定固定的 URI 结构。

不使用查询参数还允许自然访问子资源:

user/name=john.doe/inbox/df87bhJXrg63

像 Java 的 JAX-RS 这样的框架支持使用任何你想要的分隔符:

@GET
@Path("user/{id}")
User getUser(@PathParam("id") UUID id);


@GET
@Path("user/name={name}")
User getUserByName(@PathParam("name") String name);


@GET
@Path("user/email={email}")
User getUserByEmail(@PathParam("email") String email);

这是一个相当老的问题,但我也有同样的问题,并最终找到了解决办法: 在路径参数中使用正则表达式。

我是这样编写用例的

@GET
@Path("/{id : \\d+}")
@Produces(APPLICATION_JSON)
public Response getById(@PathParam("id") long id) {
<<your code>>
}


@GET
@Path("/{name}")
@Produces(APPLICATION_JSON)
public Response getByName(@PathParam("name") String name) {
<<your code>>
}