I have changed my mind a lot over the years about web development. I think I have reached another one of those points of inflection, thanks to incredibly bright folks like Simon Stewart, Dan Worthington-Bodart, Jim Webber and George Malamidis. Unfortunately, it took me a lot longer than they did to figure this out, but at least I’m writing about it :)
About a month ago, a recent trip to the Brazilian Consulate General to renew my passport made a few things click. We talk a lot about forms on the web, but it’s really rare I get to fill in a form in real life. It’s a very different experience, and while at the same time it’s somewhat painful in some respects, there are lessons to be learned.
The process goes like this: you queue up to the first booth, and an attendant asks you about what service you require and gives you a coloured piece of paper with a number and a form to fill in. They call the number on that stub when it’s your turn to be seen. When called, you present the stub, form and any necessary supporting documentation to another attendant, who gives it a good check and tells you to go over there to pay a fee. Again, you get called by the number, pay the fee, come back and the attendant checks the receipt. She then decides that your application should be processed and staples the stub to another receipt and tell you to come back in a few days. When you get back, you present the stub, and they hand you the passports.
That tiny little piece of paper is the essential thing we’ve missed on the web. As an example, I’ll use what Rails and Merb generate in the RESTful scaffolding. In this case, you get the magic 8 CRUD actions:
I’m really interested in new, here. Digging a little deeper, you’ll see:
Looks reasonable. Let’s try it out:
This would be the equivalent of being handed out a form to fill in in real life… but all I got was the form—where’s the stub? How is the application on the other side going to know I’m talking about the same interaction?
You could argue that that’s the exact reason why the cookie is there, but the cookie doesn’t represent this particular interaction. It represents my browser’s (or other HTTP agent’s) interaction with the whole app. In real life, I couldn’t use the same stub to also fill in my tax returns, I’d have to get another one, probably of a different colour, even. I need something that the server can use to track this particular form being filled in, for reasons I’ll discuss later.
One quick and easy solution to this is to add an UUID to that form. UUIDs are guaranteed to be unique, and are pretty cheap to generate. So cheap in fact, there’s no reason not to slap one on the form itself:
This allows us to track the entire process of filling in a web version of my little passport application workflow. In HTTP-speak, that workflow would be something like:
- GET /passport_applications/new.xml (200 OK)
- POST /passport_applications (201 Created)
- GET /fee_payments/new.xml?for=09711c30-40d5-012b-3f7b-001ec212da96 (200 OK)
- POST /fee_payments (201 Created)
- PUT /passport_applications/09711c30-40d5-012b-3f7b-001ec212da96 (202 Accepted)
- GET /passport_applications/09711c30-40d5-012b-3f7b-001ec212da96 (200 OK)
A benefit to using an UUID to identify resources is already evident here: because they are unguessable, there’s no problem in using them on URLs for privacy-sensitive documents, as it is extremely unlikely that potential attackers would be able to hit arbitrary UUIDs and get to something other than a 404 Not Found.
Another benefit is that UUIDs also work really well as artificial primary keys in relational databases. SQLServer, Oracle, MySQL, PostgreSQL and most other RDBMSs support some UUID type, or have a UUID function. This means we don’t need sequential IDs on our tables, and while they need a little extra storage, the upside is that they don’t have to perform expensive synchronization on the sequences. If you are not using an RDBMS and need that extra little bit of cheap scalability, document databases such as Amazon SimpleDB, CouchDB, HBase and Google BigTable also love UUIDs.
So what kinds of cool stuff can you do if you buy some more storage and collect data about every step of an user interaction, even when that interaction wasn’t successful? Imagine that every time the number on my stub got called and I talked to the attendant, she also took a photocopy of my form and documents before handing them back. What could be done with that data, given some spare cycles?
Suppose that you discovered that quite a few of your users are having trouble paying for the fee—they haven’t been told how much it was, and they had no cash at hand! You could then work out a solution, from the simplest (putting up a list of fees near the entrance) to the most complete (accepting credit and debit cards and putting a cash machine next to the booth). You could even let the process happen asynchronously: users can choose to pay when they come back to get their new passport if it’s more convenient, for example. And, best of all, it’s perfectly possible to do these things while being really nice to HTTP servers, proxies, caches and other bits of the infrastructure of the web. It’s really what REST is about, building and playing nice with the web’s infrastructure… isn’t it?