-2

How is the best approach to define a RESTFul web service API for fetch specific relation of a JPA entity?

Example:

If a have a resource User with an atribute Roles (1-N relations)

I would like some times to call my resource getUserByName (I do not want to bring the relations because performance) and getUserByNameWithRoles (here I want the relation for evict double network trip)

How is the best way to get this with java rest?

@Path("/user")
class UserResource{

  @GET
  @Path("/{name}")
  public Response getUserByName(@PathParam("name") String name){
    // hibernate query: select u from User u where u.name = :name
  }

  @GET
  // How I map this URL?
  @Path("/{name}") 
  public Response getUserByNameWithRoles(@PathParam("name") String name){
    // hibernate query: select u from User u inner join fetch u.roles where u.name = :name
  }

}

1) Have 2 methods?

2) Use some "expand" trick, with a @QueryParam (does exist any framework for this or it is by hand)

How your guys are solving this?

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • Personally I would probably just load the roles every time, even if I only needed them half the time. This sounds to me like a case of premature optimization. A user typically only has a few roles, I doubt that this will be the main bottleneck in your application. – Klaus Groenbaek Feb 10 '17 at 00:09
  • @KlausGroenbaek This was just an example (User x Roles). I just want to know how to handle it with rest. Because we are migration some rmi call to rest services between 2 webapps. – Mauricio Ferraz Feb 10 '17 at 05:20

2 Answers2

2

Using a query parameter

You could have a single method that supports a query parameter to give you the possibility of loading the roles:

@GET
@Path("/{name}")
@Produces("application/json")
public Response getUserByName(@PathParam("name") String name,
                              @QueryParam("load-roles") boolean loadRoles) {
    ...
}

Using sub-resource

Alternatively, you could have an endpoint that returns only a representation of the user and another endpoint that return only a representation of the roles of the user:

@GET
@Path("/{name}")
@Produces("application/json")
public Response getUserByName(@PathParam("name") String name) {
    ...
}

@GET
@Path("/{name}/roles")
@Produces("application/json")
public Response getRolesFromUserByName(@PathParam("name") String name) {
    ...
}

When the roles are required, just perform a request to the second endpoint to return the roles.

Using a custom media type

Alternatively, you could have a custom media type for the full representation of the resource and a custom media type for the partial representation.

With this approach, you would have the following methods:

@GET
@Path("/{name}")
@Produces({ "application/json", "application/vnd.company.partial+json" })
public Response getUserByName(@PathParam("name") String name) {
    ...
}

@GET
@Path("/{name}")
@Produces("application/vnd.company.full+json")
public Response getUserByNameWithRoles(@PathParam("name") String name) {
    ...
}

To request a partial representation of your resource, the request would be like:

GET /api/users/johndoe HTTP/1.1
Host: example.com
Accept: application/json
GET /api/users/johndoe HTTP/1.1
Host: example.com
Accept: application/vnd.company.partial+json

To request a full representation of your resource, the request would be like:

GET /api/users/johndoe HTTP/1.1
Host: example.com
Accept: application/vnd.company.full+json
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
0

Just define your various endpoints and call into your service or repository classes to execute the appropriate query on a root entity specifying whether to load the roles relation or not.

@Path("/{name}/withRoles")
public Response getUserByNameWithRoles(@PathParam("name") string name) {
  User user = userRepository.findByNameWithRoles( name );
  // translate your User to the response here.
}

and

@Path("/{name}")
public Response getUserByName(@PathParam("name") string name) {
  User user = userRepository.findByName( name );
  // translate your User to the response here.
}

At the end of the day, regardless of whether you're using framework X versus framework Y, the concepts and ideas remain fairly straight forward and consistent.

As you also mentioned, you could use a @QueryParam to pass a flag to indicate whether to populate the User with or without roles too.

Naros
  • 19,928
  • 3
  • 41
  • 71
  • Thanks. This (with 2 methods) looks like simple and helpful. – Mauricio Ferraz Feb 10 '17 at 05:07
  • Sorry I delete one answer unintentionally -> Thanks for your response. 1)Using a query parameter: I'd like to be a generic way, for all entities and all relations, not only roles. 2)Using sub-resource: This is nice, but less performatic, 2 network calls, and 2 database queries.3)Using a custom media type: This is interesting, but I think I can not define this way. This is a generic way for one or all relations. If tomorrow I have many relations the query will be slow and more network traffic just for I get the roles of the User. – Mauricio Ferraz Feb 10 '17 at 05:17
  • Understand that adding a query parameter to influence collection loading could be problematic, particularly if you decide to support passing that parameter multiple times or with some delimited value to represent loading multiple associations in a single query. I understand the desire for "generic"-ness but you shouldn't do something to write less code but open the door up to potential security and performance risks – Naros Feb 10 '17 at 14:17