2

I am trying to build the best PUT possible that allows modifying multiple entities upon single requests using java jersey. The idea is to send a request with multiple entity ids as @PathParam values and send a list of new entities in the payload as an ArrayList.

My problem is that I don't know how the RFC dictates how to build the URL with the entity ids and also how can I translate that into jersey PathParams.

For instance, the following example will replace entities with id 10 and 20 with the new ones:

Request:

curl -X PUT \
-H 'Content-Type: application/json' \
-d '[{"name":"A","surname":"B"},{"name":"1","surname":"2"}]}' \
http://my.domain.com/entity/10&20

Jersey code:

@PUT
@Path("/[entityIds]")
@Consumes(MediaType.APPLICATION_JSON)
public Response post(@PathParam("receptacleIds") List<long> receptacleIds, 
            List<Entity> newEntities) {
...
}

QUESTIONS:

  1. What's the best way to build the URL to pass multiple ids?
  2. How could I tell jersey to breakdown the URL for multiple ids?

Thank you

1 Answer 1

2

HTTP by itself is a distributed document management protocol whose application domain is the transfer of documents over a network, see Jim Webbers excellent talk. If you look at it from a document perspective, you send a single document from a client to the server and the server will process that document. HTTP is tailor made for such cases.

This gets obvious if you look at the specification of POST (or PUT) i.e. that demands on a successfully created resource to return the URI of that resource within the Location HTTP header of the response to indicate a resource creation to the client. POST, which just processes a payload according to its own semantics, may create multiple resources at once, though the Location HTTP header may only appear once. So how should the server tell a client that multiple resources got created?

The operations itself do not really fit well for batch-operations in general. While collections can be represented by listing each of the elements in a well-defined structure there is no build in option to automatically retrieve entries 10-15. One has to provide such feature manually or rely on a framework that does that for you.

Through the utilization of special-purpose media-types, such as multipart/form-data one request (or response) containing multiple separated data elements (or documents) may be transmitted between peers, each multipart element having their own media-type to allow the recipient to process the segments each on their own. This is probably the closest thing to batch-processing you can get using HTTP.

In regards to updates HTTP offers PUT and PATCH. The primer one has the semantics of replacing the current data available at a given target endpoint with the payload received in the request. From a document perspective, this simply replaces the document in the respective directory with the one provided in the request. The server may or may not perform some validation checks on the received payload first to learn if certain constraints it has for the target resource are violated and it may or may not change the format of the payload to fit the current style of the target resource. PUT always affects the whole document in general.

PATCH on the other side is similar to traditional patching in software engineering. Here a document contains a set of instructions that allow a user to basically apply that patch document on some files to transform it to a desired output. A single patch document may affect multiple files at the same time. Think of git where a single commit may include multiple file changes and a certain diff-tool can present you the changes before and after the commit. RFC 5789 requires that a patch is applied atomically. Either all of the instructions are performed successfully or none of the changes must be applied at all. This puts some transaction burden on the API. The respective changes needed usually need to be calculated by the client first and summarized into a so called patch document that needs to be sent to the server so it can be applied. It is, I guess, pretty obvious that a patch document can only be applied once in general and depends on a respective version of a resource. It is therefore recommended to utilize conditional requests, such as ETag or if-unmodified-since headers, when using PATCH to avoid either data loss or inapplicable patch documents.

Currently, there are two JSON based approaches to patching: application/json-patch+json and application/json-merge-patch+json. The difference between those is that the former one is more in line with traditional patching where you have to specify the operation to perform, i.e. add or delete, the position the change should be applied using JSON Pointers and depending on the operation to perform additional parameters such as the value to add or replace.

The latter media-type tackles a more pragmatic approach by sending a document that looks similar to the actual document. Through the utilization of some predefined rules an item can be removed if the document received contains a nullified value for that respective element. An update occurs if the actual document has a different value for an element than in the received payload. If the element in the patch document is missing, no further actions need to be applied. Usually this only affects one single document though on sending such a media-type to a collection endpoint a partial update of the elements of that collection should be possible as well.

RFC 7231 also mentioned that by overlapping resources a partial update may be possible via PUT. This evolves around the fact that data presented via a resource may also be included in other resources as well and updating one, will also lead to a modified version of the other resource. Think of a collection of users where you list the names in a table and allow a single user to get updated. If during the update you changed the last name of one user, this might affect the presentation of that user in when listing the users via the collection resource as well.

The idea is to send a request with multiple entity ids as @PathParam values and send a list of new entities in the payload as an ArrayList ... The idea is to send a request with multiple entity ids as @PathParam values and send a list of new entities in the payload as an ArrayList

The general issue here is, that one of the constraints of REST demands caching. One might request entity 10 via http://my.domain.com/entity/10 through HTTP GET operation. The response may be cacheable by the server, an intermediary or by your client. The URI is the de-facto key used for caching the response. If you now attempt to batch-update multiple entities, i.e. via http://my.domain.com/entity/10&11&12&... you basically end up with a different URI. A cache will basically evict all stored representations for a given URI if it notices a none-safe HTTP operation being performed on that URI as it believes that some kind of update will be performed. Though as you now operate on a different URI a cache might not be aware that one of the URIs is altered and thus a consecutive retrieval of entity 10 via the above mentioned URI still might return the old value before the update. This is yet a further hint that HTTP is not ideal if you have to deal with batch operations.

My problem is that I don't know how the RFC dictates how to build the URL with the entity ids

Neither HTTP nor REST dictate anything in that regards. All they require is that the URI is compliant with the spec itself. The actual form of a URI is furthermore usually not that relevant, especially clients should refrain from attempting to parse or analyze any semantics from URIs as a server is free to manage its namespace and change its URIs accordingly. Through relying on other mechanisms to determine whether to invoke a URI, such as accompanying data like link-relation names and human readable text, a server is able to change its URI structure anytime it has to without breaking the client. By avoiding to parse URIs for semantic property extractions one might also prevent typed resources and instead focus on content-type negotiation to prevent interoperability issues. Here, instead of predefined types that are usually unique to an API, standardized and therefore widely available representation formats should be used. The more media-types a server can handle the more different clients the server will be able to serve.

What's the best way to build the URL to pass multiple ids?

IMO, don't! As mentioned throughout this post, HTTP wasn't designed for batch-processing. While certain operations may give the impression that it can handle multiple entities simultaneously with one request, the truth is that it is still one single request that needs to be broken down. You might use multipart/form-data to send multiple documents in one go to the server, though I have never seen such a thing using PUT. While PUT would allow the transformation to a more applicable representation I'm not sure the engineers had particularly such a scenario in mind. I'd interpret such a PUT request for multipart/form-data to store the whole multipart form-data document at the given resource instead of splitting it up into the own respective parts.

As your example updated two out of probably many entries, why isn't PATCH a feasible solution? Here you could send one single patch document to the server containing the necessary steps to transform those entities to a desired output and call it done. The only issue here that I see is, again, the handling of any cached representation as you don't reuse the URI that actually targets the respective entity but the one of the collection (most probably) or a dedicated one.

How could I tell jersey to breakdown the URL for multiple ids?

While I'm not a big fan of this approach, as you probably can tell by now, you might either use query-parameters to send the respective ids or utilize matrix parameters to send the respective ids to the server. The difference between matrix and query parameters is, that the latter one are always appended at the end and belong to the whole URI while a matrix parameter can be used on a path segment, i.e. .../professors;hairColor=grey/courses for a case where only courses held by professors with grey hair-color should be included. Of course, one might reverse the URI or append the restrictions via query-parameter, though this might change the semantics of the URI the developer of the API might have intended slightly.

You can of course also parse the path parameter you've listed and extract the ids that way and do whatever you have to in that method.

As you hopefully can see by yourself, HTTP is probably not the ideal protocol to use in your scenario. HTTP is great when it comes to working with single "documents". While certain media types exist that allow to transmit multiple documents at once, the media type is not intended for updates IMO. HTTP basically offers PUT and PATCH to update resources and in your case it seems that you want to perform a partial update on a collection resource itself. While PATCH might be the best thing to use in such a case, it is less ideal when you consider cached values for the updated entities.

2
  • Wow, this is really an excellent answer. One minor remark: the link 'conditional requests' doesn't work as intended. Commented Nov 2, 2019 at 21:31
  • @www.admiraalit.nl good call, fixed it Commented Nov 2, 2019 at 21:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.