The "true spirit" of REST, to me, is that there's a certain set of things
you can do when creating an API that will let you re-use the huge amount of
HTTP middleware that's been written and get correct (and useful!) semantics
from it. Caches (browser-, edge-, and server-side-), load balancers,
forward- and reverse-proxies, application-layer firewalls, etc. will all
"just work" for your software if you do REST correctly, and won't have any
weird edge-cases.
Doesn't that assume that the middleware also implements REST correctly? As the article points out, there are all too many HTTP libraries that only support GET and POST, not the more "esoteric" verbs like PUT and DELETE.It assumes that the middleware supports HTTP properly.
PATCH allows two users to update different fields on the same resource at the same time with no conflicts. Otherwise, they'd use PUT and the last update would win unless they use something (client-side) such as compare and set.
GET and POST are all you really need for REST.
1. Pure read.
2. Impure (stateful) read.
3. Idempotent write.
4. Any other write.
There's no particular reason to separate inserts, updates, deletes etc as part of the protocol - they're all just different kinds of writes, and middleware doesn't derive any benefit from being able to distinguish them. Thus, this can be a part of the payload.
On the other hand, the difference between reads and writes, and the two subdivisions within each, do matter for purposes such as caching and automated error recovery (e.g. a proxy can repeat an idempotent write a few times before returning the error to the originator of the request).
In REST, we have GET for #1 and #2, PUT and DELETE for #3, and POST for #4. In practice, this is often simplified to just POST used for both #3 and #4, but that loses a valuable distinction (but is unfortunately often necessary because of the lack of support for other methods). On the other hand, the PUT/DELETE distinction is largely pointless.
POST requests can be made idempotent by binding the result of processing that request to a future, so every subsequent request simply returns the already computed result.
Impure reads are the default, and pure reads are designated by long-lived cache headers, ie. it's an impure read that lasts as long as the server responds.
So really you just need GET and POST, and server-side frameworks should make POST requests idempotent by embedding some notion of futures for side-effecting operations. See the Waterken server for the first development platform to really get this right.
e.g. Instead of saying "Insert a transaction for £20", you say, "Insert a transaction that entity A calls 1234, for £20".
Stripe use something like this to ensure that as long as you call their API to ask for a payment and use the same key, they won't charge the person twice.
Not sure what you mean by client-generated. The server-side app generates the id because it has to store and interpret it. This can be as simple as a sequence number, similar to how TCP guarantees delivery. It depends on the schema really.
This is the simplest way to ensure you can perform arbitrary retries of POST in case of a network partition.
This data doesn't even need to be integrated with your app's schema, although that's ideal so you can manage the storage lifetime. But you could use an entirely separate store for GUIDs and cached replies, and so it becomes transparent to your app and just becomes another layer.
Per the description above:
"Each non-idempotent operation is tagged with a unique identifier"
Since the operation originates on the client, the client has to tag it with the identifier, no? And the server has to store this identifier in a way that associates it with any data affected by that operation in a non-idempotent way.
Or are you saying that the client first has to make a round-trip to the server to generate the ID, and then use that server-provided ID for the actual POST?
This is always the case for REST given HATEOAS, ie. you've already made some hypermedia requests to obtain the URL of the endpoint to which you will POST.
Unless the resource you're posting to actually is the public entry point of your service, but that would be very unusual.
Generally speaking, what is the flow like? Suppose I allocated myself an endpoint, but then never posted anything to it - what does the endpoint actually contain then, if queried? Do unused ones get "garbage collected" somehow eventually?
Not necessarily. The numbers don't have to be stored before they're actually used by clients. For instance, you could return a simple integer, like an object version #, and an HMAC(integer, resource URL) to ensure the client can't tamper with it. You only store data under that integer when the user successfully POSTs to that resource for the first time.
> Generally speaking, what is the flow like? Suppose I allocated myself an endpoint, but then never posted anything to it - what does the endpoint actually contain then, if queried?
There are many possible designs here. I prefer something like this in CRUD-like contexts [1], because it gets me a fully auditable change history, optimistic concurrency control and all the storage needed is fully integrated into the app schema in a sensible way.
Combined with the HMAC described above, you don't need to preemptively allocate any storage for idempotent POSTS and you can use simple integers as your unique identifiers. So a POST against a version number that's not the latest version would simply return the version that followed if their POST was the one that succeeded, or a redirect to the latest version if someone else had updated it.
Hopefully you can imagine various relaxations on this to make other trade offs. For instance, a much simpler approach would be to return GUIDs for each possible POST, and you just store each GUID associated with the object when a POST succeeds. If the object's current GUID is the GUID the user is posting, return the current data with 200 OK, otherwise return a 301 Moved to a URL referencing the latest version and let the client try to apply their updates to that. The storage can be reclaimed after a suitable period of time; say a month if your app is for browser clients
[1] https://higherlogics.blogspot.ca/2015/10/versioning-domain-e...
By "pure" here I mean that we're talking about a pure function of its inputs.
Why distinguish? Because if you know that the results are not affected by server state, they can be cached much more aggressively.
Come on, person.
I don' know about you, but I provide REST stuff where the only option is update.
Is this just referring to using semantically correct HTTP status codes for things like caching, or is there more to it that I'm missing?
edit: And also, and perhaps most important, what about REST makes it easier to take advantage of the transport layer's advantages?
edit 2: This article has answered some of my questions and brought more clarity: https://philsturgeon.uk/api/2017/01/26/graphql-vs-rest-cachi...
At a minimum, error codes and verbs help these tools highlight different behavior and problems. A 4xx class error, which usually means that your client-side application did something wrong, or that your user did something wrong, is very different than a 5xx class error that means that something really screwed up in the server.
Thus, the response codes make it very easy to use a 3rd party tool to diagnose malfunctions, even when the 3rd party tool doesn't know much about the application.
Likewise, at a minimum, a GET request is a read and doesn't change state. This is also useful when using a 3rd party tool to diagnose an application that runs over HTTP.
I do think that the article points out just how confusing proper REST can be. The semantics don't always match what the API is actually doing, either. Perhaps it's best to just limit oneself to the GET and POST verbs, 200, a few 4xx error codes, and 500. Perhaps it's also best to assume that rest semantics are for diagnostics and not for application level control?
Re-implementing those same semantics in your own messaging protocol / format, without intertwining the concerns of the protocol and the message format, throws away any/all of those benefits. You need a protocol that guarantees that middleware can "look inside" the messages it's passing (or at least their metadata), in order for any of this to work. That's why HTTP has both a transparent part (req path+headers; resp status code) and an opaque part (req and resp bodies) to each message: the transparent part is there for data that affects middleware behavior, while the opaque part is there for data that doesn't.
Note that that doesn't mean you're stuck with HTTP1. SPDY/HTTP2 is effectively an entirely different protocol—but it keeps the same semantics of requiring certain properties of the metadata tagged onto each message at the protocol level, so that anything that speaks the protocol can use that metadata to inform its decisions.