tags:

views:

165

answers:

5

I am writing a RESTful service for a customer management system and I am trying to find the best practice for updating records partially. For example, I want the caller to be able to read the full record with a GET request. But for updating it only certain operations on the record are allowed, like change the status from ENABLED to DISABLED. (I have more complex scenarios than this)

I don't want the caller to submit the entire record with just the updated field for security reasons (it also feels like overkill).

Is there a recommended way of constructing the URIs? When reading the REST books RPC style calls seem to be frowned upon.

If the following call returns the full customer record for the customer with the id 123

GET /customer/123
<customer>
    {lots of attributes}
    <status>ENABLED</status>
    {even more attributes}
</customer>

how should I update the status?

POST /customer/123/status
<status>DISABLED</status>

POST /customer/123/changeStatus
DISABLED

...

Update: To augment the question. How does one incorporate 'business logic calls' into a REST api? Is there an agreed way of doing this? Not all of the methods are CRUD by nature. Some are more complex, like 'sendEmailToCustomer(123)', 'mergeCustomers(123, 456)', 'countCustomers()'

POST /customer/123?cmd=sendEmail

POST /cmd/sendEmail?customerId=123

GET /customer/count 

Thanks Frank

+5  A: 

You should use POST for partial updates.

To update fields for customer 123, make a POST to /customer/123.

If you want to update just the status, you could also PUT to /customer/123/status.

Generally, GET requests should not have any side effects, and PUT is for writing/replacing the entire resource.

This follows directly from HTTP, as seen here: http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods

wsorenson
That much I've figured. What I am unsure about is how to structure the URI. If the following call returns the customer GET /customer/123How do I update the customer status? POST /customer/123/status status=DISABLED POST /customer/changeStatus status=DISABLED
magiconair
You could POST to /customer/123, or do a PUT to /customer/123/status.
wsorenson
@wsorenson: I'm just now reading "Effective REST Services with .NET", and I thought sure that POST always creates a new resource "under" the resource specified in the URL. Did I misread that?
John Saunders
@John Saunders POST doesn't have to necessarily create a new resource that is accessible from a URI: http://tools.ietf.org/html/rfc2616#section-9.5
wsorenson
@wsorensen: I know it need not result in a new URL, but still thought a POST to `/customer/123` should create the obvious thing that is logically under customer 123. Maybe an order? PUT to `/customer/123/status` seems to make better sense, assuming the POST to `/customers` implicitly created a `status` (and assuming that's legit REST).
John Saunders
@John Saunders: practically speaking, if we want to update a field on a resource located at a given URI, POST makes more sense than PUT, and lacking an UPDATE, I believe it is often used in REST services. POST to /customers may create a new customer, and a PUT to /customer/123/status may align better with the word of the specification, but as for best practices, I don't think there is any reason not to POST to /customer/123 to update a field - it's concise, makes sense, and doesn't strictly go against anything in the specification.
wsorenson
A: 

Check out http://www.odata.org/

It defines the MERGE method, so in your case it would be something like this:

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

Only the status property is updated and the other values are preserved.

Max Toro
Is `MERGE` a valid HTTP verb?
John Saunders
Look at PATCH - that is soon-to-be standard HTTP and does the same thing.
Jan Algermissen
@John Saunders Yes, it's an extension method.
Max Toro
+3  A: 

You basically have two options:

  1. Use PATCH (but note that you have to defined you own media type that specifies what will happen exactly)

  2. Use POST to a sub resource and return 303 See Other with the Location header pointing to the main resource. The intention of the 303 is to tell the client: "I have performed your POST and the effect was that some other resource was updated. See Location header for whoch resource that was." POST/303 is intended for iterative additions to a resources to build up the state of some main resource and it is a perfect fit for partial updates.

Jan

Jan Algermissen
OK, the POST/303 makes sense to me. PATCH and MERGE I couldn't find in the list of valid HTTP verbs so that would require more testing. How would I construct an URI if I want the system to send an email to customer 123? Something like a pure RPC method call that doesn't change the state of the object at all. What is the RESTful way of doing this?
magiconair
PATCH: http://tools.ietf.org/html/draft-dusseault-http-patch
Jan Algermissen
I do not understand the email URI question. Do you want to implement a gateway that you can POST to to have it send an email or are you looking for mailto:[email protected]?
Jan Algermissen
The service doesn't just offer CRUD calls for entities like the customer in this example. Some operations are pure method calls like 'sendEmailToCustomer(123)', 'mergeCustomerAccounts(123, 456)' or 'countCustomers'. You could call that the 'business logic'. How do you integrate such calls into a RESTful service properly?
magiconair
@magiconair I asked the same question some time ago, http://stackoverflow.com/questions/1937006
Max Toro
Neither REST nor HTTP has anything to do with CRUD aside from some people equating the HTTP methods with CRUD. REST is about manipulating resource state by transferring representations. Whatever it is you want to achieve you do by transferring a representation to a resource with the appropriate semantics.Beware of the terms 'pure method calls' or 'business logic' as they too easily imply 'HTTP is for transport'. If you need to send an email, POST to a gateway resource, if you need to merge to accounts, create a new one and POST representations of the other two, etc.
Jan Algermissen
@Jan: Merging two accounts using three POST calls doesn't work for me as I want to execute this in a single transaction on the database and performance wise it doesn't make much sense either. This is a function that multiple clients use and if I implement it the way you suggest then every client has to implement that logic separately. So from that perspective it is just a method call and yes it is 'business logic' as this operation is part of what the service has to offer for the clients to work.
magiconair
My suggestion was really only an example. There are other ways to do that, depending on your requirements. Maybe POST repr of account A to account B or a combined repr of A and B to new account. What matters from a REST POV is that the meaning of the message must not be a function of resource state. IOW, all state must be send in the message itself (the message self-descriptiveness constraint is at work here). Now, how much you depend on your system to really be RESTful is of course a different question. Just be aware that you break REST when you do and thus might not have the desired props.
Jan Algermissen
Maybe this is helpful/interesting: http://nordsc.com/ext/classification_of_http_based_apis.html in that context.
Jan Algermissen
A: 

I am running into a similar problem. PUT on a sub-resource seems to work when you want to update only a single field. However, sometimes you want to update a bunch of things: Think of a web form representing the resource with option to change some entries. The user's submission of form should not result in a multiple PUTs.

Here are two solution that I can think of:

  1. do a PUT with the entire resource. On the server-side, define the semantics that a PUT with the entire resource ignores all the values that haven't changed.

  2. do a PUT with a partial resource. On the server-side, define the semantics of this to be a merge.

2 is just a bandwidth-optimization of 1. Sometimes 1 is the only option if the resource defines some fields are required fields (think proto buffers).

The problem with both these approaches is how to clear a field. You will have to define a special null value (especially for proto buffers since null values are not defined for proto buffers) that will cause clearing of the field.

Comments?

A: 

Things to add to your augmented question. I think you can often perfectly design more complicated business actions. But you have to give away the method/procedure style of thinking and think more in resources and verbs.

mail sendings


POST /customers/123/mails

payload:
{from: [email protected], subject: "foo", to: [email protected]}

The implementation of this resource + POST would then send out the mail. if necessary you could then offer something like /customer/123/outbox and then offer resource links to /customer/mails/{mailId}.

customer count

You could handle it like a search resource (including search metadata with paging and num-found info, which gives you the count of customers).


GET /customers

response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}

manuel aldana