Let's consider we have two entities. User entity can have many offers and an Offer must have one 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;
}
At this moment there are two controllers. UserController and the OrderController. The UserController is mapped under /api/v1/users/ and the OrderController is mapped under /api/v1/orders/.
- What should an endpoint look like that fetches the user's offer list?
- Should it be in the same controller? I do have by functionality project structure.
- How to modify or delete an
Offerfor a particularUser? In case we would have/api/v1/users/{username}/offers/{offerId}to delete or update an offer, should we have also/api/v1/offers/{offerId}endpoint that allows to edit or remove an offer? Perhaps it is worth having it for an administrator?
CodePudding user response:
General rules of thumb that I use when creating my endpoints are:
- URLs need to be clean and easy to understand
- They should be as short as possible while still informative.
- Try to build it in a such manner that it allows you to reuse it within the reasonable amount
- Think about the user experience(whether this is called from browser or mobile app etc.)
I am not sure if there is a written rule exactly how one should build an URL though.
In your specific case I would use /users/{username}/offers/{offerId} only if this is the only place you are exposing and using offers, since you are separating your code by functionality.
If you have any further logic around offers and/or have beans that have such logic I would create a separate controller for Offers which would be under /offers.
Concerning your last question. This very much depends on what you are trying to achieve. If you need to be able to update/delete/create offers then it makes sense to have such functionality. Even if it only used by the administrator. You can restrict the access to the endpoint. How to do that depends on the way you are authorize your users and the information that you have on them. Most people use roles.
If you decide to have the full CRUD functionality I would suggest to use a single path with combination of request methods.
Personally I would create the following:
@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){
...
}
}
In this scenario I think the POST/PUT for create and update will be cleaner as there will be no duplication of information. To be precise the IDs.
CodePudding user response:
I agree that it should be in the same 'UserController', it makes sense because offers belong to the user, so having an endpoint like:
@GetMapping("{user}/offers")
public Set<OfferDTO> getOffers(@PathVariable("user") String user) {
return offerService.getOffers(user);
}
You can define special DTOs for getting the meta-data from the offers if you wanted to display them in a list for example, and you could display them as a list to your user.
You could set up a similar endpoint for updating the offer which could be a POST endpoint, and a DELETE endpoint for deleting. You might want to think about what would happen if the user is looking at an offer when you delete it though, like making an asynchronous task for deleting the offer in a background thread and updating the UI to inform the user that the offer is deleted.
Spring has some really nice annotations for security stuff (check this and this), you could write your own annotation for administrator endpoints:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAuthority('" ROLE_ADMIN "')")
public @interface IsAdmin {}
Then annotate your method like this:
@DeleteMapping("/{user}/{offer}/delete")
@IsReceiverAdmin
public void delete(@PathVariable("user") String user, @PathVariable("offer") String offer){
return offerService.delete(user, offer);
}
Of course the implementation of the service layer would be quite important, but it could be as simple as calling your repositories and performing the operations there :)
