如何为子资源设计 rest 端点 URL
How to design rest endpoint URLs for sub resources
假设我们有两个实体。 User
实体可以有很多要约,而 Offer
必须有一个 User
。
class User {
...
@OneToMany(mappedBy = "user", orphanRemoval = true)
private Set<Offer> offers;
}
class Offer {
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fk_user")
private User user;
}
此时有两个控制器。 UserController
和 OrderController
。 UserController
映射在 /api/v1/users/
下,OrderController
映射在 /api/v1/orders/
下。
- 获取用户的报价列表的端点应该是什么样的?
- 是否应该在同一个控制器中?我确实有 按功能 项目结构。
- 如何修改或删除特定
User
的 Offer
?如果我们有 /api/v1/users/{username}/offers/{offerId}
来删除或更新报价,我们是否也应该有 /api/v1/offers/{offerId}
允许编辑或删除报价的端点?也许管理员值得拥有它?
我同意它应该在同一个 'UserController' 中,这是有道理的,因为优惠属于用户,所以有一个像这样的端点:
@GetMapping("{user}/offers")
public Set<OfferDTO> getOffers(@PathVariable("user") String user) {
return offerService.getOffers(user);
}
您可以定义特殊的 DTO 以从报价中获取 meta-data,例如,如果您想将它们显示在列表中,并且您可以将它们作为列表显示给您的用户。
您可以设置一个类似的端点来更新报价,它可以是一个 POST 端点,以及一个用于删除的 DELETE 端点。您可能想考虑如果用户在您删除报价时正在查看报价会发生什么,例如在后台线程中执行异步任务以删除报价并更新 UI 以通知用户报价已删除。
Spring 有一些非常好的安全注释(检查 this and this),您可以为管理员端点编写自己的注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAuthority('" + ROLE_ADMIN + "')")
public @interface IsAdmin {}
然后像这样注释你的方法:
@DeleteMapping("/{user}/{offer}/delete")
@IsReceiverAdmin
public void delete(@PathVariable("user") String user, @PathVariable("offer") String offer){
return offerService.delete(user, offer);
}
当然,服务层的实现非常重要,但它可以像调用您的存储库并在那里执行操作一样简单:)
我在创建端点时使用的一般经验法则是:
- URL需要简洁易懂
- 它们应该尽可能短,但仍能提供信息。
- 尝试以允许您在合理数量内重复使用它的方式构建它
- 考虑用户体验(无论是从浏览器还是移动应用等调用)
我不确定是否有明确的书面规则应该如何构建 URL。
在您的具体情况下,只有当这是您公开和使用优惠的唯一地方时,我才会使用 /users/{username}/offers/{offerId}
,因为您是按功能分隔代码的。
如果您有关于优惠的任何进一步逻辑 and/or 有具有此类逻辑的 bean,我将为优惠创建一个单独的控制器,该控制器将位于 /offers
下。
关于你的最后一个问题。这在很大程度上取决于您要实现的目标。如果您需要能够 update/delete/create 提供,那么拥有这样的功能是有意义的。即使它仅由管理员使用。您可以限制对端点的访问。如何做到这一点取决于您授权用户的方式以及您拥有的关于他们的信息。大多数人使用角色。
如果您决定拥有完整的 CRUD 功能,我建议您使用带有请求方法组合的单一路径。
我个人会创建以下内容:
@RestController
@RequestMapping(value = "/users")
class UserController {
@GetMapping("{userId}/offers")
public Set<Offer> getAllOffers(@PathVariable("userId") String userId){
...
}
@GetMapping("{userId}/offers")
public Offer getOffer(@PathVariable("userId") String userId, @RequestParam(required = true) String offerId){
...
}
@PutMapping("{userId}/offers")
public Offer createOffer(@PathVariable("userId") String userId, @RequestBody Offer offer){
...
}
@PostMapping("{userId}/offers")
public Offer updateOffer(@PathVariable("userId") String userId, @RequestBody Offer offer){
...
}
@DeleteMapping("{userId}/offers")
public void deleteOffer(@PathVariable("userId") String userId, @RequestParam(required = true) String offerId){
...
}
}
在这种情况下,我认为用于创建和更新的 POST/PUT 会更清晰,因为不会有重复的信息。准确地说是 ID。
假设我们有两个实体。 User
实体可以有很多要约,而 Offer
必须有一个 User
。
class User {
...
@OneToMany(mappedBy = "user", orphanRemoval = true)
private Set<Offer> offers;
}
class Offer {
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fk_user")
private User user;
}
此时有两个控制器。 UserController
和 OrderController
。 UserController
映射在 /api/v1/users/
下,OrderController
映射在 /api/v1/orders/
下。
- 获取用户的报价列表的端点应该是什么样的?
- 是否应该在同一个控制器中?我确实有 按功能 项目结构。
- 如何修改或删除特定
User
的Offer
?如果我们有/api/v1/users/{username}/offers/{offerId}
来删除或更新报价,我们是否也应该有/api/v1/offers/{offerId}
允许编辑或删除报价的端点?也许管理员值得拥有它?
我同意它应该在同一个 'UserController' 中,这是有道理的,因为优惠属于用户,所以有一个像这样的端点:
@GetMapping("{user}/offers")
public Set<OfferDTO> getOffers(@PathVariable("user") String user) {
return offerService.getOffers(user);
}
您可以定义特殊的 DTO 以从报价中获取 meta-data,例如,如果您想将它们显示在列表中,并且您可以将它们作为列表显示给您的用户。
您可以设置一个类似的端点来更新报价,它可以是一个 POST 端点,以及一个用于删除的 DELETE 端点。您可能想考虑如果用户在您删除报价时正在查看报价会发生什么,例如在后台线程中执行异步任务以删除报价并更新 UI 以通知用户报价已删除。
Spring 有一些非常好的安全注释(检查 this and this),您可以为管理员端点编写自己的注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAuthority('" + ROLE_ADMIN + "')")
public @interface IsAdmin {}
然后像这样注释你的方法:
@DeleteMapping("/{user}/{offer}/delete")
@IsReceiverAdmin
public void delete(@PathVariable("user") String user, @PathVariable("offer") String offer){
return offerService.delete(user, offer);
}
当然,服务层的实现非常重要,但它可以像调用您的存储库并在那里执行操作一样简单:)
我在创建端点时使用的一般经验法则是:
- URL需要简洁易懂
- 它们应该尽可能短,但仍能提供信息。
- 尝试以允许您在合理数量内重复使用它的方式构建它
- 考虑用户体验(无论是从浏览器还是移动应用等调用)
我不确定是否有明确的书面规则应该如何构建 URL。
在您的具体情况下,只有当这是您公开和使用优惠的唯一地方时,我才会使用 /users/{username}/offers/{offerId}
,因为您是按功能分隔代码的。
如果您有关于优惠的任何进一步逻辑 and/or 有具有此类逻辑的 bean,我将为优惠创建一个单独的控制器,该控制器将位于 /offers
下。
关于你的最后一个问题。这在很大程度上取决于您要实现的目标。如果您需要能够 update/delete/create 提供,那么拥有这样的功能是有意义的。即使它仅由管理员使用。您可以限制对端点的访问。如何做到这一点取决于您授权用户的方式以及您拥有的关于他们的信息。大多数人使用角色。
如果您决定拥有完整的 CRUD 功能,我建议您使用带有请求方法组合的单一路径。
我个人会创建以下内容:
@RestController
@RequestMapping(value = "/users")
class UserController {
@GetMapping("{userId}/offers")
public Set<Offer> getAllOffers(@PathVariable("userId") String userId){
...
}
@GetMapping("{userId}/offers")
public Offer getOffer(@PathVariable("userId") String userId, @RequestParam(required = true) String offerId){
...
}
@PutMapping("{userId}/offers")
public Offer createOffer(@PathVariable("userId") String userId, @RequestBody Offer offer){
...
}
@PostMapping("{userId}/offers")
public Offer updateOffer(@PathVariable("userId") String userId, @RequestBody Offer offer){
...
}
@DeleteMapping("{userId}/offers")
public void deleteOffer(@PathVariable("userId") String userId, @RequestParam(required = true) String offerId){
...
}
}
在这种情况下,我认为用于创建和更新的 POST/PUT 会更清晰,因为不会有重复的信息。准确地说是 ID。