Reputation: 1719
Ok, so we all know the REST way of getting a list of all entities would be to HTTP/GET /entities
, the best way of getting a single entity would be to HTTP/GET /entities/{entityId}
and the best way to get some entities is to HTTP/GET /entities/{entityId}?where=condition
(*) right?
(*) I actually mean /entities?where=condition
But what would be a good approach when we need to get a specific set of entities, equivalent to a SQL select ... where id in(id1, id2...)
when multiple HTTP/GET entities/{entityId}
is not an option due to latency?
And specifically how would I be able to do it with RESTEasy
Upvotes: 2
Views: 5364
Reputation: 12839
Although you already have found a solution, I'll post an answer as I'm not a big fan of the accepted answer for reasons I explain below. Admittingly, this is a pretty opinionated answer as the HTTP spec allows multiple ways to achieve similar things and REST does not dictate a certain URI style as well as leaves plenty of wiggle room for semantical interpretations.
The hypertext transfer protocol (HTTP) is not very descriptive regarding the semantics of the possible URI parameters. path
and query
parameters are proably well-known, header
and matrix
parameter are often neglected, though JAX-RS (as you initially asked for RESTeasy) can handle them almost as easy as the other ones.
REST furthermore is an architectural style and not a protocol. To call a service (or API) RESTful it has to adhere to a couple of constraints as well as respect the underlying HTTP protocol. As REST is resource orientated, unique resource identifiers are used to invoke a respective resource (hence URI). However, REST does not put any constraints on how URIs should be designed. Humans tend to put some semantically meaning into a good URI desing though for a computer it is just a further string.
In your comment you wrote:
I don't really see it like that. Most of the API's I've faced work like I described (maybe I have worked with the wrong ones).
on my comment that /entites/{entityId}?where=condition
returns a subset of one specifiy entity rather than a subset of entities. By specifying an {entityId}
as path parameter within the URI, you already limited the result set to a single entity. If you intended to return a set of entites in first place that match a certain entity property, why do you even provide an {entityId}
?
Query parameters are appended to the end of the URI and thus belong to every single path segment equally, compared to matrix parameters f.e. which belong to a single path segment only and thus convey a slightly different semantics on longer URIs which have multiple path segments. On simple URIs which do not include sub-resources the difference between matrix- and query-parameters is rather diminishing and they both can be used interchangingly. For URIs with multiple path segments though, the semantics may change though.
Also I don't understand what you mean by a subset of an entity?
If you have a JSON representation for a user entity with ID user1 like the one below:
{
"firstName": "Tim",
"lastName": "Sample",
"address": {
"street": "...",
"zip": "...",
"city": "...",
"country": "..."
}
}
invoking GET /user/user1?filter=lastName
I'd expect the query to return { "lastName": "Sample" }
only, on filtering by address
f.e. I'd expect only the address sub-resource to get returned, although I'd use /users/user1/address
in that case. Something like GET /user/user1?lastName=Sample
may be interpreted as a check if the identified user has the provided name and therefore should return true or false as response. As you see, humans semantically interpret URIs or their parameters while for a computer the URI just contains substrings and they don't care if the parameter is provided as path-, query-, matrix- or header parameter. They just rely on some predefined instructions which tell them where to extract the needed information from.
My concerns regarding the accepted solution are that on using POST
you can literally send anything to the server. You therefore need to explicitely document the expected representation that needs to be sent to the service and the behavior the server will execute upon reception of the request. Further, on using POST
for queries you lose the ability to cache the response. The later one is one of the few constraints REST has. Although certain caching frameworks do not cache responses on URIs which contain query parameters, this link as well as this answer both indicate that this is more an urban legend then reality.
You can, of course, implement a server side caching in order to minimize the DB lookups, but the request would still reach the server. On using GET instead of POST the request would not even reach the server at consecutive attempts due to the clients ability to cache the answer (if not prevented via special response header settings) and therefore return the answer directly from the cache rather then looking the state up again and again.
But what would be a good approach when we need to get a specific set of entities, equivalent to a SQL select ... where id in(id1, id2...) when multiple HTTP/GET entities/{entityId} is not an option due to latency?
As described in an other post, matrix parameters can be specified on path segments rather then on the whole URI like query parameters. This makes them quite useful for filtering on certain parts of an URI. If you want to return f.e. all courses held by professors with grey haircolor you could use something like GET /professors;haircolor=grey/courses
. You could of course reverse the structure and use something like /courses/professors?haircolor=grey
which is syntactically completely ok but if you think about which resource can exist without the other more easily and use these resources before more dependent resources, you will probably end up with the former URI.
A possible solution for your question therefore could be something like: GET /entity;id={id1};id={id2};...
. As also explained in this answer using query or matrix parameters on single resources might not be a big difference but if you f.e. want to return all addresses of a specified set of users only you could use something like this: GET /users;id={id1};id={id2}/addresses
. This allows for response caching as you use HTTP GET, you also semantically use a resource-subresource syntax where the one that is more likely to exist without the other is used before the referenced resource.
As RESTeasy is able to work with JAX-RS, matrix parameters can easily be injected into method arguments using @MatrixParam
annotation. As with @QueryParam
or @PathParam
parameters, the underlying JAX-RS framework will try to convert the parameter on a best-effort attempt.
@GET
public Response getSomething(@MatrixParam List<String> ids) {
...
}
In cases where the parameter can not be marshalled to an object automatically, you can also inject an UriInfo
object using a @Context
annotation and then retrieve the matrix parameter via the respective PathSegment
the parameter is annotated on and then marshal it to an object on your own.
@GET
public Response getSomething(@Context UriInfo info) {
for (PathSegment segment : info.getPathSegments()) {
MultivaluedMap matrixParameters = segment.getMatrixParameters();
...
}
}
As the PathSegment
returns a MultivaluedMap
, the same key is able to return multiple values (as a List) like in your case multiple IDs you want to insert into the DB query. UriInfo
also provides a MultivaluedMap
on looking up the path- and query-parameters.
It is therefore up to you what parameter style you prefer, REST does not dictate a specific URI design or semantic. Though, instead of using POST
I'd suggest to use GET
to reduce the documentation overhead needed in order to send queries to the service and to gain the ability to cache responses returned.
Upvotes: 2
Reputation: 31
You can create a HTTP/POST request with a JSON body, with the entity ids as an array property and other properties for any other custom matching/selection criteria, in the which would be deserialized on the service.
Request JSON Object:
{
"entityIds" : [12,22,45,2,44,5,66],
"order" : "DESC"
}
EntityRequest.java
public class EntityRequest {
List<Integer> entityIds;
String order;
public List<Integer> getEntityIds() {
return entityIds;
}
public void setEntityIds(List<Integer> entityIds) {
this.entityIds = entityIds;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
}
EntityResponse.java
public class EntityResponse
{
List<Entity> entities;
public List<Entity> getEntities() {
return entities;
}
public void setEntities(List<Entity> entities) {
this.entities= entities;
}
}
EntityService.java
@Path("/entities")
public class EntityService {
@POST
@Path("/")
@Consumes("application/json")
@Produces("application/json")
public EntityResponse createProductInJSON(EntityRequest entityRequest) {
List<Entity> entities = new ArrayList<>();
EntityResponse response = new EntityResponse();
List<Integer> ids = entityRequest.getEntityIds();
String order = entityRequest.getOrder();
//TODO: Build/execute your sql query, populate the entities list and return
response.setEntites(entities);
return response;
}
}
Upvotes: 1
Reputation: 7504
As per HTTP request type, each request type should serve as per protocol.
For example, HTTP/get should always be retrieving data and never do modifications using this call.
Also, as per REST, we should use these HTTP types as follows:
And so on ...
Hence, I would recomment to implement a /query endpoint of HTTP/post type which should be made generic to handle maximum query scenarios.
We could send nested json data in body to specify query parameters.
Json body for example:
{
"whereClause":{
"OR":{
{
"field":"name",
"operator":"=",
"value":"Raj"
},
{
"field":"age",
"operator":">=",
"value":20
}
},
"orderByClause":{
"name":"ASC"
"age":"DESC"
},
"groupByClause":[
"name"
]
}
This way you would achieve high flexibility and may query is lots of different ways.
Hope it helps!!
Upvotes: 2