What Can a RESTaurant Teach You About REST? (Whataburger)
Stay focused. We're talking about State Transfer (the ST in reST). So when I'm driving between offices, I often stop by a Whataburger to get some lunch. When I arrive, I need to know how to order my food.
GET /order/ HTTP/1.1 Host: whataburger42.example.com
And Whataburger #42 kindly responds with two links I can follow to make that choice: Drive Through or Dining Room?
HTTP/1.1 200 OK Date: Mon, 23 May 2005 22:38:34 GMT Server: Rosalinda Content-Type: text/html; charset=UTF-8 <ul> <li><a href="/order/drivethru/">Drive Thru. Cars in line: 9</a></li> <li><a href="/order/diningroom/">Dining Room. Cars in parking lot: 14</a></li> </ul>
It's very likely the cache headers on that response would only be for 30 seconds or so—it's a busy Whataburger at lunch time. So this is classic REST: we're using HTTP to retrieve hyperlinks that navigate us through application state. The hungry client (me!) can choose between two options, and the exact method for specifying which one is simple: I follow a link.
You're a Houstonian. Surely you always take the drive through?
Surely. Well, actually in the above case I'd almost certainly go into the dining room. And theirein lies the interesting lesson of the Whataburger concurrency dilemma. The two order pipelines handle state transfer entirely differently, because of concurrency and correctness issues. I won't keep posting silly HTTP transcripts, but you can play along as if I did. So whether I'm in the dining room or the drive through, I need to order my food. We might imagine that a GET of the resources presented above (e.g. /order/drivethru/) returns a <form> which I can POST to in order to create an order. This works in both places, right?
Actually, sort of. And here we get back to the concept of getting it right. If I'm ordering food during the incredibly busy lunch hour, my order goes through a canonicalization. If it's 3.30 in the afternoon, the B-team is on staff and just takes my order and gives me a number. Why? Let's imagine I'm in the dining room and order (POST) something like "Um, I'll take a #1 meal, with onion rings instead of fries, and a drink. Oh, no pickles." At lunch time they can't afford to screw up their pipeline of burgers, so they'll ask some clarification. "Do you want cheese with that?"
Of course, everyone loves cheese.
What does that look like in a REST universe? The restaurant doesn't even need to issue me an order number before they come back with their upsell/correction. So I imagine they'd simply return yet another <form> which I'd need to fill out. Perhaps a <form> to fill out with restricted options: the order you gave me, or the order you gave me with cheese. If I'm in the drivethrough, they always canonicalize the order in a certain form to absolutely minimize confusion. My order becomes a "#1 w/cheese, no pickles, onion rings, diet coke". That's another representation of the same resource (my order), but the server is insisting on canonicaliation because at lunch time getting it wrong is too expensive.
Got it. I'll be ready the very moment cars come standard with an HTTP client.
Don't be a smart ass. This simple transaction (and we're not even done yet!) already elucidates one example of choices for managing state. We POSTed to a URL which gave us another form to interact with. In the real world, the state of that particular application involves Rosalinda—our ever cheerful cashier—and I remembering what we're talking about. If we came back in an hour, we'd have to start at the "Um". But in our REST example, the state of the conversation is entirely contained in the form I got back asking me if I wanted cheese with that. Rosalinda doesn't need to give me an order number or alter her databases.
When does this become relevant to my day job?
Lots of transactions need canonicalization. Geocoding is a great example. We've all typed "800 8th St" into a mapping program and had it answer not with the map we expected, but a form or list of hyperlinks asking whether we meant to ask for that address in Port Arthur, Hempstead or Port Neches. (It could even be an "HTTP 300: Multiple Choices" response, but something tells me that fine a reading of the HTTP spec is some years away.) Those links contain the entire correct canonicalized address, and the server doesn't need to remember it was talking to you. My request for 800 8th St Port Arthur is indistinguishable from the less knuckleheaded person's request who asked for it correctly in the first place. I arrived at the same application state. Yes, I wanted cheese.
That's a good point, you said the drive through and the dining room were different, but all we've talked about is identical canonicalization processes.
Well, both order channels have guided me through their state identically so far. In the dining room, my final POST of a canonical order results in the creation of an order resource: I get a little orange plastic order number: 23. In the REST world, I am told my order now exists at /order/diningroom/23. I can GET the status of that resource as often as I like. Is my burger ready yet? Is my burger ready yet?
Rosalinda's co-worker Randall is happy to tell me as often as I ask that my order is or isn't ready yet. But he tells me immediately. And he's also answering my fellow hungry diner's queries as well: #21, #22, and #24 are all asking. In the dining room, food is served asynchronously. When it's ready, it's ready. It would not be unusual to get my order before #21 if he also ordered a milkshake and biscuits. One of these times, my GET will results in a beautiful (digitally signed!) cheeseburger. (And this is why you choose the dining room over the drive through when the drive through is long. You can get your food in the average time it takes to prepare it under load, not the sum of times it takes the people ahead of you to fill their orders.) After my digitally signed burger is eaten, if I GET at the same resource again, I will probably be told "HTTP 410: Gone" or "HTTP 404: Not found". (Yes, I'm totally ignoring security and the possibility someone will steal my burger by guessing my order number. There are many orthogonal ways to handle that.)
Back in the drive through, the state transfer is totally different. A simple order number and asynchronous handling is not enough. Cars must be served in order. I have to wait in line. My POST to create a new resource won't return a nice URL I can poll on. It will probably block until it returns the digitally signed cheeseburger. I get on a busy web server and had to wait behind other requests. Where was the state? It was all in the server: shuffling connections, building & servicing queues, etc. As far as I was concerned, the application was stateless.
But at what cost?! The server (the drive through) had to maintain an open connection with me that whole time. And remember what my order was. Heavy duty, man. And it's not very scalable. In the dining room, Randall could easily handle dozens of diners asking him where is order was. The diners held onto their own state—he hardly had to remember anything! But in the drive through, cars are waiting in line and waiting in line and my dreams of a fast lunch are shattered when I see the car in front of me ordering 12 burgers for her office lunch.
In return for the simplicity of simply POSTing a blocking call (easier to program—you can leave the air conditioning on), the server takes on a heavy burden. The Whataburger near my office chooses an alternative to asynchronicity in an attempt to scale: they have two drive-through lanes. When I POST my order, I am probably getting an "HTTP 302: Found" or "HTTP 303: See Other" telling me which drive through URL to make my blocking post to (e.g. /order/drivethrough/1).
Now I'm hungry
Not me. I got today's burger at the neighborhood beer joint a few hours ago. They handle their scalability and state transfer issues like Whataburger's dining room: my number today was 8.