Gleyak
Gleyak

Reputation: 564

Rest Resources Separation

I've been trying to start a REST api with Spring Boot and I'm a bit strugling with the separation of my resources and which endpoint should be in which file. Let's say we have an api enpoint to deal with a user and achievements from this user:

/user/{id} GET - to fetch user by id

/achievement/{id} GET - to fetch by achievement

Which are both in their separates resources file:

UserResource

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

And AchievementResource

@RestController
public class AchievementResource {

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

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

So far so good, pretty simple. My problem comes when I must get all achievements from a User. Naming covention says I should have an endpoint such as:

/user/{id}/achievements GET

But where should this endpoint be? I feel like both Resources could be good since for the UserResource, the root of the endpoint is the user, but the AchievementResource could be logical too since we are returning achievements.

Upvotes: 0

Views: 112

Answers (3)

Behnam Anjomruz
Behnam Anjomruz

Reputation: 46

It depends on your point of view and the business behind the scene. You can use just one endpoint in many cases; if "users" are the main resources who have achievements, then "/users/{user-id}" and {users/{user-id}/achievements/{achievement-id} get the user by Id and special achievement of the user

@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...
    } 

}

And if locating "achievements" on top of "users" in their entity hierarchy has meaning to you and your business, then /achievements/{achievement-id}/users/{user-id} can be a rest presentation:

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


}

finally ,whenever they are not in an entity hierarchy, you can pass userId to "/achievements/{achievements-id}" (or achievement-id to "/users/{user-id}") as a RequestParam.

Upvotes: 0

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57204

Easy answer: you have the wrong problem

But where should this endpoint be?

The definition of the resource should be in your machine readable api definition. You produce the class files you need by feeding your definition into a code generator for your choice of language. The generator will put the classes it creates in files somewhere, and you leave them in this default arrangement until some point in the future when you have a compelling reason to arrange them differently (at which point, you fork the code generator and make your preferred design the default).


That said, when designing by hand there's nothing particularly special about "REST endpoints". The guidelines for where resource classes belong is no different from any other classes in Java....

That said, I find that the literature around file layout heuristics rather disappointing. There doesn't seem to be a lot of material discussing the trade offs of different designs, or contexts in which one choice might be more compelling than another.

For your specific situation, I would advise putting the new resource into a file of its own. The argument here being that your UserResource has User dependencies, and your AchievementsResource has achievements dependencies, but your new thing has both, and as a matter of (hand waves) principle, we should avoid bringing unneeded achievements dependencies into the namespace of the UserResource (and vice versa).

In other words, if we find ourselves adding imports to an existing file to implement a new thing, that's a hint that the new thing may be better placed somewhere else.

Using separate files also has nice mechanical advantages - it reduces merge collisions, each file will have its own source control history (meaning that the history of Users isn't cluttered with a bunch of commits that are exclusively about new thing). See Adam Tornhill's work over at CodeScene, for example.

Upvotes: 1

user15743914
user15743914

Reputation:

As you separated the controllers, it is not wrong, you should classify the methods by their general entity, "if I need to recover the user's achievements", it is related to both, however, where does she get this data from? of the Achievements knowing that each achievement must have a relationship in the database with the user, you can very well look it up in the achievement controller with a List returnAchievementsByUser (Integer Id) method.

Upvotes: 0

Related Questions