How do you RESTfully Create and Delete at the Same Time?
Let's say I have a trip planning application, and each trip is composed of "path" resources (representing a route to be driven, for example) composed as a series of points. I can CRUD these resources using requests like so (just an example):
POST /trips/1234/paths
<Path>
<Point>32,32</Point>
<Point>32,34</Point>
<Point>34,34</Point>
</Path>
DELETE /trips/1234/paths/3
Now consider that I want to be able to split a Path into two paths. In the aobve example, I might want to pick the point (32,34) to split on, which would result in two paths - one ends at that point, one starts at that point. What this means is that a single action creates two new resources, and simul开发者_运维问答taneously, deletes another (the path that was split).
So if the path in the example above was the only path in the system, and I split it with a single call, the system would now contain two new paths and the original would be gone. Example:
<Path>
<Point>32,32</Point>
<Point>32,34</Point>
</Path>
<Path>
<Point>32,34</Point>
<Point>34,34</Point>
</Path>
I'm struggling with how this would be handled RESTfully. How does one handle calls that result in multiple resources being created/modified/deleted and communicate that to the caller?
I can definitely figure it out with multiple calls (two POSTs to create the new paths and a DELETE to remove the original), but I want this to be a single call.
It's not exactly pretty but this can be solved with a "processing resource"
POST /PathSplitter?SplitAt=(32,34)
Content-Type: application/vnd.acme.path+xml
<Path>
<Point>32,32</Point>
<Point>32,34</Point>
<Point>34,34</Point>
</Path>
=>
200 OK
Content-Type: application/vnd.acme.pathlist+xml
<Paths>
<Path href="/trips/1234/paths/4"/>
<Path href="/trips/1234/paths/5"/>
</Path>
I would try to implement it like @Darrel Miller's answer yet make it a little less RPCish. The idea is to create a PathSplit resource that guides the client each step of the way to complete the transaction. This way the client knows the result of each step and knows hows to back out if there is an error along the way.
Something like this should do the trick.
POST /pathsplit HTTP/1.1
Content-Type: application/vnd.acme.pathsplit+xml
<pathsplit xmlns="http://schema.acme.com">
<path>
<id>123</id>
</path>
<splitpoint>
<point>32,34</point>
</splitpoint>
</pathsplit>
=>
HTTP/1.1 201 Created
Content-Type: application/vnd.acme.pathsplit+xml
Location:
http://acme.com/pathsplit/11
<pathsplit xmlns="http://schema.acme.com">
<path>
<id>123</id>
</path>
<splitpoint>
<point>32,34</point>
</splitpoint>
<newpaths>
<Path>
<Point>32,32</Point>
<Point>32,34</Point>
</Path>
<Path>
<Point>32,34</Point>
<Point>34,34</Point>
</Path>
<createuri>http://acme.org/trips/paths</createuri>
</newpaths>
<oldpath>
<uri>http://acme.org/trips/paths/123</uri>
</oldpath>
<status>in-progress</status>
<pathscreated>0</pathscreated>
</pathsplit>
With something like this the client can always GET
the pathsplit resource to see what actions to take. In this example it would create both new paths
following the createuri
link and sending a POST
request for each new path
in newpaths
. The server updates the status as each new resource is created.
Once the status has pathscreated
of 2, the client can delete the old path
by following the uri and sending the DELETE
verb. Finally, it would delete the pathsplit resource, thus indicating that everything succeeded.
At every step along the way, in case of an error, or in the all too common case of a network outage, the client always knows what state the server is in and what state the transaction is in.
The pathsplit resource remains available as long as the transaction hasn't completed. The client also has the option of reverting a transaction that has not yet completed. Again, since it knows what operations have completed successfully and which haven't, it can always back out the transaction.
The short answer is that making this a single call just isn't really RESTful; unless you remodel your representations, my understanding of REST and HATEOAS is that this sort of process is trying to inject a verb into the server-side representation, which is a big no-no. That's not to say that what you want to do can't be done RESTfully, but I think it involves a bit of a refactor of your representation tree.
Then there's the question of "why" you want this to be a single call; I can see why it makes sense for it to be that from a transactional point of view. That would seem to imply exposing a transaction REST representation to the client, so the client could properly manage the transactioning. I'm not saying that that's simple, though...
A solution with strict CRUD operations doesn't seem useful here to me. However, the operations on resources don't necessarily need to be CRUD operations. Many people even think of this as a bad way to design a REST interface (just google for rest+crud).
So, I think it's totally okay when you define a split operation as one REST call. As you apply this operation on one resource only (the original path), you can define this operation directly on it. No need to define it on multiple resources.
精彩评论