Functional Form

Avoiding side-effects since 2004.

Wednesday, May 23, 2007

REST: From theory to practice

REST. What is it, and how can it be used to design better web applications?

A presentation at RailsConf did me a great service by first pointing out all the things REST is not. It isn't CRUD. It isn't pretty URLs. It is neither a protocol nor an architecture, but it can play a role in your implementation of all of the above. REST itself though, is less concrete than all of that. It is a theoretical framework, a way of thinking about designing distributed software systems. For me, the first step in absorbing its principles is to forget about the database and focus on the fundamentals. This article will start there, then drill down to show how these ideas can help organize the development of your Rails applications.

REST encourages a focus on resources. A resource is anything that can be named, and your system can have as many resources and corresponding names as you want. Conversely, there is a limited set of operations defined on those resources. Unlike objects in object-oriented programming languages, which support very diverse, rich interfaces, resources in a RESTful system are relatively uniform. So how can a sophisticated API be developed if REST requires a fixed and limited number of operations that resources can support? The answer: add more resources!

Lets play with an example. Say I'm writing a web application that has a collection of shapes that can be moved to new locations. In an object oriented program, I might have a move method defined on each shape, but in this RESTful API, I have assigned consistent semantics to a limited set of operations supported by the HTTP protocol. I can show (GET), create (POST), update (PUT), and destroy (DELETE) my resources. Nowhere in the HTTP specification is the a MOVESHAPE method defined. A naiive approach to remedying this limitation is to shoehorn this operation into the protocol by abusing URLs.

http://againstgrain.com/shapes/move/1

I call this abuse because URL stands for uniform resource locator, and it's very hard to see this imperative-style command encoding as much of a resource. This is an API decision that fights the nature of the protocol it uses. How can the API go with the flow?

There are many potential solutions. Let me outline two, the first very simple, the second more sophisticated.

The first solution is to recognize that a move operation is just a change to the location of an object. If we expose this location as a resource and allow it to be updated, we'll implement movement in a natural way without contorting URLs to name things that aren't actually resources:

So we combine one of the four standard operations:
update, represented by an HTTP PUT

With a resource:
http://withgrain.com/shapes/1/location

Updating the location of the shape will naturally equate to moving it.

But what if we want the movement to be relative to the objects current position, so that the client can say that they want a shape to move 5 pixels up and 10 pixels to the right without needing to know the objects current position or do any computation? To solve that problem, we apply a technique I learned doing computational semantics: reification.

Reification means that we give solid form or objecthood to something formerly fleeting or ephemeral. Anything can be reified. The fact that I am named Nathan Sobo can be thought of as my NathanSoboness, which is an (albeit abstract) conceptual object. Here we'll apply the technique in a more conservative fashion, and say that shapes are associated with a history of movements. This movement history is a collection, which is itself a resource.

http://withgrain.com/shapes/1/relative_movements

Now say we want to move the shape. We combine the above resource locator with one of our standard operations, create, implemented as an HTTP POST. By posting a new movement to a shapes history, we cause the shape to move.

Now we're working with HTTP rather than around it.

So how does this transfer to the design of Rails applications? Embracing resource oriented application development means you'll be writing more controllers with fewer, more consistent methods. Lets work through a potential implementation of the shape API in Rails. It will all start in the routes file, with map.resources...

Lets say we want to expose both the relative and absolute means of moving a shape. First we'll start with a shapes resource.

map.resources :shapes

This represents the collection of all shapes in our system. It assumes the existince of a corresponding ShapesController and will set up a series of routes and url-generating methods to help reference the actions therein. Note the controller and its standard complement of methods below.

class ShapesController < ActionController::Base
def index
end

def show
end

def create
end

def edit
end

def update
end

def destroy
end
end

But with map.resources, the actions on the controller do not play a critical role in the url. They merely name the operations to which a given pairing of HTTP request method and URL will map. GETting /shapes will execute index. POSTing to /shapes will execute create. GETting /shapes/:id will execute show. PUTting to /shapes/:id will execute update, and DELETEing /shapes/:id will execute destroy. So even though there are 5 actions, there are really only two url patterns, one referencing the resource that is the collection of all shapes, and another referencing resources that are members of that collection. We can reference these urls with automatically defined methods:

shapes_url
shape_url(@square) or shape_url(@square.id)

By pairing these with the correct HTTP method, we can access every operation we need.

Now lets add relative movement:

map.resources :shapes do |shape|
shape.resources :relative_movements, :name_prefix => 'shape_'
end

This again assumes the existence of a RelativeMovementController with all of the standard methods defined on it. Except the resources supported by this controller are nested within shape resources, so the URL patterns look like this:

/shapes/1/relative_movements
/shapes/1/relative_movements/4

Because of the :name_prefix we supplied (which will no longer be needed at some release of Rails in the future), we can refer to these URLs with helper methods that look like this:

shape_relative_movements_url(@triangle) to get /shapes/1/relative_movements
shape_relative_movement_url(@triangle, 4) to get /shapes/1/relative_movements/4

All of the same rules about HTTP method choice allow access to the RelativeMovementController's actions.

Now a cool twist: Singleton resources. Lets add the nested position resource to shapes.

map.resources :shapes do |shape|
shape.resources :relative_movements, :name_prefix => 'shape_'
shape.resource :position, :name_prefix => 'shape_'
end

And a corresponding controller, this time with a different complement of methods:

class PositionController < ActionController::Base
def show
end

def edit
end

def update
end
end

Because position is a singleton resource nested inside of shape, this controller is designed to deal with a single resource rather than a collection of them, so there is no need for an index action. The HTTP verb / URL combination mappings are also different. So PUTting to /shapes/1/position will invoke the update action.

None of these changes are Earth-shattering, but the simple act of focusing on resources is a force that will organize your application. Rather than growing a hodgepodge of actions on ever fattening controllers, you'll instead create a greater number of controllers that are more circumscribed in their responsibilities.

What does this say about your model? Not much. I used to think that it was important to have a controller for every model object, and I no longer do. Controllers are responsible for supporting the exposure of resources to a remote API. This collection of resources is, in a sense, your remote client's model. Whether your resources map precisely onto your underlying data model is your business. For example, you might expose resources that have no direct correspondence in the model layer. Or you might have model objects that you don't choose to expose as resources.

But regardless, REST finally provides an organizing principle for the controller layer. Even if you don't plan on exposing a RESTful API as a service, thinking in terms of resources will help you build more consistent applications and help you make fewer decisions.

I realize that this article has by no means covered every aspect of REST, but I hope it fills a gap that I felt as I was learning all of this.

8 Comments:

At 9:11 PM, Blogger Robert J. Berger said...

I got a clearer picture of how to use REST with Rails from that article than anything I've read so far. Thanks!

 
At 10:27 AM, Anonymous Anonymous said...

"REST encourages a focus on resources. A resource is anything that can be named, and your system can have as many resources and corresponding names as you want. Conversely, there is a limited set of operations defined on those resources. Unlike objects in object-oriented programming languages, which support very diverse, rich interfaces, resources in a RESTful system are relatively uniform. So how can a sophisticated API be developed if REST requires a fixed and limited number of operations that resources can support? The answer: add more resources!"

Does this actually make sense to you? How does the development of a sophisticated API have anything to do with the operations that REST supports? Are you suggesting that there is some mapping of objects in an API developed in an object-oriented language to REST resources? Do you write things just to amuse yourself?

 
At 12:34 AM, Blogger Steve said...

I found this article helpful. One of the things about RESTful applications that always felt awkward was how to go beyond controllers that fronted a model object. I thought the examples in this post made sense as concrete examples of where you would want to create "subcontrollers" that provide an extended API and additionally functionality for a single model.

 
At 3:17 AM, Anonymous Anonymous said...

That's really awesome especially the paragraph in which you build your case:


REST encourages a focus on resources. A resource is anything that can be named, and your system can have as many resources and corresponding names as you want. Conversely, there is a limited set of operations defined on those resources. Unlike objects in object-oriented programming languages, which support very diverse, rich interfaces, resources in a RESTful system are relatively uniform. So how can a sophisticated API be developed if REST requires a fixed and limited number of operations that resources can support? The answer: add more resources!



But honestly, I am not to expriment with some toy example and have to provide a REST interface to an earlier version of the application build on Rails. And if you try to be pure "RESTy RESTy" then you would have to face an exponential rise in the number of controllers and URL patterns.

However, Nice description! I learned a lot!

Regards,
Mohsin

 
At 10:00 AM, Anonymous Anonymous said...

excelent article. I have found making the update method of the parent model(in this case 'shape') slightly more intelegent is often the solution Im most compfortable with. I hope to hear more of your thoughts on REST. You just earned yourself a subscriber.

 
At 10:26 AM, Anonymous Anonymous said...

I still don't understand what this buys you. So many books, blog posts, conference sessions... Compared to the adoption of OOP, this seems to me to have far more limited benefits (if any).

Most of what I've read about REST is about the philosophy and/or the implementation. Where's the beef?

///ark

 
At 11:07 PM, Blogger Unknown said...

Mark, one big benefit lies in the integration.
I've been doing REST services for nearly a year, and integration across platforms/frameworks is almost trivial in comparison with the SOA alternatives. One time we had someone from a different project which we were consuming come by, give us a URL and an hour later we were done.

 
At 9:40 AM, Anonymous Anonymous said...

Good introduction to REST. I happened to write one a few months ago on my dev blog. Cheers!

 

Post a Comment

<< Home