在 RESTful Web 服务中按 ID 和用户名查找

Posted

技术标签:

【中文标题】在 RESTful Web 服务中按 ID 和用户名查找【英文标题】:FInd by ID and Username in RESTful Web Services 【发布时间】:2020-01-25 13:27:04 【问题描述】:

我对单例资源的最佳偏好感到困惑。我希望端点的用户 ID 为:

/users/id

但我也想要username 作为参数:

/users/username

我正在使用 Spring MVC(通过 Spring Boot),它说 2 个端点存在冲突。所以我决定首先为用户检索单例资源。但是为了让客户端仍然使用用户名作为参数,我添加了用户名作为用户集合资源的查询参数:

/users?username=<username>

在我的repositoryservice 层中,返回是Optional<User>,即它是emptyone user 结果。但是在controller 中,我将其包装在list 中,可以为空也可以为1,以使其与/users 作为列表的返回一致。

这样的设计合理吗?还是有更好的设计?谢谢。

【问题讨论】:

如果两个查询在同一个数据库上,你可以使用'UNION'来加入查询, 【参考方案1】:

基本上,您有两种选择:

您要么有两个单独的端点,例如,/users/byId/id/users/byName/username,每个端点都映射到不同的控制器方法
class UserController 
    @GetMapping("/byId/id")
    Optional<User> findById(@PathVariable("id") String id)
       ...
    

    @GetMapping("/byName/username")
    Optional<User> findByUsername(@PathVariable("username") String username)
       ...
    

或者一个端点/users/id-or-name,然后你进行联合查询,所以:findAllByIdOrName()

我倾向于说在单独的端点中执行它更清洁、更高效。


编辑

RESTful 约定意味着您有 2 个端点:

    @GetMapping("/id")
    Optional<User> findById(@PathVariable("id") String id)
       ...
    

    @GetMapping("/")
    List<User> findUsers(@RequestParam("username") String username)
       // if username is not empty, filter users
       // we could also filter with other user properties according to specs
       ...
    

除此之外的任何东西都已经偏离了惯例。

【讨论】:

第一个选项不遵循 RESTful URI 约定,所以我不会考虑它。对于第二个,那么我的@PathVariable 是什么? 谢谢。我现在才读。实际上,您的第二个选择是我考虑的那个。那是我在上面发布的确切选项。很高兴,我推动了它。【参考方案2】:

我遇到了同样的问题。两个端点发生冲突,所以事件 /user/id 不想工作,所以我确实给 findbyusername 一个不同的端点,例如:

@GetMapping("/user/username/username") 

【讨论】:

【参考方案3】:

您应该尽量防止客户端使用字符串操作来组装 URI,因为这样客户端必须对服务器内部有深入的了解。

不过,优雅的 URI 方案通常是合理资源结构的标志。如果 URI 设计得很好,那么对于想要使用 REST API 的开发人员来说,这会变得更轻松;客户会喜欢易于记忆的 URI。手动编辑 URI 的能力(例如,通过在“/”之后截断其中的一些)是一个明显的优势。

如您所见:URI 标识资源。资源是“事物”,名词,而不是动作或动词!如果您进行了腐蚀性资源设计,匹配的名称通常会自己出现。但是,我想在这里举一个例子,由于它的结构或名称,它可能表明存在问题。

http://example.com/customers/create?name=XYZ

这里有点混乱: 动词“create”在 URI 中没有位置,并且该结构表明该操作由 URI 标识,而不是由 HTTP 动词标识。

您的 URI 应该包含名词。对于按预期标识资源的 URI,您还可以轻松想象不同的 HTTP 方法是如何工作的:PUT 更新 DELETE 删除,GET 获取信息。以下是上述 URI 看起来更好的示例:

发布http://example.com/customers/

透明和可理解的 URI 设计的一个明显规则是将层次关系映射到 URI 的路径元素结构。

示例: http://example.com/organization/it/support/networks

因此,如果您是服务器的开发人员并因此是 URI 的设计者,您应该确保 http://example.com/organization/it/support 上的 GET 也返回有用的结果。在您的客户中,无论如何您都不应该做出这样的假设! 这从根本上有助于分布式系统的稳定性,也称为robustness principle 或 Postel 定律。

对于过滤器的 URI 设计,它们有不同的可能性。最明显的是使用查询参数。对于 /customers 下您要为来自德国的所有客户过滤的客户列表,示例 URI 可能如下所示:

http://example.com/customers?country=Germany 多个查询组件可以用“&”链接:

http://example.com/customers?country=Germany&year=2020

在这里,他们也通过这样的 URI 设计满足了用户的期望。如果查询字符串被省略 (?country=Germany&year=2020),用户将被带到未过滤的所有客户列表中。

拥有稳定的 URI 很重要,酷炫的 URI 不会改变 (Tim Berners-Lee)。

回答你的问题:

设计不应被高估;它应该是有用的,但并不完美(无论如何这是不可能实现的)。

【讨论】:

【参考方案4】:

您不能对一个特定映射使用相同的端点名称。在这里,您对两个 @GetMapping 使用相同的端点名称。如果你想做这件事,你应该使用参数。

class UserController 
   @GetMapping("/users")
   Optional<User> findById(@RequestParam(required = false) String id, @RequestParam(required = false) String username)
      if (!username.equals("")) 
         // Response using a username
      
      if (!id.equals("")) 
         // Response using an id
      
   

【讨论】:

我想要遵循 RESTful URI 约定的更简洁的 URI。所以id 作为查询字符串是不可接受的。

以上是关于在 RESTful Web 服务中按 ID 和用户名查找的主要内容,如果未能解决你的问题,请参考以下文章

使用 OAuth2 实现 Spring RESTful Web 服务 - 将 id 令牌保存为 id 会话

如何从 android 调用 RESTful Web 服务?

使用 Jersey 和 Apache Tomcat 构建 RESTful Web 服务

如何从Android调用RESTful Web服务?

在 Restful Web 服务中如何以编程方式获取用户名或密码(基本身份验证)

RESTful Web 服务认证