12

I'm trying to move a resource from /buckets/1 to /buckets/2 such that:

Initial state

  • /buckets/1 = foo
  • /buckets/2 = HTTP 404

Final state

  • /buckets/1 = HTTP 301 to /buckets/2
  • /buckets/2 = foo

What's a RESTful way of asking the server to move a resource in this manner?

Gili
  • 86,244
  • 97
  • 390
  • 689
  • Possible duplicate of [Move resource in RESTful architecture](https://stackoverflow.com/questions/6512677/move-resource-in-restful-architecture) – Roman Vottner Nov 01 '19 at 16:27

3 Answers3

15

Answering my own question:

  • For the sake of this discussion let's assume that we are storing "balls" in buckets
  • The first thing to notice is that a ball's life-cycle is not determined by its containing bucket (moving a ball from one bucket to another does not delete the old ball). As such we should promote balls to a top-level resource: /balls
  • REST seems to work best in terms of symbolic links as opposed to inline values, so instead of GET /buckets/1 returning the value of the ball in the bucket let's have it return the URI of the ball instead.

We can then move balls as follows:

(examine original state)
GET /buckets/1: "balls = {'/balls/1'}"
GET /buckets/2: "balls = {}"
GET /balls/1: "bucket = /buckets/1"

(move ball into bucket #2)
PUT /balls/1: "bucket = /buckets/2"

(examine new state)
GET /buckets/1: "balls = {}"
GET /buckets/2: "balls = {'/balls/1'}"
GET /balls/1: "bucket = /buckets/2"

End-result: the ball's identity remains consistent as it moves across buckets and (most importantly) this operation is atomic.

Gili
  • 86,244
  • 97
  • 390
  • 689
  • 2
    I like your answer, but I don't understand your mention of atomicity. It seems to me that you have two requests. Won't the world be in a strange state in between the two requests? (The ball resides in 2 buckets, which isn't really the state you want to be in). – Daniel Yankowsky May 09 '11 at 21:26
  • 1
    How do you tell the different between PUT that move a ball between bucket and a PUT that update the ball? – Ido Ran Jun 28 '11 at 20:22
  • @Ido, if a user PUTs a bucket which differs from the current value then it is a move. Keep in mind that you could move a ball and update its state at the same time. – Gili Jun 29 '11 at 15:31
  • This works fine if you send a full resource that has an updated id. If you just want to move the resource on the server the PUT request would have to somehow account for that. – tcurdt Apr 02 '21 at 08:20
4
  1. GET /buckets/1
  2. DELETE /buckets/1
  3. PUT /buckets/2 {data returned by #1}

That doesn't make the server 301, though. The alternative would be to use the WebDAV MOVE method, i.e. by creating your own @MOVE annotation using the @HttpMethod annotation:

import ...;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("MOVE")
public @interface MOVE {}

but doing so breaks REST's architectural principle of using HTTP as a uniform interface (RESTful Java).

Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • Interesting answer but not the correct one because, by your own assertion, the above is not RESTful. – Gili Apr 05 '11 at 18:08
  • Which isn't RESTful, using `@MOVE`? – Matt Ball Apr 05 '11 at 18:12
  • @Matt, you wrote: "but doing so breaks REST's architectural principle of using HTTP as a uniform interface." Aren't you implying that @MOVE isn't RESTful? Please clarify. Also, I don't think that a combination of DELETE and PUT is RESTful in the sense that you're implying that /buckets/2 refers to a different object from /buckets/1 which is not true. – Gili Apr 05 '11 at 18:13
  • 1
    Did you read the HTTP Method Extensions section in RESTful Java? – Matt Ball Apr 05 '11 at 18:18
  • @Matt: Yes, I did. My understanding is that they support defining new methods so long as they are well-established in the community (versus everyone defining their own custom methods). I don't understand the text of your answer though. Are you saying that introducing @MOVE is RESTful or not? – Gili Apr 05 '11 at 18:31
  • I found a very interesting discussion thread where Fielding discusses @MOVE here: http://tech.groups.yahoo.com/group/rest-discuss/message/5874 and http://tech.groups.yahoo.com/group/rest-discuss/message/5867 – Gili Apr 05 '11 at 18:36
  • 2
    Nice find. My take on the matter is that there isn't a really RESTful way to do "move" because "move" isn't a RESTful operation. I think of the RESTful operations having direct mappings to, for example, SQL operations (`GET -> SELECT`, `POST -> INSERT`, etc.) and there's certainly no "move" SQL operation. – Matt Ball Apr 05 '11 at 18:53
  • The guys on rest-discuss seem to agree with you. They argue that relationships between resources belong in the resource representation (think HTML links) as opposed to something you need to retain using a MOVE command. That seems to fly in the face of what HTTP 301 is all about though, doesn't it? How do I prevent clients from mistakenly believing that a resource has been deleted when it was really moved? – Gili Apr 05 '11 at 18:59
  • How about the following approach? Every resource has a canonical URI. Instead of adding the canonical resource into the bucket, we add a symbolic link. When a resource is moved, we delete the symbolic link from one bucket and add a new symbolic link into the second bucket. By instructing clients to only communicate with the canonical URI we guarantee that clients will never mistakenly believe that a resource has been deleted. In fact, an even better solution to promote the resource to the top-level. Instead of adding /buckets/1 into /buckets we should add /foo/1 instead. – Gili Apr 05 '11 at 19:09
  • This isn't very efficient when moving a huge resource, as it requires `2n` of network traffic (where `n` is the resource's size). (ie: a 2GiB file requires 4GiB of network transfer). – WhyNotHugo Aug 21 '12 at 10:24
-1
  1. Create original resource:

    PUT /bucket/1

  2. Call server procedure that responsible for moving resources:

    POST /bucket/1/move-to/2

  3. Original resource path now should return Moved status code:

    GET /bucket/1 HTTP 301

  4. Resource now should be available on new path:

    GET /bucket/2 HTTP 200

ujeenator
  • 26,384
  • 2
  • 23
  • 28