Sub-Resources in spring REST

16,405

Solution 1

In Spring boot, we can implement JAX-RS subresource concept using @Autowired Spring Concept. Create an object of your child resource class and Spring will initialize at runtime and return that object. Don't create an Object manually. like: Above mention scenario

 - MessageResource.java

@RestController
@RequestMapping("/messages")
public class MessageResource {

    MessageService messageService = new MessageService();
    @Autowired
    @Qualifier("comment")
    CommentResource comment;

    @RequestMapping(value = "/{messageId}/comments")
    public CommentResource getCommentResource() {
        return comment;
    }
}    

 - CommentResource.java

@RestController("comment")
@RequestMapping("/")
public class CommentResource {

    private CommentService commentService = new CommentService();

    @RequestMapping(method = RequestMethod.GET, value="/abc")
    public String test2() {
        return "this is test comment";
    }
}



Now it will work like sub-resource
http://localhost:8080/messages/1/comments/abc

You can send any type of request.

Solution 2

Your url (http://localhost:8080/messages/1/comments/abc) suggests that comments are nested in the message. Your controller should look like this:

@RestController
@RequestMapping("/messages")
public class MessageResource {

    @RequestMapping(value = "/{messageId}")
    public String getCommentResource(@PathVariable("messageId") String messageId) {
        //test
        return messageId;
    }

    @RequestMapping(value = "/{messageId}/comments/{commentsContent}")
    public String getCommentResource(
                      @PathVariable("messageId") String messageId, 
                      @PathVariable("commentsContent") String commentsContent) {
        //test
        return messageId + "/" + commentsContent;
    }
}

I'm not entirely sure what you wish to do in your MessageResource class, but the idea is there.

Rest - HTTP methods

For now, these use are Get requests. You should however consider using the appropriate Http Method:

  • Get: read a resource
  • Post: create a resource
  • Put: update
  • Delete: delete :)

Take a look at this: http://www.restapitutorial.com/lessons/httpmethods.html

Example with Post:

@RequestMapping(method=RequestMethod.POST, value = "/{messageId}/comments/{commentsContent}")
    public ResponseEntity<String> getCommentResource(
                                  @PathVariable("messageId") String messageId, 
                                  @RequestBody Comment comment) {
        //fetch the message associated with messageId
        //add the comment to the message
        //return success
        return new ResponseEntity<String>(HttpStatus.OK);
 }

Class names

Also, I would personally rename these classes to MessageController and CommentController.

Edit after comments - Split controllers

You can just literally split the controllers (closer to what you had):

@RestController
@RequestMapping("/messages")
public class MessageResource {

    @RequestMapping(value = "/{messageId}")
    public String getCommentResource(@PathVariable("messageId") String messageId) {
        //test
        return messageId;
    }
}

@RestController
@RequestMapping("/messages")
public class CommentResource {

    @RequestMapping(value = "/{messageId}/comments/{commentsContent}")
    public String getCommentResource(
                      @PathVariable("messageId") String messageId, 
                      @PathVariable("commentsContent") String commentsContent) {
        //test
        return messageId + "/" + commentsContent;
    }
}

Solution 3

What you are looking for is supported in JAX-RS implementations such as Jersey and is called Sub-Resources. When building large APIs which become nested in nature, sub resources are an extremly useful feature.

Spring Boot default rest implementation is not a JAX-RS but SpringMVC. Whilst it is possible to use Jersey in Spring Boot, it's a bit involved trying to set it up, and doesn't appear to be well used/supported in the community.

On a side note, DropWizard is awesome!

Solution 4

I'm also being, forcedly, migrated from JAX-RS to Spring-MVC.

I'm still looking for an elegant way to do this just like I do with JAX-RS.

I'm sharing what I've tried.

@RestController
@RequestMapping("parents")
public class ParentsController {

    @RequestMapping(method = RequestMethod.GET,
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<List<Parent>> read() {
    }

    @RequestMapping(method = RequestMethod.GET,
                    path = "/{id:\\d+}",
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Parent> read(@PathVariable("id") final long id) {
    }
}

And the ChildrenController.

@RestController
@RequestMapping("/parents/{parentId:\\d+}/children")
public class ChildrenController {

    @RequestMapping(method = RequestMethod.GET,
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    @ResponseBody
    public List<Child> read(@PathVariable("parentId") final long parentId) {
    }

    @RequestMapping(method = RequestMethod.GET, path = "/{id:\\d+}",
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    @ResponseBody
    public Child read(@PathVariable("parentId") final long parentId,
                     @PathVariable("id") final long id) {
    }
}

Two problems I've found,

@PathVariable is not applicable on fields.

I just can't do @PathVariable("parentId") private long parentId;

No free will for multiple mapping for ChildrenController

A beauty of JAX-RS is that we can map ChildrenController for different paths like even the ChildrenController has a class-level @Path with it.

@Path("/children");
public class ChildrenResource {
}

@Path("/parents")
public class ParentsResource {

    @Path("/{id}/children")
    public ChildrenResource resourceChildren() {
    }
}


/children
/parents/{id}/children

Solution 5

You can keep it simple. Just create two classes and use a constant to refer the children resource with parent resource. This helps to make a link between the two classes and make the developer understand the relationship between them.

So:

@RequestMapping(value= MessageController.URL)
public class MessageController {
    public static final String URL= "/messages";
}

And:

@RequestMapping(value = MessageController.URL + "/{idMessage}/comments")
public class CommentController {

}

You can also split the controllers in different packages, showing this hierarchy in your package organization too:

com.company.web.message.MessageController
com.company.web.message.comment.CommentController 
Share:
16,405
prranay
Author by

prranay

Updated on June 07, 2022

Comments

  • prranay
    prranay almost 2 years

    I'm trying to build messanger app.

    I've to call CommentResource from MessageResource.

    I want separate MessageResources and CommentResources.

    I'm doing something like this :

    MessageResource.java

    @RestController
    @RequestMapping("/messages")
    public class MessageResource {
    
        MessageService messageService = new MessageService();
    
        @RequestMapping(value = "/{messageId}/comments")
        public CommentResource getCommentResource() {
            return new CommentResource();
        }
    }
    

    CommentResource.java

    @RestController
    @RequestMapping("/")
    public class CommentResource {
    
        private CommentService commentService = new CommentService();
    
        @RequestMapping(method = RequestMethod.GET, value="/abc")
        public String test2() {
            return "this is test comment";
        }
    }
    

    I want

    http://localhost:8080/messages/1/comments/abc

    to return "this is test comment".

    Any Idea??

    PS: In a simple word, I want to know JAX-RS sub-resource equivalent implementation in spring-rest

  • prranay
    prranay over 7 years
    Your solution works perfectly but I want to keep these two controllers separate and don't want to combine.
  • prranay
    prranay over 7 years
    yeah it solves he problem and code looks clean As MessageResource contains only Message APIs and CommentResource contains Comment APIs. Below is the code i'm using, Your solution gave me new direction to look into the problem. Thanks :)
  • MrSham
    MrSham almost 6 years
    @RestController @RequestMapping("/messages/{messageId}/comments") public class CommentResource { ////// Will be much better. Since comments are always will be part of some message and you have to have some message Id to get the comments. So using common uri path as much as possible to the class level is better.
  • Ahmed Anwar
    Ahmed Anwar over 5 years
    You did not answer his question. He asked for a JAX-RS subresource alternative, you just showed him how to give his REST service a URL
  • spaghettifunk
    spaghettifunk over 4 years
    Works like a charm! Sad that I couldn't find an example like this in the official documentation.
  • Mariusz Zawadzki
    Mariusz Zawadzki about 4 years
    How were you able to run it? Does it require some configuration? At the current version of spring boot (2.2.4.RELEASE) it treats CommentResource as response value to serialize. If I add a field with getter I get json representation of it. And on calling GET localhost:8080/messages/1/comments/abc I get 404.
  • TheJeff
    TheJeff over 3 years
    Man I'm not sure how to make sense out of this. First off, the "public CommentResource getCommentResource()" returns the CommentResource but doesn't inject the messageId context into it or use it in any way.. so I'm not sure if this is a mistake in the answer or my knowledge gap - clarification would be appreciated. Also the Comment resource has a different path "comment" as opposed to "comments" - eg singular vs. plural so I'm not sure if this is intentional - I think it is a mistake. Thanks for any correction or response.