【问题标题】:How to design rest endpoint URLs for sub resources如何为子资源设计休息端点 URL
【发布时间】:2022-01-21 17:03:42
【问题描述】:

假设我们有两个实体。 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;
}

此时有两个控制器。 UserControllerOrderControllerUserController 映射在/api/v1/users/ 下,OrderController 映射在/api/v1/orders/ 下。

  • 获取用户报价列表的端点应该是什么样的?
  • 应该在同一个控制器中吗?我确实有按功能项目结构。
  • 如何修改或删除特定UserOffer?如果我们有/api/v1/users/{username}/offers/{offerId} 来删除或更新报价,我们是否还应该有/api/v1/offers/{offerId} 端点来允许编辑或删除报价?也许值得让管理员拥有它?

【问题讨论】:

  • 我认为这可能会对restfulapi.net/resource-naming 有所帮助,至于项目结构取决于您自己,我更喜欢将独特的资源放在他们自己的控制器中。无论您是需要通过用户还是直接修改报价,都应由消费者的要求决定。

标签: java spring hibernate rest


【解决方案1】:

我在创建端点时使用的一般经验法则是:

  • 网址需要简洁易懂
  • 它们应尽可能简短,同时仍能提供丰富的信息。
  • 尝试以允许您在合理数量内重复使用它的方式构建它
  • 考虑用户体验(无论是从浏览器还是移动应用等调用)

我不确定是否有明确的书面规则应该如何构建 URL。 在您的具体情况下,我会使用 /users/{username}/offers/{offerId} 仅当这是您公开和使用优惠的唯一地方,因为您按功能分隔代码。
如果您对优惠有任何进一步的逻辑和/或有具有此类逻辑的 bean,我将为优惠创建一个单独的控制器,该控制器将位于 /offers 下。 关于你的最后一个问题。这在很大程度上取决于您要达到的目标。如果您需要能够更新/删除/创建优惠,那么拥有这样的功能是有意义的。即使它只是由管理员使用。您可以限制对端点的访问。如何做到这一点取决于您授权用户的方式以及您对他们的信息。大多数人都使用角色。
如果您决定拥有完整的 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。

【讨论】:

    【解决方案2】:

    我同意它应该在同一个“用户控制器”中,这是有道理的,因为优惠属于用户,所以有一个类似的端点:

    @GetMapping("{user}/offers")
        public Set<OfferDTO> getOffers(@PathVariable("user") String user) {
        return offerService.getOffers(user);
    }
    

    如果您想将元数据显示在列表中,您可以定义特殊的 DTO 以从报价中获取元数据,并且您可以将它们作为列表显示给您的用户。

    您可以设置一个类似的端点来更新报价,它可以是一个 POST 端点,一个用于删除的 DELETE 端点。您可能想考虑如果用户在您删除商品时正在查看商品会发生什么,例如在后台线程中创建用于删除商品的异步任务并更新 UI 以通知用户该商品已被删除。

    Spring 有一些非常好的安全注释(检查thisthis),您可以为管理员端点编写自己的注释:

    @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);
      }
    

    当然,服务层的实现非常重要,但它可以像调用你的存储库并在那里执行操作一样简单:)

    【讨论】:

      猜你喜欢
      • 2014-02-02
      • 2019-08-25
      • 2016-05-01
      • 2018-09-12
      • 1970-01-01
      • 2017-10-09
      • 1970-01-01
      • 1970-01-01
      • 2017-11-01
      相关资源
      最近更新 更多