I'm quite new into REST API development. I have decided to create a Blog application using Spring Boot and I'm really struggling with the design and structure of my app.
Right now my app consists of Post and Comment models and repositories. For both models, I have created service classes (PostService and CommentService). In these classes, I have all the business logic (just simple CRUD right now).
Now I am scratching my head about the design of my #RestControler for Posts. In PostController I have exposed these actions:
#PostMapping("/api/posts/create")
public Post create(#RequestBody Post post) { ... }
#GetMapping("/api/posts")
public List<Post> findAll() { ... }
#GetMapping("/api/posts/{id}")
public Post findById(#PathVariable("id") Long id) { ... }
#PutMapping("/api/posts/{id}")
public Post update(#RequestBody Post post) { ... }
#DeleteMapping("/api/posts/{id}")
public void delete(#PathVariable Long id) { ... }
Now I'm getting to my question. I am wondering what is correct design of adding a Comment to the Post.
Should I expose all CRUD method for Comment using CommentController class and use create method?
Is it ok to add a new method addComment to PostController which will create a new Comment?
In my head adding a Comment to the Post belongs to the Post, but I really don't know.
Could some of give me some advice regarding this matter?
Thanks a lot!
Bye,
Tom
If I were you, I'd consider REST Design Principles from the OpenAPI Specification and would follow resource -> sub-resource -> method||identifier pattern. This would probably be the most KISS and clean design for the readability and understanding purposes.
#PostMapping("/api/posts/") //you don't need /create as a separate URI
public Post create(#RequestBody Post post) { ... }
#GetMapping("/api/posts") //This is OK.
public List<Post> findAll() { ... }
#GetMapping("/api/posts/{id}") //OK, however {id} should be optional, hence you can combine this and upper methods in one method.
public Post findById(#PathVariable("id") Long id) { ... }
#PutMapping("/api/posts/{id}") //OK.
public Post update(#RequestBody Post post) { ... }
#DeleteMapping("/api/posts/{id}") //OK.
public void delete(#PathVariable Long id) { ... }
and now, for the comments API design, I would have contain them under posts resource, and would have added these corresponding URIs:
#GetMapping("/api/posts/{id}/comments/{commendId}") //commentId is optional
#PostMapping("/api/posts/{id}/comments/") //you don't need any {commendId} here, just post the payload
and etc. I hope you can come up with method signatures and other method mappings.
You can also see the RESTful naming conventions here
To be honest I don't think that someone can give you the perfect answer here. It is often a personal decision. In common you can say the following about a REST API.
the path should only represent your data structure in the database. So for example /api/posts
No verbs in your path. What you want to do should be handled by the RequestType (GET, POST, PUT, PATCH, DELETE, etc.)
Now to your case. I can really good understand why you are struggling. I think here are two options:
PostsController
You say a Comment is always a part of a Post and because of this
you design your API like this.
#PostMapping("/api/posts/{id}/comment")
public Comment create(#PathVariable Long id), #RequestBody Comment comment) { ... }
CommentsController
You handle Comment as an own object and the Post is just a relation you add to it by attribute.
#PostMapping("/api/comments")
public Comment create(#RequestBody Comment comment) { ... }
So it is always is it a Subset vs make own Object structure. I think in this case here I would prefer option 2 because I think you want do more operations on this object.
Also you can than design your API in the way that every Controller starts with the object that will be handled /api/OBJECT/xxx/yyy
UPDATE
After reading the comment from #gulliva I think also a good way is to use this URL #PostMapping("/api/posts/{id}/comment") but put it in the CommentsController. I think this is a good way.
Related
I have a simple question regarding how do I link an endpoint through HATEOAS if that endpoint requires a valid #RequestBody? I've seen another question regarding this but with no straight answers and so I wonder if the question was not clear enough.
Take a look at the code below:
#GetMapping(path = "/notification/{id}")
#ResponseStatus(HttpStatus.OK)
public NotificationItemResponse getNotification(#PathVariable final String id) {
return notificationItemMapper.toResponse(findUseCase.findNotification(id))
.add(linkTo(methodOn(NotificationItemController.class).getNotification(id)).withSelfRel())
.add(linkTo(methodOn(NotificationItemController.class).saveNotification()).withRel("save")) <- error here, saveNotification expects a valid request body
.add(linkTo(methodOn(NotificationItemController.class).revokeNotification(id)).withRel("revoke"))
.add(linkTo(methodOn(NotificationItemController.class).markNotificationAsSeen(id, "{userName}")).withRel("visualize"));
}
saveNotification() is a method on this controller that requires a request body. That request body is a long json containing a notification message, target users, etc.
It doesn't seem right to create a dummy body just to pass down here, and it hardly seem right to pass a null value. What is the correct approach here? How do I correctly link a method that requires a request body? More specifically, what do I pass down as that request body?
What is the best practice here, other than passing a null or dummy body, as I stated before as a non-optimal solution?
The question is pretty old, but I faced the same issue today, and it looks pretty hard to find the correct answer.
After some research, I found this example in Spring HATEOAS Docs: 3.3 Affordances
#GetMapping("/employees/{id}")
public EntityModel<Employee> findOne(#PathVariable Integer id) {
Class<EmployeeController> controllerClass = EmployeeController.class;
// Start the affordance with the "self" link, i.e. this method.
Link findOneLink = linkTo(methodOn(controllerClass).findOne(id)).withSelfRel();
// Return the affordance + a link back to the entire collection resource.
return EntityModel.of(EMPLOYEES.get(id), //
findOneLink //
.andAffordance(afford(methodOn(controllerClass).updateEmployee(null, id)))
.andAffordance(afford(methodOn(controllerClass).partiallyUpdateEmployee(null, id))));
}
In this case, they use a method afford(...), which works pretty similar to linkTo(...). Looks like passing a null object is a best practice, or at least it is encouraged by the Spring team. So in your case it would look like this:
.add(linkTo(methodOn(NotificationItemController.class).saveNotification(null)).withRel("save"))
I have a working production SpringBoot application, and part of it is getting a do-over. It would be very beneficial for me to delete my old #RequestMapping from the ResponseEntity<String> foo()s of my world, keeping the old code as an as a duplicate while we try to roll out the new functionality behind a feature gate.. All production tenants go through my no-longer-declarative foo() function, while all my test and automation tenants can start to tinker with a brand new EntityResponse<String> bar().
The way to implement the change was so clear in my mind:
class Router{
#Bean
RouterFunction<ServerResponse> helloWorldRouterFunction(OldHelloWorldService oldHelloWorldService) {
return RouterFunctions.route()
.route(RequestPredicates.path("/helloWorld/{option}"), x ->
{
String option = x.pathVariable("option");
if (FeatureManager.isActive()) {
return ServerResponse.ok().body(String.format("New implementation of Hello World! your option is: %s", option));
} else {
// FutureServerResponse is my own bad implementation of the ServerResponse interface
return FutureServerResponse.from(oldHelloWorldService.futureFoo(Integer.parseInt(option)));
}
}
)
.build();
}
}
Here's the implementation for OldHelloWorldService::futureFoo
#RestController
static class OldHelloWorldService {
#RequestMapping("/specialCase")
ResponseEntity<String> specialCase() {
// some business logic
return ResponseEntity.ok().body("Special case for Hello World with option 2");
}
/**
* Old declarative implementation, routed via functional {#link ServerRouteConfiguration}
* to allow dynamic choice based on {#link FeatureManager#isActive()}
* <p>
* as you can see, before the change, this function was a {#link RequestMapping} and it handled the
* completable future, we could return both concrete OK responses with a body, and FOUND responses with a location.
*/
// #RequestMapping("/helloWorld/{option}")
CompletableFuture<ResponseEntity<String>> futureFoo(
// #PathVariable
int option) {
return CompletableFuture.supplyAsync(() -> {
if (option == 2) {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("/specialCase"))
.build();
} else {
return ResponseEntity.ok().body(String.format("Old implementation of Hello World! your option is: %s", option));
}
});
}
}
This feature lets my backend code decide what kind of ResponseEntity it will send, in the future. As you see, a smart function might for instance decide to either show a String message with an OK status, or give a Location header, and declare FOUND status, and not even give a String body at all. because the result type was a full fluid ResponseEntity, I had the power to do what I will.
Now with a EntityResponse you may still use a CompletionStage, but only as the actual entity. while building the EntityResponse I am required to give it a definitive final status. If it was OK, I can't decide it will be FOUND when my CompletionStage ran it's course.
The only problem with the above code, is that org.springframework.web.servlet.function does not contain the FutureServerResponse implementation I need. I created my own, and it works, but it feels hacky, And I wouldn't want it in my production code.
Now I feel like the functionality should still be there somewhere, Why isn't there a FutureServerResponse that can decide in the future what it is? Is there a workaround to this problem maybe somehow (ab)using views?
To state the maybe not-so-obvious.. I am contemplating a move to reactive and WebFlux, but changing the entire runtime will have more dramatic implications on current production tenants, and making that move with a feature gate would be impossible because the urls would have to be shared between MVC and Flux.
It's a niche problem, and Functional Web MVC has little resources so I will appreciate greatly any help I can get.
I have created a github companion for this question.
I've had to patch spring web to get what I needed. I pushed a pull-request to spring web with my patch, in the end something similar was created and pushed to the 5.3 release of spring.
if anybody else is looking for the async behavior described in the question, ServerResponse.async function in spring 5.3.0+ (spring boot 2.4.0+) solves the issue.
My service:
#POST
public String setData(#QueryParam("id") Long is, MyObject payload) {
...
}
or
#POST
public String setData(#PathParam("id") Long is, MyObject payload) {
...
}
My interceptor on the server:
Object read(MessageBodyReaderContext context) throws IOException, WebApplicationException {
Class mypayloadtype = context.getType;
InputStream mypayloadinpustream = context.getInputStream();
Long myidparam = ???????? // how to get the query or path param here?
}
EDIT: To be a bit more concrete:
What I'd like to do is to grab the XML and store it based on the parameters in a separate audit system. Maybe PreProcessInterceptor / PostProcessInterceptor are the better choices?
Any hints or alternative ways to get the param when the xml is still available for preprocessing?
Miguel
I just stumbled over the same problem today. I needed the #PathParams and #QueryParams in the read() method and ended up with something like this:
public class MyInterceptor implements PreProcessInterceptor, MessageBodyReaderInterceptor
{
private static ThreadLocal<UriInfo> uri = new ThreadLocal<UriInfo>();
public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
{
uri.set(request.getUri);
...
}
public Object read(MessageBodyReaderContext context)
{
String param = uri.get().getPathParameters().getFirst("myidparam");
...
}
}
Although when thinking about it now - I'm not quite sure, if just using PreProcessInterceptor/PostProcessInterceptor will also do the trick for my (and maybe your) problem. I'll have another look tomorrow.
I am not an expert on the topic but to me it seems as if the MessageBodyReaderContext interface does not really know if it is on the server or the client side, so it cannot expose the request or its parameters / path parts etc.
So as far as I know this is not possible.
If your code knows that it lives on the server side of the rest
communication, maybe you can use a servlet filter to store the request
in a ThreadLocal and then access it from there while the request is
handled, somewhat similar to RequestContextFilter / RequestContextHolder from the spring framework? (Then the request object does not know anything about the annotations of your service, but instead one has to extract the information manually from the request. This means to have the same information in two places, so there has to be a better solution ...)
Edit: after looking at some examples I get the vague feeling that if you want to read the input stream to create an object and add path parameters to it, MessageBodyReaderInterceptor is simply not the way to go. Instead set up a MessageBodyReader which constructs the object from the request body data, and this then will be passed into the public String setData(#PathParam("id") Long is, MyObject payload), assuming that this method is annotated with a #Consumes which matches the #ConsumeMime annotation for the MessageBodyReader. There you might be able in the setData to set the missing id on the object read from the request body. Some examples related to this seem to be here: How to get full REST request body using Jersey? (but for Jersey, not jBoss :-/)
However I am not sure if that works for you, and I also feel I completely overestimated my ability to answer this question appropriately, so I hope someone more knowledgeable comes in with a better solution.
I have a controller with a method that handles incoming GET data, stores some things in the model, and then redirects to another page that deals with these objects.
I can't seem to find any good way of getting the object stored in the first method back out of the model to use in the second method. How can I do this?
Here's the top of the controller:
#Controller
#RequestMapping("/reviews")
#SessionAttributes({"review", "externalReview"})
public class ReviewController {
// [SNIP]
}
Here's the code that adds the objects I'm after to the model:
#RequestMapping(value="/new", params="UName", method=RequestMethod.GET)
public String newFormFromExternal(#ModelAttribute("externalReview") ExternalReview externalReview, Model model) throws IncompleteExternalException {
// Convert the inbound external
Review fromExternal = ExternalReviewUtil.reviewFromExternalReview(externalReview, externalDAO);
// Add the externalReview to the session so we can look to see if we got a reviewee on the way in
model.addAttribute("externalReview", externalReview);
model.addAttribute("review", fromExternal);
return "redirect:/reviews/newFromExternal";
}
You are in luck.
If you are using or have ability to update to the newly released Spring 3.1, you can make use of the newly scoped Flash variables.
http://static.springsource.org/spring/docs/3.1.0.RC1/spring-framework-reference/html/mvc.html#mvc-flash-attributes
If you can't use 3.1, you probably can implement the solution yourself. Essentially you want to capture the model object required to be present in the redirect, put in the session, and remove it once it is retrieved to keep your session from bloating.
Currently, I'm just getting a Map of the model, getting the object I want out by it's key (the String name), and then casting it to the object it really is (rather than just Object).
Here's the code:
#RequestMapping(value="/newFromExternal", method=RequestMethod.GET)
public String newExternalForm(Model model) {
// Get the review from the model
Review review = (Review) model.asMap().get("review");
/*** Do stuff with the review from the model ****/
return "reviews/newFromPacs";
}
This way works, but it seems hacky and clunky. Is this really the only way?
One possible solution is to use #ModelAttribute, though it's quite ugly since you'll need to disable databinding for that attribute (for security):
#RequestMapping(value="/newFromExternal", method=RequestMethod.GET)
public String newExternalForm(#ModelAttribute Review review) {
...
}
#InitBinder("review")
public void disableReviewBinding(WebDataBinder b) {
b.setAllowedFields();
}
I'm interested in opinions on the best way to handle the concept of "entitlement" using either Spring Security or Shiro.
For example, imagine, say, a JAX-RS endpoint that has a signature like this:
AccountDetails getAccountDetails(String accountId);
Using Spring Security, I might annotate an implementation like:
#Secured(AUTHORIZED_USER)
public AccountDetails getAccountDetails(String accountId) { ... }
or using Shiro,
#RequiresAuthentication
public AccountDetails getAccountDetails(String accountId) { ... }
What I am looking for, however, is some recommendations on "best practices" for how to ensure that the user has permission to access the particular account id (which I think is called "entitlement management").
I could imagine a couple of different approaches:
#Secured(AUTHORIZED_USER)
#AccountEntitled
public AccountDetails getAccountDetails(#Account String accountId) { ... }
(which strikes me as not completely straightforward using Spring Security, but I'd love to be wrong).
Or, I could imagine introducing an AccountId domain object, and a factory which will only succeed in turning a String into an AccountId if the principle held by the current security context allows that users to see that account. But that starts to get a bit messy.
On the whole, I don't want to invent new concepts here; this seems like bread & butter stuff, but I've not had much luck finding credible recommendations around best practices here.
Thanks for any suggestions.
It sounds like what you are trying to do is implement row-level security for specific accounts. There are other Stackoverflow questions (How to implement row-level security in Java? and Database independent row level security solution) that discuss potential solutions to this very problem. Additionally, the link provided in the first answer discusses implementing Row Level Security with Spring and Hibernate. However, the higher ranked answer recommends implementing row-level security directly at the database level.
Having worked with Shiro I can say that it can be done. However you must implement your own security structures (Realms, Permissions, Annotations) to accommodate the type of functionality you describe. One approach would be to add an annotation similar to what you have in your last example that indicates the method requires a permission check. This annotation would be tied to an Interceptor which would in turn generate the appropriate permission and then call to the security framework to verify the permission.
It would look something like this.
Method:
#RequiresAuthorization
#Entitled
public AccountDetails getAccountDetails(#Account String accountId) {...}
Interceptor:
#Interceptor
#Entitled
public class EntitledInterceptor {
#AroundInvoke
public void interceptOrder(InvocationContext ctx) {
// return type is AccountDetails
// parameter[0] is acccoundId
Permission p = new CustomPermission(context.getMethod().getReturnType(),
ctx.getParameters()[0]);
if(SecurityUtils.getSubject().isPermitted(p)){
return ctx.proceed();
} else {
throw new RowLevelSecurityException("No access!");
}
}
Realm:
public boolean isPermitted(SubjectPrincipals principal, Permission p){
if( p instanceof CustomPermission){
CustomPermission cp = (CustomPermission) p;
Class<?> type = cp.getType(); //AccountDetails
Integer id = cp.getId(); //accountId
Integer userId = principal.getPrimaryPrincipal(); //or username
customPermissionCheckingLogic(userId, type, id);
}
}
Obviously this implementation relies on CDI and you having a way to determine what table(s) to check based on the object type provided (JPA annotations work in this regard). Additionally there may be ways to hook into Shiro's annotation scanning to provide more direct/native permission functionality than what I've done here.
Documentation on CDI interceptors.