So there are some parts of REST that make perfect sense to me. In particular, the preference for nouns w/ CRUD over verbs has many great side effects: easier caching, better logging, easier discoverability, etc. And I also get the Content-Type and Content-Language stuff, especially in terms of avoiding ".json" or ".xml" so that you can compare resource identity via a simple string compare.
But I simply do not see any value in HATEOAS outside of largely read-only datasets and generic dataset explorer type applications. Maybe it makes sense for someone like Freebase, but it's completely useless for pretty much every other API out there.
You simply cannot build a useful API client application without deep knowledge of the problem domain and the interface part of API. You're going to have API documentation and you're going to have to read it.
Now, I understand the desire to avoid IDs and manual URL construction. That's a valuable goal. And I'll admit that I never thought of using 201 and the Location header on create; clever. But just knowing the list of relative URLs from a resource is useless. It's not like a Link rel="newcomment" header is going to show up and magically you'll have comment form. Besides, you need to know which
"rel" to lookup, so you might as well just append "/comments" and avoid the indirection.
And this all breaks down yet again when you get to offline support. If you've got a web app which is going to deal with not-yet-saved objects, you're back to being unable to compare URLs, or constructing them.
Lastly, while I like working with clean URLs and GET/POST over RPC calls. I dislike the ad-hoc specifications necessary to build real applications. We've got a "RESTful" API for our app, but we keep running into situations where different views need subtly different data. For example, decorating a resource with relationship to the current user (eg. isAdmin) or joining data when returning a list of related objects (eg. members vs memberships). The query param spaghetti is growing unwieldy, subtle authorized data leak problems are an inevitability, client-side models get confusing and easily create bugs if passed around.
The only solutions to these problems are excessive discipline. Discipline is something that compilers are great at providing, which is why you see things like ProtoBufs and Thift. There's no arguing over HATEOAS or RESTfulness or GET/POST or Content-Type or any of that. The message definition files act as a baseline API documentation, which are enforced programmatically. The designers of these tools had things to do and didn't have time to deal with this nonsense.
> You simply cannot build a useful API client application without deep knowledge of the problem domain and the interface part of API. You're going to have API documentation and you're going to have to read it.
That has nothing to do with HATEOAS. Of course you have to know the interface part of the API, but the interface part is the content types. Not the Content-Type, though they can match, but the content types: the shape and structure of the documents you get from the service, and send to it. And those content types tell you, among other things, where to get or send other types.
> Besides, you need to know which "rel" to lookup
Sure, see above, that's part of the content types which the consumer needs to know in any case.
> And this all breaks down yet again when you get to offline support. If you've got a web app which is going to deal with not-yet-saved objects, you're back to being unable to compare URLs, or constructing them.
I fail to see the issue. You know the data you need to send to the service, and you know the content types to traverse in order to reach where to send your data in the service. What is the issue?
> Lastly, while I like working with clean URLs
URL shape has nothing whatsoever to do with REST.
> I dislike the ad-hoc specifications necessary to build real applications.
Why would they be any more ad-hoc than with any other interface standard?
I'm not following anything you've said about "content types". The well-defined, well-known content types in typical applications are images and other "attachment" type resources, as well as the occasional RSS feed or something like that.
In domain-specific APIs (ie. nearly all the ones that matter), every single resource type has a unique schema. Ignoring versioning, I can request resources with a specific URL pattern and parse them with specific logic. That's all there is to it. It's not complicated. The Content-Type is entirely irrelevant, unless I decide to use it for versioning or waste my time supporting both XML and JSON.
> > Stop the pontificating and get back to work.
> Oh irony, you're so delicious.
My point was directed at the whole "What is RESTful?" debate, including all the versioning, content types, URLs, headers, verbs, etc. Discussions of approaches and problems is not pontification. Discussion of "Which approach is more RESTful?" is pontification.
What bugs me about adding contents, strange result codes, and all of these things that "are in the spec even if they're only implemented sporadically" is that it's programming as if interoperability didn't matter.
It's almost as bad as the SOAP implementation in ASP.NET.
> I'm not following anything you've said about "content types".
Which amply demonstrates your complete lack of understanding of the subject "pontificate" about.
edit: you can downvote me all you want, does not change that fact. Here's what Fieldings has to say on the subject:
> A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types. Any effort spent describing what methods to use on what URIs of interest should be entirely defined within the scope of the processing rules for a media type (and, in most cases, already defined by existing media types).
(I used "content types" for his "media types", that's about it).
If you accept those constraints which you readily understand and discard those which require great discipline to master, you are unlikely to make informed decisions about your software's architecture.
Caching is affected by the HTTP verbs. For example, GET responses are (sometimes) cacheable because the object may not change. POST responses are not cacheable because they are not contractually required to be idempotent.
Logging is more interesting. I don't necessarily mean printf style logging either. Think about audit trails. For example:
One way to do a password reset is to have:
/forgot_password/
GET:
1) Shows a form asking for your email address.
POST:
1) Generates a security token
2) Sends an email with the token
3) Shows "Check your inbox!"
/reset_password/?token=abc123
GET:
1) Verifies the security token
2) Shows a form asking for your new password
POST:
1) Re-verifies the security token
2) Sets the new password
3) Expires the security token
Now, in this case, you'll notice that the security token needs to be stored somewhere. Where do you put it?
Well, one easy thing to do would be to add a column to the `users` table called `password_reset_token`. But then, of course, you're gonna want to expire a token that's not used in a while, so you need a `password_reset_token_created_at`. And surely you'll want to know which IP address the request came from, in case you start seeing a pattern of abuse. And what if I request my password be reset twice because the first email takes a little long to show up? When the first email finally comes, will the token already be invalid?
An alternate approach would be to restructure the URLs in a CRUD style:
/password/
GET:
Same as GET /forgot_password/
POST:
Similar to POST /forgot_password/
Store the reset request in a `passwords` table
/password/:token/
GET:
Similar to GET /reset_password/
Supports multiple active tokens
POST:
Similar to POST /reset_password/
Fills the new password into the `passwords` table
This re-imagines passwords as nouns. As a side effect, you get a complete log of whenever anyone changes their password or requests to reset it. Want to enforce password reuse policies? Do analysis of password lifetimes? Track down malicious abuse of your password reset system? Those things are now pretty easy with a schema like this:
Here, the reset token is blank if the user is setting their initial password or changing their current password using their old password.
The created_at timestamp is what it sounds like. The updated_at timestamp would match the created_at timestamp, except for when password reset requests are fulfilled, setting the hash and salt.
Checking a user's current password simply involves:
SELECT hash, salt
FROM passwords
WHERE user_id = ?
AND hash IS NOT NULL
ORDER BY updated_at DESC
LIMIT 1
EDIT: I really like this comment :-P I may clean it up and turn it into a submission of its own.
> This re-imagines passwords as nouns. As a side effect, you get a complete log of whenever anyone changes their password or requests to reset it.
> Want to enforce password reuse policies? Do analysis of password lifetimes? Track down malicious abuse of your password reset system?
> Those things are now pretty easy with a schema like this
I am also proponent of the more "resource oriented" view of an application but I fail to see how the CRUD approach you proposed is unique in order to support the above.. Because, all of the things that you mentioned can be also implemented/supported if you transmit the tokens in query strings. I.e. /password/:token and /reset_password/?token=:token are more or less the same thing from the REST point of view although the first is more user friendly, cache friedly, etc. After all URIs are opaque to the client: http://www.w3.org/DesignIssues/Axioms.html#opaque and that's where HATEOAS enters so the client just needs to know a single (bookmark) URI and the hypermedia will guide to the creation of the rest resource identifiers needed by the application (HATEOAS is indeed the "highest level" of REST: http://martinfowler.com/articles/richardsonMaturityModel.htm...).
Unless you mean that by following the 'noun'-oriented view of the password tokens you can easily spot these operations in the apache logs.. to which I agree if you also include the user id in these URIs...
Whether you use a query string or whatever, isn't super important. In fact, the URLs are completely irrelevant.
The reason I mention URLs at all is because thinking about routes helps you think about resources. Thinking about resources helps you nounify some verbs. This is a good thing because, as I have shown, when dealing with persistence, nouns have advantages over verbs.
But I simply do not see any value in HATEOAS outside of largely read-only datasets and generic dataset explorer type applications. Maybe it makes sense for someone like Freebase, but it's completely useless for pretty much every other API out there.
You simply cannot build a useful API client application without deep knowledge of the problem domain and the interface part of API. You're going to have API documentation and you're going to have to read it.
Now, I understand the desire to avoid IDs and manual URL construction. That's a valuable goal. And I'll admit that I never thought of using 201 and the Location header on create; clever. But just knowing the list of relative URLs from a resource is useless. It's not like a Link rel="newcomment" header is going to show up and magically you'll have comment form. Besides, you need to know which "rel" to lookup, so you might as well just append "/comments" and avoid the indirection.
And this all breaks down yet again when you get to offline support. If you've got a web app which is going to deal with not-yet-saved objects, you're back to being unable to compare URLs, or constructing them.
Lastly, while I like working with clean URLs and GET/POST over RPC calls. I dislike the ad-hoc specifications necessary to build real applications. We've got a "RESTful" API for our app, but we keep running into situations where different views need subtly different data. For example, decorating a resource with relationship to the current user (eg. isAdmin) or joining data when returning a list of related objects (eg. members vs memberships). The query param spaghetti is growing unwieldy, subtle authorized data leak problems are an inevitability, client-side models get confusing and easily create bugs if passed around.
The only solutions to these problems are excessive discipline. Discipline is something that compilers are great at providing, which is why you see things like ProtoBufs and Thift. There's no arguing over HATEOAS or RESTfulness or GET/POST or Content-Type or any of that. The message definition files act as a baseline API documentation, which are enforced programmatically. The designers of these tools had things to do and didn't have time to deal with this nonsense.
Stop the pontificating and get back to work.