What is the restful way to represent a resource cl

2020-02-02 06:31发布

I have REST API that exposes a complex large resource and I want to be able to clone this resource. Assume that the resource is exposed at /resources/{resoureId}

To clone resource 10 I could do something like.

  • GET /resources/10
  • POST /resources/ body of put containing a duplicate of the representation by GET /resources/10 without the id so that the POST creates a new resource.

The problem with this approach is that the resource is very large and complex it really makes no sense to return a full representation to the client and then have the client send it back as that would be just a total waste of bandwidth, and cpu on the server. Cloning the resource on the server is so much easier so I want to do that.

I could do something like POST /resources/10/clone or POST resources/clone/10 but both of these approaches feel wrong because the verb in the URL.

What is the most "restful/nouny" way to build url that can be used in this type of situation?

标签: rest
5条回答
Explosion°爆炸
2楼-- · 2020-02-02 06:56

I think POST /resources/{id} would be a good solution to copy a resource.

Why?

  • POST /resources is the default REST-Standard to CREATE a new resource
  • POST /resources/{id} should not be possible in most REST apis, because that id already exists - you will never generate a new resource with you (the client) defining the id. The server will define the id.

Also note that you will never copy resource A on resource B. So if you want to copy existing resource with id=10, some answers suggest this kind of thing:

POST /resources?sourceId=10

POST /resources?copyid=10

but this is simpler:

POST /resources/10

which creates a copy of 10 - you have to retrieve 10 from storage, so if you don't find it, you cannot copy it = throw a 404 Not Found.

If it does exist, you create a copy of it.

So using this idea, you can see it does not make sense to do the following, copying some b resource to some a resource:

POST /resources?source=/resources/10
POST /resources-a?source=/resources-b/10

So why not simply use POST /resources/{id}

  • It will CREATE a new resource
  • The copy parent is defined by the {id}
  • The copy will be only on the same resource
  • It's the most REST-like variant

What do you think about this?

查看更多
狗以群分
3楼-- · 2020-02-02 07:00

You want to create a copy of a specific resource. My Approach in that case, would be to use the following endpoint : POST /resources/{id}/copy, read it "create a copy of resource {id}"

查看更多
男人必须洒脱
4楼-- · 2020-02-02 07:07

Francis' answer is a great one and probably what you're looking for. With that said, it's not technically RESTful since (as he says in the comments) it does rely on the client providing out of band information. Since the question was "what is the restful way" and not "what is a good way/the best way", that got me thinking about whether there is a RESTful solution. And I think what follows is a RESTful solution, although I'm not sure that it's necessarily any better in practice.

Firstly, as you've already identified, GET followed by POST is the simple and obvious RESTful way, but it's not efficient. So we're looking for an optimization, and we shouldn't be too surprised if it feels a little less natural than that solution!

The POST + sourceId solution creates a special URL - one that points not to a resource, but to an instruction to do something. Any time you find yourself creating special URLs like that, it's worth considering whether you can work around the need to do that by simply defining more resources.

We want the ability to copy

resources/10

What if we come up with another resource:

resources/10/copies

...and the definition of this resource is simply "the collection of resources that are copies of resource/10".

With this resource defined, we can now re-state our copy operation in different terms - instead of saying "I want the server to copy resources/10", we can say "I want to add a new thing to the collection of things that are copies of resources/10".

This sounds strange, but it fits naturally into REST semantics. For instance, let's say this resource currently looks like this (I'm going to use a JSON representation here):

[]

We can just update that with a POST or PATCH [1]:

POST resources/copies/10
["resources/11"]

Note that all we're sending to the server is metadata about a collection, so it's very efficient. We can assume that the server now knows where to get the data to copy, since that's part of the definition of this resource. We can also assume that the client knows that this results in a new resource being created at "resources/11" for the same reason.

With this solution, everything is defined clearly as a resource, and everything has one canonical URL, and no out-of-band information is ever required by the client.

Ultimately, is it worth going with this strange-feeling solution just for the sake of being more RESTful? That probably depends on your individual project. But it's always interesting to try and frame the problem differently by creating different resources!

[1] I don't know if makes sense to allow GET on "resources/10/copies". Obviously as soon as either the original resource or a copy of it change, the copy isn't really a copy any more and shouldn't be in this collection. Implementation-wise, I don't see the point in burdening the server with keeping track of that, so I think this should be treated as an update-only resource.

查看更多
我欲成王,谁敢阻挡
5楼-- · 2020-02-02 07:10

Since there is no copy or clone method in HTTP, it's really up to you what you want to do. In this case a POST seems perfectly reasonable, but other standards have taken different approaches:

Both of these approaches assume that you know the destination URI. Your example seems to lack a known destination uri, so you pretty much must use POST. You can't use PUT or COPY because your creation operation is not idempotent.

If your service defines POST /resources as "create a new resource", then why not simply define another way to specify the resource other than as the body of the POST? E.g. POST /resources?source=/resources/10 with an empty body.

查看更多
时光不老,我们不散
6楼-- · 2020-02-02 07:13

Will just put it out there, if this can be of help to anyone.
We had a similar scenario, where we were providing "clone vm" as a feature for scaling out on our IaaS offering. So if a user wanted to scale out they would have to hit POST: /vms/vm101 endpoint with request_body being

{"action": "clone", // Specifies action to take, since our users can do couple of other actions on a vm, like power_off/power_on etc.
 "body": {"name": [vm102, vm103, vm104] // Number of clones to make
          "storage": 50, ... // Optional parameters for specifying differences in specs one would want from the base virtual machine 
          }

and 3 clones of vm101 viz. vm102, vm103 and vm104 would be spinned.

查看更多
登录 后发表回答