5

I have a single ID REST API that I need to extend to support multiple (up to 10Ks) IDs. Basically to run update on all relevant IDs instead of sending 10Ks request in network.

Current endpoint:

@POST
@Path("{id}/update")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(@PathParam("id") int id, List<RequestVo> requestVo) {

One option suggested is comma-delimited values as stackexchange's answers-by-ids

Usage of /answers/{ids} GET

{ids} can contain up to 100 semicolon delimited ids. To find ids programmatically look for answer_id on answer objects.

This is the case on similar answers

http://our.api.com/Product/<id1>,<id2> :as James suggested can be an option since what comes after the Product tag is a parameter

But it seems awkward to me and RequestVo will be same for all IDs (which is currently is fine, but later to add such support will be harder)

It seems I need to change from Path variable to add it inside RequestVO

Which means the Id will be a JSON key, e.g.

[{
"id" : "1",
"name": "myAttribute"
"toggle": true
},
{
"id" : "2",
"name": "mySecondAttribute"
"toggle": false
}
]

Is this the correct approach or am I missing something?

Thank you in advance for any comments\answers

Current request VO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestVO {

 private String name;
 private boolean toggle;
 // will add now private int id
 }

My concern is also if I want (one of the requirement) to update with same request (as name=doA, toggle=true) for 10Ks Ids I'll have to duplicate request VO instead of sending ID separately

Community
  • 1
  • 1
Ori Marko
  • 56,308
  • 23
  • 131
  • 233
  • I'd add the List of identifiers in the request body, having a huge number of request parameters is kind of awkward. – T A Jul 01 '19 at 13:07
  • @TA so my approach is correct – Ori Marko Jul 01 '19 at 13:08
  • I do not quite understand how your `RequestVo` is related to your identifiers, can you elaborate? If you want to update these objects for each id, I would just pass a `List ids` in addition to your `List requestVo` instead of merging these two. – T A Jul 01 '19 at 13:14
  • @TA wouldn't it cause issues when merging? if I'll get size 5 of ids and size 2 in RequestVO ? – Ori Marko Jul 01 '19 at 13:18
  • Why not adding a new Endpoint like `updateBlockReasonBatch` without `{id}` path variable and just take a body with a list of id's? – arnonuem Jul 01 '19 at 13:44
  • @arnonuem I want 1 logical endpoint for update – Ori Marko Jul 01 '19 at 13:49

3 Answers3

4

The best way is to keep id in your RequestVO DTO itself and not in URL as you have already suggested because even 100 ids in URL can make your URL very big and you are talking about 10K ids.

And again in future, the bit length of a single id may increase or later on you might need to update 50k or even 100K objects.

According to maximum length of a URL, there is no general specification on URL length but extremely long URLs are usually a mistake and URLs over 2,000 characters will not work in the most popular web browsers.

So I think your second approach is best here and will be good for future purposes also.

You may also want to use a PUT request because it makes more sense for an update request. So your code will become like this:

@PUT
@Path("/update")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(List<RequestVo> requestVo) {

Naresh Joshi
  • 4,188
  • 35
  • 45
  • My concern is also if I want (one of the requirement) to update with same request (as name=doA, toggle=true) for 10Ks Ids I'll have to duplicate request VO instead of sending ID separately – Ori Marko Jul 07 '19 at 11:07
  • I do not understand your concern, can you explain it? if not in the comment may be in the question itself. – Naresh Joshi Jul 07 '19 at 13:16
  • My JSON will have id:1 name:doA, toggle:true, id:2,name:doA, toggle:true, id:3,name:doA, toggle:true, ... it seems duplicate name/toggle but maybe there's no other way – Ori Marko Jul 07 '19 at 13:18
  • If multiple ids mean multiple database objects or rows, if multiple Db rows can have the same data (same name and toggle) and all those objects need to update. But if that is not your case then you can handle this with some logic in your API, or maybe while as a logic while calling your API. – Naresh Joshi Jul 07 '19 at 13:23
1

I find the path product/{id}/update questionable, because you could achieve similar behavior by mapping @Put-request to product/{id} itself. The READ, WRITE differentiation is already explicit by the Request-mapping. Also, whether or not using verbs in restful urls is a topic for itself.

Assuming you could use plural endpoints, this could look like /products/{id}.

Because you want to batch/bulk update products, you could map @Put-requests to /products now, with a list of updated Products in the RequestBody. Keep in mind, that this somewhat complicates the Response, as you may have to return Http-207 for answering the correct status of the update for each element in the list.

I want 1 logical endpoint for update

You can have a logical service method for this, but not endpoints really. You already mentioned the problem of /{id} in your path for bulk updates. If you really, really need to, I would remove the @Put-mapping from /products/{id} and redirect to /products where the update content would be a single element list, or a little more sophisticated, distinguished by a mediaType (what again means two endpints, but a single url).

Edit: I just happen to understand the VO-issue. You are not updating Products, but parts of it (the name RequestVO was misleading me). This smells like a @Patch-mapping to me, where parts of a Product get updated. So I still would use /products but with a @Patch-mapping.

When a client needs to replace an existing Resource entirely, they can use PUT. When they’re doing a partial update, they can use HTTP PATCH.

This brings up another issue, use @Post only if the id is unknown (usually before something is CREATED and gets an id assigned, for UPDATES use @Put and reuse the assigned id) Using post is technically doable, but because of idempotece not advisable.

sschrass
  • 7,014
  • 6
  • 43
  • 62
  • Thank you for answering, To sum up I'll remove Path and use `@PUT` for supporting idempotent and move id to VO. Is there safe to send upto 100K IDs or should API limit it? – Ori Marko Jul 07 '19 at 09:27
  • I probably would support chunks á 1k or smaller, greatly depending on you situation. Keep in mind that some of the updates could fail for some reason and you must/should tell the client how the updates turned out. Instead of Put have a look at Patch as it looks more suitable in case of VOs not Products being communicated. Patch can be idempotent too, depending on your implementation. Yes, the productId should be in the requestVO as key to the product. – sschrass Jul 07 '19 at 09:42
  • My main concern is that if I want (current requirement) to do the same operation e.g. name=doA and toggle=true for all Ids, it seems like duplicating data for each id – Ori Marko Jul 07 '19 at 11:03
  • You mean duplication of operation params in the RequestBody? This would be up to you. You could design an PatchVO, that requires a list of Ids and a single operation params. instead of having a list with id and param each. I haven't enough insight into your domain, but this may could be designed differently. The least that can be done is documenting the PatchVO and its implications to not confuse clients. – sschrass Jul 07 '19 at 11:33
0

Why not just pass the list of your IDs in the body of your request as JSON array? the code would be:

@POST
@Path("/update/ids")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(@RequestBody List<Integer> ids, List<RequestVo> requestVo) {
...
}
Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
  • What happens when size of ids different then requestVo? how can you map correctly between them? – Ori Marko Jul 07 '19 at 12:23
  • Well, ideally you would just include id as one of the properties of RequestVo and then you don't need to map. I was just pointing out an obvious solution of passing long info in the request body instead of URL – Michael Gantman Jul 07 '19 at 12:48