在 RESTful Web 服务中按 ID 和用户名查找
FInd by ID and Username in RESTful Web Services
我对单例资源的最佳偏好感到困惑。我想要具有用户 ID 的端点:
/users/{id}
但是我也想要username
作为参数:
/users/{username}
我正在使用 Spring MVC(通过 Spring 引导),它说 2 个端点存在冲突。所以我决定首先为用户检索单例资源。但是为了客户端仍然使用用户名作为参数,我为用户的集合资源添加了用户名作为查询参数:
/users?username=<username>
在我的 repository
和 service
层中,return 是 Optional<User>
,即它是 empty
或 one user
结果。但是在 controller
中,我将其包装在一个 list
中,要么为空,要么为一个,以使其与 /users
的 return as list.
一致。
这样的设计合理吗?还是有更好的设计?谢谢。
您应该尝试阻止客户端使用字符串操作组装 URI,因为为此,客户端必须对服务器内部结构有深入的了解。
然而,优雅的URI方案往往是合理资源结构的标志。当 URI 设计得很好时,对于想要使用 REST API 的开发人员来说,生活会更轻松;客户会喜欢容易记住的 URI。手动编辑 URI 的能力(例如,通过在“/”之后截断其中的一些 URI)是一个明显的优势。
如您所见:URI 标识资源。资源是“事物”,名词,不是动作或动词!如果你做过腐蚀资源设计,匹配的名字通常会自己出现。但是,我想在这里举一个例子,可能是因为它的结构或名称的问题的指示。
http://example.com/customers/create?name=XYZ
这里有点混乱:
动词“创建”在 URI 中没有位置,结构表明该操作由 URI 标识,而不是由 HTTP 动词标识。
您的 URI 应包含名词。对于按预期标识资源的 URI,您还可以轻松想象不同的 HTTP 方法如何工作:PUT updates a DELETE deletes, and a GET 获取信息。这是上面的 URI 看起来更好的示例:
POST http://example.com/customers/
透明且易于理解的 URI 设计的一个明显规则是将层次关系映射到 URI 的路径元素结构。
示例:
http://example.com/organization/it/support/networks
因此,如果您是服务器的开发者,因此也是 URI 的设计者,您应该确保 http://example.com/organization/it/support 上的 GET 也是 returns 有用的结果。在您的客户中,您无论如何都不应该做出这样的假设!
这从根本上有助于分布式系统的稳定性,也称为 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)。
回答你的问题:
设计不应被高估;它应该是有用的,但并不完美(这是不可能实现的)。
基本上,您有两个选择:
- 您有两个单独的端点,例如,
/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
...
}
除此之外的任何其他内容都已经偏离了惯例。
不能对一个特定映射使用相同的端点名称。在这里,您对两个 @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
}
}
}
我遇到了同样的问题。这两个端点有冲突,所以事件 /user/{id} 不想工作,所以这就是为什么我给 findbyusername 一个不同的端点,例如:
@GetMapping("/user/username/{username}")
我对单例资源的最佳偏好感到困惑。我想要具有用户 ID 的端点:
/users/{id}
但是我也想要username
作为参数:
/users/{username}
我正在使用 Spring MVC(通过 Spring 引导),它说 2 个端点存在冲突。所以我决定首先为用户检索单例资源。但是为了客户端仍然使用用户名作为参数,我为用户的集合资源添加了用户名作为查询参数:
/users?username=<username>
在我的 repository
和 service
层中,return 是 Optional<User>
,即它是 empty
或 one user
结果。但是在 controller
中,我将其包装在一个 list
中,要么为空,要么为一个,以使其与 /users
的 return as list.
这样的设计合理吗?还是有更好的设计?谢谢。
您应该尝试阻止客户端使用字符串操作组装 URI,因为为此,客户端必须对服务器内部结构有深入的了解。
然而,优雅的URI方案往往是合理资源结构的标志。当 URI 设计得很好时,对于想要使用 REST API 的开发人员来说,生活会更轻松;客户会喜欢容易记住的 URI。手动编辑 URI 的能力(例如,通过在“/”之后截断其中的一些 URI)是一个明显的优势。
如您所见:URI 标识资源。资源是“事物”,名词,不是动作或动词!如果你做过腐蚀资源设计,匹配的名字通常会自己出现。但是,我想在这里举一个例子,可能是因为它的结构或名称的问题的指示。
http://example.com/customers/create?name=XYZ
这里有点混乱: 动词“创建”在 URI 中没有位置,结构表明该操作由 URI 标识,而不是由 HTTP 动词标识。
您的 URI 应包含名词。对于按预期标识资源的 URI,您还可以轻松想象不同的 HTTP 方法如何工作:PUT updates a DELETE deletes, and a GET 获取信息。这是上面的 URI 看起来更好的示例:
POST http://example.com/customers/
透明且易于理解的 URI 设计的一个明显规则是将层次关系映射到 URI 的路径元素结构。
示例: http://example.com/organization/it/support/networks
因此,如果您是服务器的开发者,因此也是 URI 的设计者,您应该确保 http://example.com/organization/it/support 上的 GET 也是 returns 有用的结果。在您的客户中,您无论如何都不应该做出这样的假设! 这从根本上有助于分布式系统的稳定性,也称为 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)。
回答你的问题:
设计不应被高估;它应该是有用的,但并不完美(这是不可能实现的)。
基本上,您有两个选择:
- 您有两个单独的端点,例如,
/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
...
}
除此之外的任何其他内容都已经偏离了惯例。
不能对一个特定映射使用相同的端点名称。在这里,您对两个 @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
}
}
}
我遇到了同样的问题。这两个端点有冲突,所以事件 /user/{id} 不想工作,所以这就是为什么我给 findbyusername 一个不同的端点,例如:
@GetMapping("/user/username/{username}")