Is there a RESTful way to implement this whilst maintaining atomicity of the operation?
Short Answer
Just use POST
Medium Answer
Seriously; it is okay to use POST.
POST serves many useful purposes in HTTP, including the general purpose of “this action isn’t worth standardizing.” -- Fielding, 2009
Long Answer
REST doesn't have collections. REST has resources and representations, and a uniform interface that includes a vocabulary of self-descriptive messages that are common to all resources.
HTTP doesn't have collections either. It defines a vocabulary of standardized self-descriptive messages that are common all over the web. In other words, when interpreting a message we don't need any specialized knowledge of either the producer or consumer of the message. GET means GET, HEAD means HEAD, 200 means OK, 404 means Not Found, conditional requests, authentication, caching... it's all the same everywhere.
The application domain of HTTP is the transfer of documents over a network. We're just sending each other little copies of documents telling the other guy what to do. If I want you to move a document (A) into a "collection" (B), then I send to you a document (C) that looks something like:
Please move document A into collection B
All of the other stuff -- the method-token, the headers values, the response codes -- that's all meta-data of the document transfer domain; information that we attached to the document so that general purpose HTTP components can do useful things.
In other words, the meta-data allows us to take advantage of the intelligence that we've built into the document transfer application so that we get more value out of it than mere transport.
So, how can we surface the idea of "collection" so that our document transfer application can take advantage of it?
There are at least two answers to this. One answer is WebDAV, which offers a definition of collection resources. And no joke, if what you want is remote web content authoring, you should give it a serious look. RFC 4918 defines the standard semantics for the COPY and MOVE method-tokens.
The other, and I think more common, approach is to describe the relationships between resources. We've got web linking, which gives us standized forms for describing Target/Context/Relation triples. And we've got RFC 6573, which defines the semantics of the item and collection link relation types.
So we can get kind of close: if we have a representation schema like Collection+JSON which has a mechanism for describing a document's own links, then any client familiar with that schema will be able to identify the link relations within it, and those link relations can be changed by sending to the server a representation of the document with the new link values using the same messages that we would use for any other edit (ie: PUT/PATCH). The server can easily understand the request, and decide on its own whether or not to fulfill it.
But it's only close; it doesn't generalize particularly well (where do you embed link relations in a CSV file?). So that leaves you either sending multi-part documents around (ugh) or trying to embed the relations in the headers. And yes, we've already standardized a header for that.
But what we haven't defined is how the semantics of an HTTP request are further refined when link relation headers are present in the request.
And that means that general purpose components aren't going to have a clue what is going on, and aren't going to be able to act intelligently.
And that leaves you with two choices
- drive the specification and adoption of new standard(s)
- recognizing that the action isn't worth standardizing
Discussion
The problem is that POST isn't idempotent.
Yes, but let's look carefully at what that means here.
The standard doesn't say "POST is restricted to use for non-idempotent actions". It is perfectly satisfactory to use POST for idempotent actions that aren't worth standardizing.
What it says is that general-purpose components are not allowed to assume that POST is idempotent; that the idempotent semantic constraint doesn't apply to all POST messages. Because the constraint is missing, our document transfer application can't do intelligent things like autonomously retry lost messages.
That's something we could potentially fix via a new method token (let's call it TSOP); you could write up the semantics of TSOP, and guide it through the standards process, and get it registered with IANA, and drive adoption. Ta-da! You now have general-purpose browsers that will resubmit lost form submissions.
Failing that, you are left to look into other registered methods that are unsafe and idempotent. Of the obvious ones, you are limited to PUT.
And PUT is fine -- every general purpose component in the world will understand the document transfer semantics of
PUT /a36c586a-cf90-46aa-b098-b3ffa038bebd HTTP/1.1
Content-Type: text/plain
Please move document A into collection B
So we have a resource model that is documents about changes to documents, and affordances so that clients can find the documents that are documents about documents, and we can design all that.
I am trying to understand exactly what isn't achieved here.
The piece that we don't have is any sort of standardized language for describing changes to previously cached resources in an HTTP response. We only have invalidate, and that only applies to a limited number of meta-data elements have other use.
Consider that 200, in response to a PUT, means that the payload of the payload of the response is a representation of the status of the action. So we might imagine that the response payload to our PUT request could look something like:
SUCCESS
Document /A has been moved to /B
Document /A/1 has been moved to /B/1
Document /B/2 has been removed
Document /C/4 has been moved to /K/9
Of course, we're just making up a language here -- if we want adoption, then this idea would need to be tightened up in to a standard. That might look like using the link header (these do appear to be link triples), and standardizing a new link relation, and then standardizing the semantics of link headers with that relation in the context of an HTTP response.
And then driving adoption.
mv
, then that does not perform a single operation. It depends on the circumstance. Within the same filesystem, it will typically perform arename
, but between different filesystems, there is no such thing, it performs acreat
followed bywrite
, followed by anunlink
of the original file … in other words it performs roughly the same sequence that acp
followed byrm
would perform.mv
command is simply intended to illustrate the desire for an operation that apparently makes a resource disappear under its old path and reappear under a new path, without changes to its content, and also history-preserving, and somewhat atomic.