>
You want to use PUT to update your resource? OK, but some Holy Specifications state that the data input has to be a “complete resource”, i.e follow the same schema as the corresponding GET output.If you were working in a strongly typed language with RPC calls, you would see the same problems, or symptoms thereof. For example, if you had the two RPC calls storeFoo and retrieveFoo, you'd expect them to both take Foo objects, no? Something like,
storeFoo(name: String, foo: Foo)
retrieveFoo(name: String) -> Foo
and PUT/GET in HTTP yearns for the same dichotomy.
> So what do you do with the numerous read-only parameters returned by GET (creation time, last update time, server-generated token…)? You omit them and violate the PUT principles?
Yes, and just call it POST, since it's no longer symmetric and violates PUT principles. REST has nothing against POST. Again, this problem would be reflected similarly in RPC:
storeFoo(name: String, foo: PartialFoo)
retrieveFoo(name: String) -> Foo
(or perhaps storeFoo ignores some fields on Foo, etc.)
> creation time
This is a fantastic example, because I actually had this problem w/ a non-RESTful API. We took a generic sort of "create record" and recorded the current time as the "start" time. The problem was when the device lost network connectivity (which was essentially always, just a matter of "how bad is the latency): the creation of the record would lag, sometimes by hours, and the eventual "creation time" it was tagged with was wrong. We should have trusted the device, because it knew much better than the server when the actual record was created.
Now, this isn't going to be the case all the time; sometimes you literally want just the time the DB INSERT statement happened, and that's fine.
> Pick your poison, REST clearly has no clue what a read-only attribute it
POST a "create" request, GET has the newly created fields. Symmetric PUT/GET is nice, but show me where in HTTP it is recorded as an absolute.
> Meanwhile, a GET is dangerously supposed to return the password (or credit card number)
Not only does REST not dictate this, nobody in their right mind should do this. GETs just return the resource. What that resource is, what data is represented — that's up to you in HTTP just as it is in RPC.
> lots of resource parameters are deeply linked or mutually exclusive(ex. it’s either credit card OR paypal token, in a user’s billing info)
If your request looks like,
"paypal_token": ...
"credit_card": ...
Then your RPC would look like,
struct PaymentDetails {
paypal_token: ...
credit_card: ...
}
updatePaymentDetails(..., new_details: PaymentDetails)
and you're in the same hot water, again, just with RPC. If your type system allows it, you can make them mutually exclusive there, perhaps something like,
enum PaymentDetails {
Paypal(token),
CreditCard(card_number),
}
but then, that cleanly translates back into a RESTful API's information too. Now, JSON is typically used, and it doesn't really expose a real sum type, which is a shame. You can work around it w/ something like,
"payment_details": {
"type": "paypal",
"token": ...,
}
and it works well enough. If you can't express it in the type system (in
either RPC or REST) then you have to do some validation, but that's true regardless of whether RPC or REST is in use.
> you’d violate specs once more: PATCH is not supposed to send a bunch of fields to be overridden
…how is that a violation of the spec?
> The PATCH method requests that a set of changes described in the request entity be applied to the resource
> With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version.
That's exactly what sending a subset of fields is. It's an adhoc new media type describing a set of changes.
> So here you go again, take your paperboard and your coffee mug, you’ll have to specify how to express these instructions, and their semantics.
No more than you would an RPC call, AFAICT. E.g.,
updateFoo(
field_a_new_value: Optional,
field_b_new_value: Optional,
)
etc. is really no different.
> OK, but I hope you don’t need to provide substantial context data; like a PDF scan of the termination request from the user.
If it's not a simple "delete this thing", that's fine. POST is still there.
> * For exemple, lots of developers use PUT to create a resource directly on its final URL (/myresourcebase/myresourceid), whereas the “good way” of doing it is to POST on a parent URL (/myresourcebase), and let the server indicate, with an HTTP redirection, the new resource’s URL.*
Either is fine.
> Using “HTTP 401 Unauthorized” when a user doesn’t have access credentials to a third-party service sounds acceptable, doesn’t it? However, if an ajax call in your Safari browser gets this error code, it’ll startle your end customer with a very unexpected password prompt.
Only if you accept Basic authentication, and indicate that in your headers. It is, I agree, somewhat unfortunate that the browsers do not let you control this behavior. I don't feel that this is an issue for many people these days. (If you're using something like a JWT, you're not going to hit this, since you'll likely be using something like Bearer for an authentication scheme.)
> Or you’ll shamelessly return “HTTP 400 Bad Request” for all functional errors, and then invent your own clunky error format, with booleans, integer codes, slugs, and translated messages stuffed into an arbitrary payload.
Would you not need to stuff that information into some form of error or exception type in the RPC world? (The error "code" might be standardized, e.g., JSONRPC does this, but the associated data cannot be.) But unless you clearly fall into one of the predefined categories, and even if you do, it doesn't hurt to settle on a standard "error" type/format.
REST is about talking about the resources, about defining formats / objects that clearly indicate whatever state/data they represent. For most people, this is "just" going to be a JSON document, but all too often you see the same concept or state expressed five different ways in not-RESTful APIs. For example, a ZIP code that is sometimes just a string, sometimes a {"zip_code": "12345} sometimes a {"zip": 12345}; the author here is failing to understand that a common type exists, and that he should form an actual format around it that he can refer to globally (this field is a zip — and we defined that here and we know it always looks the same, always.)
Frankly, I feel like most of that issue is from JavaScript and JSON, since both discourage any form of static typing of data, and humans naturally just get the immediate job done, but at the cost of having the same concept expressed six different ways.
> Or, well, yes, actually it remember its authentication session, its access permissions… but it’s stateless, nonetheless. Or more precisely, just as stateless as any HTTP-based protocol, like simple RPC mentioned previously.
This isn't what "stateless" refers to. It's stateless in that the requests are (generally speaking) independent of each other. One might need authentication, and you might have to get that somewhere, and that might need a request, yes. Store data of course changes the state of the server. But you can disconnect and reconnect and reissue that next request without caring.
> The split of data between “resources”, each instance on its own endpoint, naturally leads to the N+1 Query problem.
It can, but again, doesn't need to, and is no more susceptible to this than an RPC API. In an RPC API, you still need to determine what to expose, and how many API calls and round trips that will require…
> “The client does not need any prior knowledge of the service in order to use it”. This is by far my favourite quote. I’ve found it numerous times, under different forms, especially when the buzzword HATEOAS lurked around;
I feel like this was expressed by Fieldings to encapsulate two ideas:
* You can push code, on demand.
* You can refer to associated resources, or even state transitions, via hyperlinks.
Most supposedly (but not really) RESTful APIs do neither, so it's entirely a moot point. I think people way over read this to mean absolutely no prior knowledge, whereas Fieldings intended it to mean something closer to "only knowledge of the media types being returned", which is actually quite a bit of prior knowledge. But the two bullets above still allow you to encode a considerable amount of flexibility into an API, considerably more than something that ignores those points.
Frankly, I feel like if you start with an RPC protocol, you'll eventually want some stuff: caching. Retries would be nice, but you need to know if you can retry the operation. Metadata (headers). Partial requests. Pagination. Can you encode all of this with RPC protocols? Absolutely! But it comes up so often, it would be nice to standardize it, and many of these things (caching, pagination, range requests) get a lot easier if you stop talking about operations and start talking about the data (resources) being operated on, which is — to me — the big "ah ha!" of HTTP, and why HTTP is what it is today. That is, HTTP is a highly evolved RPC protocol.