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?
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
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.