RESTful resource - accepts a list of objects
I'm building a collection of RESTful resources that work like the following: (I'll use "people" as an example):
GET /people/{key} - returns a person object (JSON)
GET /people?first_name=Bob - returns a list of person objects who's "first_name" is "Bob" (JSON)
PUT /people/{key} - expects a person object in the payload (JSON), updates the person in the datastore with the {key} found in the URL parameter to match the payload. If it is a new object, the client specifies the key of the new object.
I feel pretty comfortable with the design so far (although any input/criticism is welcome).
I'd also like to be able to PUT a list of people, however I'm not confident in the RESTfulness of my design. This is what I have in mind:
PUT /people - expects a list of objects in JSON form with keys included in the object ("key":"32948"). Updates all of the corresponding objects in the datastore.
This operation will be idempotent, so I'd like to use "PUT". However its breaking a rule because a GET request to this same resource will not return the equivalent of what the client just PUT, but would rather return all "people" objects (since there would be no filters on the query). I suspect there are also a few other rules that might be being broken here.
Someone mentioned the use of a "PATCH" request in an earlier question that I had: REST resource with a List property
"PATCH" sounds fantastic, but I don't want to use it because its not in wide use yet and is not compatible with a lot of programs and APIs yet.
I'd prefer not to use POST because POST implies that the request is not idempotent.
Does anyone have any comments / suggestions?
Follow-up:::
While I hesitated to use POST because it seems to be the least-common-denominator, catch-all for RESTful operations and more can be said about this operation (specifically that it is idempotent), PUT cannot be used because its requirements are too narrow. Specifically: the resource is not being completely re-written and the equivalent resource is not being sent back from a GET request to the same resource. Using a PUT with properties outside of it's specifications can cause problems when applications, api's, and/or programmers attempt to work with the resource and are met with unexpected behavior from the resource.
In addition to the accepted answer, Darrel Miller had 开发者_C百科an excellent suggestion if the operation absolutely had to be a PUT and that was to append a UUID onto the end of the resource path so an equivalent GET request will return the equivalent resource.
POST
indicates a generic action other than GET
, PUT
, and DELETE
(the generic hashtable actions). Because the generic hashtable actions are inappropriate, use POST
. The semantics of POST
are determined by the resource to which an entity is POST
ed. This is unlike the semantics of the generic hashtable methods, which are well-known.
POST /people/add-many HTTP/1.1
Host: example.com
Content-Type: application/json
[
{ "name": "Bob" },
{ "name": "Robert" }
]
Using PUT is definitely the wrong verb in this case. POST is meant to do exactly what you are asking. From the HTTP specification:
The fundamental difference between the POST and PUT requests is reflected in the different meaning of the Request-URI. The URI in a POST request identifies the resource that will handle the enclosed entity. That resource might be a data-accepting process, a gateway to some other protocol, or a separate entity that accepts annotations. In contrast, the URI in a PUT request identifies the entity enclosed with the request -- the user agent knows what URI is intended and the server MUST NOT attempt to apply the request to some other resource...
As such, if you want to update multiple resources in a single call, you have to use POST.
Just be cause PUT is required to be idempotent and POST is not, does not mean that POST cannot be idempotent. Your choice of HTTP verb should not be based on that, but based on the relationship of the requested resource and the resource acted upon. If your application is directly handling the resource requested, use PUT. If it is acting on some other resource (or resources, as in your case), use POST.
I really don't see any easy way you could use PUT to create an arbitrary set of people. Unless, you are prepared to have the client generate a GUID and do something like,
PUT /PeopleList/{1E8157D6-3BDC-43b7-817D-C3DA285DD606}
On the server side you could take the people from the list and add them to the /People
resource.
A slight variation to this approach would be to get the server to include a link such as
<link rel="AddList" href="/PeopleList/{1E8157D6-3BDC-43b7-817D-C3DA285DD606}"/>
in the People resource. The client would need to know that it needs to PUT a list of people to the AddList
link. The server would need to make sure that each time it renders the /People resource it creates a new url for the AddList link.
Regarding Darren Miller's suggestion of using PUT to a GUID (I can't comment...), the point of using PUT would be to achieve idempotency for the operation. The litmus test if idempotency would be this conversation between the client and server:
PUT /PeopleList/{1E8157D6-3BDC-43b7-817D-C3DA285DD606}
204 NO CONTENT
(indicates that it all went well)- client loses connection and doesn't see the
204
- client automatically retries
PUT /PeopleList/{1E8157D6-3BDC-43b7-817D-C3DA285DD606}
How would the server differentiate the two? If the GUID is "used up" so to speak then the server would have to respond 404
or 410
. This introduces a tiny bit of conversational state on the server to remember all the GUIDs that have been used.
Two clients would often see the same I guess, because of caching or simply keeping stale responses around.
I think a smart solution is to use POST to create an (initially empty, short lived) holding area for a resource to which you can PUT, i.e. clients need to POST to create the GUID resource instead of discovering it via a link:
POST /PeopleList/CreateHoldingArea
201 CREATED
andLocation: /PeopleList/{1E8157D6-3BDC-43b7-817D-C3DA285DD606}
PUT /PeopleList/{1E8157D6-3BDC-43b7-817D-C3DA285DD606}
This would mean that the lost idempotency would not result in much overhead; clients simply create new GUIDs (by POSTing) if they didn't see the initial 201 CREATED
response. The "tiny bit of conversational state" would now only be the created but not yet used holding areas.
The ideal solution would of course not require any conversational state on the server, but it eludes me.
精彩评论