休息资源分离

Rest Resources Separation

我一直在尝试使用 Spring 引导启动 REST api,但我对资源的分离以及哪个端点应该在哪个文件中感到有些挣扎。 假设我们有一个 api enpoint 来处理用户和来自该用户的成就:

/user/{id} GET - 通过 id

获取用户

/achievement/{id} GET - 按成就获取

它们都在各自的资源文件中:

用户资源

@RestController
public class UserResource {

    public UserResource() {...}

    @GetMapping("/users/{id}")
    public UserDTO getUser(String id) {
        log.debug("REST request to get User : {}", login);
        return userService.getUserWithAuthoritiesById(id).map(AdminUserDTO::new));
    }

和AchievementResource

@RestController
public class AchievementResource {

   
    public AchievementResource(...) {...}

    @GetMapping("/achievements/{id}")
    public ResponseEntity<Achievement> getAchievement(@PathVariable Long id) {
        return achievementRepository.findById(id);
    }
}

到目前为止一切顺利,非常简单。当我必须从用户那里获得所有成就时,我的问题就来了。命名约定说我应该有一个端点,例如:

/user/{id}/achievements GET

但是这个端点应该在哪里?我觉得这两个资源都可能很好,因为对于 UserResource,端点的根是用户,但 AchievementResource 也可能是合乎逻辑的,因为我们正在返回成就。

你把controllers分开了,没有错,你应该按照它们的通用实体对方法进行分类,“如果我需要恢复用户的成就”,两者都有关系,但是,她从哪里得到这个数据来自?知道每个成就都必须在数据库中与用户建立关系,您可以使用 List returnAchievementsByUser (Integer Id) 方法在成就控制器中很好地查找它。

简单回答:你的问题有误

But where should this endpoint be?

资源的定义应该是机器可读的 api 定义。通过将您的定义输入到您选择的语言的代码生成器中,您可以生成所需的 class 文件。生成器会将它创建的 classes 放入文件 的某个地方 ,并且您将它们保留在这种默认排列中,直到将来某个时候您有充分的理由来排列它们不同的方式(此时,您分叉代码生成器并将您喜欢的设计设为默认设计)。


也就是说,在手动设计时,“REST 端点”没有什么特别之处。资源 classes 所属位置的准则与 Java 中的任何其他 classes 没有区别......

说,我发现有关文件布局启发式的文献相当令人失望。似乎没有很多 material 讨论不同设计的权衡,或者一种选择可能比另一种选择更具吸引力的上下文。

针对您的具体情况,我建议将新资源放入其自己的文件中。这里的论点是你的 UserResource 有 User 依赖,你的 AchievementsResource 有成就依赖,但是你的新东西有 both,并且作为(手波)原则,我们应该避免带来将不需要的成就依赖项放入 UserResource 的命名空间(反之亦然)。

换句话说,如果我们发现自己向现有文件添加导入来实现新事物,这暗示新事物可能更好地放在其他地方。

使用单独的文件也有很好的机械优势——它减少了合并冲突,每个文件都有自己的源代码控制历史(这意味着用户的历史不会被一堆专门关于新事物的提交弄得乱七八糟).例如,参见 Adam Tornhill's work over at CodeScene

这取决于你的观点和幕后的业务。在许多情况下,您可以只使用一个端点;如果“users”是有成就的主要资源,则“/users/{user-id}”和{users/{user-id}/achievements/{achievement-id}通过Id和特殊成就获取用户用户

@RestController
@RequestMapping("users")
public class UsersRestController{

   @GetMapping("/{user-id}")
    public UserDTO getUser(@PathVariable("user-id") String id) {
        code...
    }

   @GetMapping("/{user-id}/achievements/{achievement-id}")
    public AchievementDTO getAchievement(@PathVariable("user-id") String userId, 
                                         @PathVariable("achievement-id") String achievementId) {
        code...
    } 

}

如果在实体层次结构中将“成就”置于“用户”之上对您和您的业务有意义,那么 /achievements/{achievement-id}/users/{user-id} 可以休息一下介绍:

@RestController
@RequestMapping("achievements")
public class AchievementsRestController{

   @GetMapping("/{achievement-id}")
    public UserDTO getAchievement(@PathVariable("achievements-id") String id) {
        code
    }

   @GetMapping("/{achievements-id}/users/{user-id}")
    public AchievementDTO getAchievement(@PathVariable("user-id") String userId, 
                                         @PathVariable("achievement-id") String achievementId) {
        code
    } 


}

最后,只要它们不在实体层次结构中,您就可以将 userId 传递给 “/achievements/{achievements-id}”(或 achievement-id 到“/users/{user-id}”)作为 RequestParam。