Taking Things Too Far: REST

Posted by Koz Monday, June 22, 2009 06:13:00 GMT

I’m going to put up a few posts based on a talk I gave at RailsConf ‘09 in Vegas and RailsWayCon in Berlin. Sorry for the delay in updating but I wanted to deliver the talks before posting here.

There’s a common pattern I see when working on code-reviews with ActionRails or my consulting work. People find a new technique, technology or idea and put it to work in their projects. At first they get a huge benefit, problems get solved quickly and things are good. Driven by this initial success they double down, they put their new tool to work in more areas of their application, they even go back over their old stuff and seek out more pure ways to apply it.

However as time passes they find the benefits aren’t quite what they used to be. Their nice new toy has turned into something which they find gets in the way on a regular basis. Eventually they ‘throw that shit out’. Part of this is just the natural progression of technology, something better comes along and we adopt it. But another part of it is our tendency to over do things. The technology we picked up isn’t shit, the promise we saw was real. But we’ve taken it beyond its intended use, learned the wrong lessons and tied ourselves up as a result.

I’m going to cover a few techniques used in the Rails community which are great, but which turn on you if you take them too far. Starting with RESTful design.

Restful Design

RESTful design really started catching on with Rails 1.2, and by the time 2.0 was released it had become something approaching Canon Law. Everyone who was anyone and building a Rails application, was focussing on resources, CRUD and HTTP. There were two chief benefits of this change.

The first benefit, and the one everyone focussed on, was that you had a relatively straightforward way to add an API to your application. Back in the preREST dark-ages everyone who was designing an API for their application had to make a bunch of decisions about how they wanted to build it.

  • Do you re-use your controllers, or have a separate ApiController?
  • How do you pass arguments around?
  • What should the URLs look like?
  • Perhaps XML-RPC or SOAP is the right way?

With REST you get answers to all those questions, and instead of worrying about that, you just get on with building your Application.

Even for applications without an API, REST gives you some benefits. You avoid discussions about what your controllers and actions should be called, and what your URLs should look like. It also makes it easier for new developers to get up to speed with your project. Almost every rails developer now knows that if you’re looking for the thing which creates posts, you’ll be looking at PostsController#create.

Taking it Further

If we look at a slightly more complicated example, we can see the beginnings of the friction that comes from taking things too far. Take an example of a site which lets people upload photos and write blog posts, and lets users comment on one another’s data. The most common way to approach this design would be:

  map.resources :bookmarks, :has_many => [:comments]
  map.resources :posts,     :has_many => [:comments]

The nice thing about this design is that the URLs will reflect the underlying structure of the data you’re managing. For example the URL for comments on post number 5 will be /posts/5/comments and for bookmark 3 will be /bookmarks/3/comments. However where it starts to get a /little/ annoying is when you want to do something generic to all comments, like providing a ‘mark as spam’ link alongside a comment. Because comments exist solely as a child of the Commentable we can’t generate the URLs without knowing the class and id of that object. So it’s just that little bit more difficult to deal with comments generically (e.g. in an admin interface). This tends to lead to you writing a helper something like this:

  def spam_comment_url(comment)
    case o = comment.commentable
      when Post
        spam_post_comment_url(o, comment)
      when Bookmark
        spam_bookmark_comment_url(o, comment)
      end
    end
  end

Now this is a good indicator that you should probably also have a top-level resource for your comments, and thankfully there’s a feature for this case which gives you a nice pragmatic way out.

  # First define the top-level comment resource
  map.resources :comments, :member => {:spam=>:post}

  # then add a shallow collection under each of the commentables
  map.resources :bookmarks do |bookmarks|
    bookmarks.resources :comments, :shallow=>true
  end

  map.resources :posts do |posts|
    posts.resources :comments, :shallow=>true
  end

Taking it Too Far

Unfortunately people often get started with REST and love the way it simplifies their designs and gives them conventions to follow. They then take their new rose coloured glasses and start making sure everything in their app is “purely RESTful”. Every new design decision must be perfectly RESTful, anything which looks like RPC is instantly purged from the application.

Taking this more extreme approach to the problem of marking comments as spam they’ll say something like:

When you think about it, marking a comment as spam is really creating the SpamScore child resource of the comment with the value of spam set to true

And build something like this, so when they want to mark a comment as spam they ‘only’ have to construct a POST request to the bookmark_comment_spam_score_url of /posts/1/comments/2/spam_score:

  map.resources :bookmarks do |bookmarks|
    bookmarks.resources :comments do |bookmark_comments|
      bookmark_comments.resource :spam_score
    end
  end

While this may be purely restful, it’s much more complicated than the ‘impure’ RPC approach taken above with a simple URL like /comments/1/spam. Plus if you want to get truly pure your URLs should probably be more like this:

map.resources :users do |users|
  users.resources :bookmarks do |bookmarks|
    bookmarks.resources :comments do |bookmark_comments|
      bookmark_comments.resource :spam_score
    end
  end
end

The advice I typically give when I come across a “complex but pure” model like this is to go back to basics and remember why we originally started using REST. Does it help us make an API? Does it make things simpler for new developers to follow? Does it make it easier to work with some of the great plugins out there? If the answer to all those questions is no, you should probably dial back the purity and do the pragmatic thing.


Comments

Leave a response

  1. JanJune 22, 2009 @ 08:16 AM

    The second paragraph is not ended correctly.

  2. CyrilJune 22, 2009 @ 09:59 AM

    Well… I see your point but your example is not a good one: it seems to me that any reasonable REST design would use a PUT on the comment to change the spam_score property. Adding information to a resource doesn’t imply the creation of a new resource.

  3. KozJune 22, 2009 @ 10:00 AM

    Thanks jan, fixed.

  4. KozJune 22, 2009 @ 10:05 AM

    Actually Cyril people do exactly this stuff, even David mentioned something similar in his keynote in ‘06, see slide #30 here:

    http://media.rubyonrails.org/presentations/worldofresources.pdf

    It’s definitely not something I’d recommend, and you’re right to think it’s a bad example. But people are doing this stuff, and the message I’m trying to get across is that you shouldn’t ;)

  5. Michael SchuerigJune 22, 2009 @ 11:43 AM

    Pretty URLs are a nice and benign part of Rails folklore, but they are not entailed by REST as defined by Fielding. Resources are part of a structure, of course, but the URIs denoting the resources are completely opaque. In particular, the relationship among resources does not have to be mirrored in the URI. Using pretty URIs is a purely aesthetic decision.

    From a REST point of view, pretty URIs may even be detrimental as they may be seen as an invitation to parse the URI into its parts and tinker with them. At that point you would be abandoning the REST architectural style for something different. The REST way is to follow links supplied by the server, not to construct URIs according to an assumed structure.

    I may need to point out that to REST or not to REST is not a moral question. REST is not synonymous with good, rather, it is a specific architectural style. Depending on context, it may be useful or not to use this particular style.

  6. AdamJune 22, 2009 @ 04:03 PM

    Koz,

    In my experience (which is not extensive, I’ll admit), since 1.2, the “Rails Way” only produces REST-like applications, unless something has happened recently that I hadn’t recognized. All you ever see is a Standard API (GET, POST, PUT and DELETE) over HTTP. The single most valuable part of REST is missing—> URIs. Nothing is ever done by passing URIs, which is, IMHO, the whole point of REST.

    Further, your counter example only show’s that Rails’ understanding of REST is incorrect, not that REST isn’t a good architectural style.

    First, Using Human grokkable URIs is MERELY documentation (that’s one of the constraints imposed by the REST Architectural Style). That is to say that a REST application could use GUIDs for all its URIs (and any architecture that would fall apart under that restriction is NOT REST). It is very clearly stated in Fielding’s thesis that all the interactions one may have with a resource are listed in a resource (typically that resource, but potentially another) and that NO URIs are generated or assumed by the code consuming that resource. As a result, you might have a representation that looks like this (this would have a content type of something like “application/X-littlefyr-comment+json”):

    { “subject”: “This is my subject”, “commenter”: “http://openid.example.com/JimBob”, “comment”: “Here’s a really long comment… ”, “is_spam”: false, “spam”: “http://www.myexample.com/comment1/spam”, “reply”: “http://www.myexample.com/comment1/reply” }

    Second, regardless of what you do, you SHOULD request that URL (which is the same from both the REST and RPC points of view) using either a POST or a PUT. Using a simple GET would violate HTTP as that would have a side effect. Besides, one might expect to get a description of the “spamminess” of the comment from a GET.

    But here is the FAR more important point. Applications built around the REST architectural style are seldom great interfaces for end users; they are best suited for application interfaces for programmers. Here’s a case in point. If I built a RESTful Loan origination system, I would have something like http://www.restloan.com/loans to which you would post a representation of a loan resource in order to apply for a loan. Most Loan application forms are hugely complex, sufficiently so that you would want to break it up into a multi step wizard-like interface. You’re also going to want to allow for some ability to start and stop the process in the middle (so you can go find the registration number on your birth certificate or what ever other arcane piece of trivia they way). You cannot do this with straight HTML + HTTP. By breaking the process into multiple steps you are going to do one of a number of things:

    a) Old School -> Perform a series of POSTs to some intermediate URLs until the process is complete 2) Write a Flash/Silverlight/JavaScript + Ajax UI which handles POSTing the representation to the URL iii) Write some unobtrusive JavaScript to take the HTML and put in the necessary controls to make the process sane.

    In the first case, POSTing multiple times before creating the final resource is not REST, even if you could contrive some rationale for calling each post a new resource. In the second case, you’ve created an independent application, hosted in a browser, which is using a REST API. The third case, I would argue, is an example of the second in that your unobtrusive JavaScript is an application which is consuming a RESTful resource (an HTML representation) as input to its program (I’ll grant that’s a little weak).

    If we decompose our applications into a User Interface, designed using a User Centered architectural style, which communicates with a collection of applications designed using a REST architectural style, I think we’ll have a distributed MVC that scaled like the internet. And that could be revolutionary.

  7. LenaryJune 23, 2009 @ 11:00 AM

    Seems i too wrote on the subject, but from a different perspective: http://lenary.co.uk/post/120613629/rest

    i was approaching from the angle that making something RESTful is not always the best way, rather than arguing that making everything subresourced is not always the best way. Interesting angles on the same debate i think.

  8. AaronJune 23, 2009 @ 02:38 PM

    Adam has some good points.

    There are other options for your spam score. Consider:

    map.resources :bookmarks do |bookmarks|
        bookmarks.resources :comments member => { :spam_score => :any }
    end

    In this case, you get URLs like:

    bookmarks/1/comments/2/spam_score

    Which maps to a `spam_score` method on your CommentsController. You can use this method to handle GETs, POSTs, DELETEs, and so on. Or you can limit what http methods route to this controller method. This allows you to easily extend the standard Rails resource by adding other resourceful URLs without making everything a model.

    Finally, the Rails approach to REST is simply one particular convention and sometimes it works and sometimes it doesn’t. A proper RESTful application should be designed by identifying the resources and URLs first and then going about implementing them using whatever tool happens to work best.

  9. MatthewJune 23, 2009 @ 11:59 PM

    Yes, don’t conflate Rails approach to REST, and the REST architecture in general.

    In fact some (including Roy Fielding who invented the term) might say that Rails isn’t entirely true to the REST philosophy, for a bunch of reasons which I won’t bore you with right now.

    Certainly, Rails routing and controller architecture often requires a lot of boilerplate to acheive re-usable resource structures, and to create hyperlinks between resources, as you example demonstrates. Rails lacks a clean, obvious abstraction of the key concept of REST, the ‘resource’ – whereas a more pure REST framework (like java’s Restlet) have this concept as an interface in their architecture…

  10. FredrikJune 25, 2009 @ 06:41 PM

    Thanks for an interesting article! It’s always nice to read how some Rails-features are meant to be used. I my self has really stretched this REST-thing to far some times, specially by nesting to many resources because it feels like the right way at first.

    It also would be interesting to read about rails best practices when it comes to generalize controllers and models. What’s the best way to write this comment controller that works on both bookmarks and posts? Also, how do you abstract active record models without going with STI? I worked around this before by adding a has_one association to all the common models, but it’s not very clean.

  11. Rich SturimJune 26, 2009 @ 05:04 PM

    I’m so glad that someone had the courage to cast a thoughtful stone at the religion we know as REST.

    All the merits of RESTful design are valid except when they convolute the hell out of the code. All devs enforcing REST need to fully consider your last 3 points … they should be known from hence forth as the “3 Commandments of REST

    Does it help us make an API? Does it make things simpler for new developers to follow? Does it make it easier to work with some of the great plugins out there?

    Thanks Koz

  12. MayJune 27, 2009 @ 04:11 AM

    Using intuitive, and that means grammatical, names is also important. Resources are nouns, methods are verbs. comments/1/spam, if “spam” is a method, would mean “[I’d like to] spam comment number one”. That doesn’t make sense. If “spam” is a subresource, it would mean “the spam of comment number one”. Doesn’t make sense either. “spam_score” is better, but I would expect that to be numeric.

    My approach is either a simple field of the table “comments”, no extra route or controller code needed:

    POST comments/1&comments[spammy]=true

    and if it’s supposed to do more than just set a flag, do that in the Comment model (where it belongs), by defining a setter or a before_save.

    Or you want to make it a resource of its own (similar to Aaron, but I prefer one controller per resource):

    map.resources :comments {|c| c.resource :spam_mark }
    PUT comments/2/spam_mark
    DELETE comments/2/spam_mark

    I usually start out with a simple field, and when at some point I notice that the whole spam logic is getting too big, or that other things (posts, mails) can be spammy too, I outfactor it into a polymorphic resource. Adding custom methods is rarely a good way because it clutters up your controllers and you have to distinguish between HTTP verbs. It’s not even simpler.

    Adam: Good point about the multistep form. I don’t have a good solution to that either, other than partial saves and carrying some state around. “Some unobtrusive JavaScript” cannot be the solution because it becomes very obtrusive for accessibility or noscript browsers. AJAX should only be interactive sugar, it shouldn’t define the logic of the interaction.

    Maybe reconsider the whole “wizard” as merely UI sugar for one big form, and make the steps into tabs that are activated by a fragment identifier? This (showing a tab, hiding the others based on fragment) can be done by pure CSS nowadays. As a fallback for older browsers you’d get a long form with the tabs stacked vertically. The semantics of fragment identifiers has been expanded to include visual state, and I think that’s OK as long as it only means hiding the parts of a lengthy form that are currently “out of focus”.

    It could still save the data in between steps, with something like a complete=false parameter so it doesn’t try validating. I wouldn’t see that as unRESTful.

  13. KozJune 28, 2009 @ 07:56 AM

    While I appreciate the different approaches you could take to making a restful spam-comment functionality. My proposed one with a spam score subresource was just an example. But I worry that perhaps I wasn’t clear enough with my main point here. All these design decisions are secondary (maybe tertiary) to the more important question of “what’s the quickest and best way to achieve our goals”.

  14. KozJune 28, 2009 @ 08:03 AM

    Adam,

    One point you’re making is very true, typical ‘rest’ webservices today need to pass around more URLs. Especially with associations, you’ll frequently see things like comment_id in the xml, when it should probably be comment_href or similar and contain the URL for the resource in question.

    However I still fall back on the pragmatic question of, what will that buy you? Unless you have a good answer for that, then just stick with the simplest approach :)

  15. WebHostingSphereJune 30, 2009 @ 05:58 AM

    Excellent post! It was so nice to meet you and attend your presentation in railsConf. I am quite outdated and still use old Java XML style services… Will read your blog well to learn a little bit.

    Thanks, WebHostingSphere

  16. Sheldon HearnJuly 03, 2009 @ 09:38 AM

    I’m thrilled that you’ve revived your blog. I’m enjoying your posts immensely.

    Thanks, Sheldon.

  17. DreamrJanuary 09, 2010 @ 03:44 PM

    Hey Koz, glad to see TRW back.

    But this time I have a bone to pick with you. Since fully adopting REST in Rails (And after that period it takes you to translate all your old-world webdev stuff to REST thinking) I have not found a single instance for a resource to not be restful.

    StaticPages are one thing, as they aren’t actually a resource, but surely you can find a restful way of displaying such info without nesting routes to hell.

    I am seeing no benefit of sprinkling RPC calls in my apps. And whenever I have to clean up someone else’s app for a client, it is usually because they couldn’t make up their minds to use REST or RPC, or any other choice, and instead mixed it all together.

    Fuck that. I will be sticking to my 100% rest resource apps and releasing code that people don’t have to “figure out”.

    Love ya though Koz :)

  18. Billy KimbleFebruary 10, 2010 @ 01:32 AM

    Excellent post. I have been kicking the idea of REST-ifying everything myself, but I am encountering many cases where it just doesn’t feel right to use rest.

    1) When you need to update multiple resources at once, similar to your example. If you have 30 photos that are assigned to an author, and you need to change that photo resource to belong to someone else, your action needs to be aware of all of the photo ids. Now you have 1 action touching a collection of resources rather than a single resource. This doesn’t gel well with passing in single IDs so you start to frankenstein your controller.

    2) 1 action that needs to modify multiple resources

    3) Multi Step wizards. My biggest peave with REST at the moment. I have a 4 step process that spans 3 resources in different fashions, but the data is not saved until the end. In addition to this wizard, there are non-wizard views that allow users to perform the exact same actions. You start getting things in your Resource#update that look like:

    if @resource.valid? if params[:from_a_wizard] redirect_to(next_step_in_wizard_url) else redirect_to(resource_url(:id=>@resource.id)) end else if params[:from_a_wizard] redirect_to(same_step_in_wizard_with_errors) else someaction render :someaction end end

    Its at this point that your code starts becoming clunky—when you have 7 actions containing N-case statements depending on how many entry points you have in to the controller.

    I see blindly following REST as akin to the people who blindly employ all of the Agile/XP processes in their work just because “thats the latest thing and it is what you are supposed to do”. There are good and bad aspects and you should really sit down and pick what worst best for you, rather than marrying a methodology and disavowing the rest.

    I like REST for basic CRUD operations, but in a real world application, there are many edge cases that you will come across that don’t fit neatly in to the REST paradigm. It is these cases where you will see the most hackish and convoluted code full of workarounds just to get things ‘the restful way’.

Comment