CRUD and REST

Another frequent subject is how to make a REST interface to it. This page is for gathering opinions on how to do it in the 'optimal' way.

Writing a Catalyst controller realizing the CRUD operations on a model seems like an easy thing to do - and it is in it's most basic form. Unfortunately there is little of isolation between the basic Create and Update operations and other additional features that anyone implementing a 'real' application will need. There are two ways to solve this problem:

1 Make the CRUD operations more complete - for example by operating not on single database rows - but on logical objects that can be composed of many linked DB rows.

2 Make the CRUD add on minimally interfering with the rest of the application. Make it something that you could just add to your ready made application - without any adjustments made to it - so that the developer gets the CRUD operations virtually for free. It would be nice to be able to take a table or a view and just make a crud interface for that particular thing.

3 Don't close the CRUD inside a 'black box' module - but just write an example - so that people can cut and paste it and then adjust to their needs.

Or do both two first points.

Additional requirements

  • The browser interface should do a redirect after a successful POST

  • When there is a validation error the browser interface should redisplay the form with error messages

  • A corollary to point 2 above is that the object that is retrieved by an action in the CRUD add on chain (using for example a chained address like /foo/instance/42) should be a vanilla object from the model - so that it would be compatible with the other code in the controller

  • It should modular as much as possible (but still simple to use). I can identify following parts in it: the CRUD Catalyst controller (with REST?), the browser compatibility for REST, Form Processor, model (DBIC, RDBO, LDAP, File itp), integrator between the Form Processor and the model (HTML::FormFu::Model::DBIC, Rose::HTML::Form::DBIC, Form::Processor::Model::DBIC). Ideally all parts should be interchangeable.

karpet: CatalystX::CRUD meets all the requirements above, to the best of my knowledge.

URI schema

On human-friendly, clean URLs:

On RESTful URI design:

A consensus is forming on what should be the URI pointing to an object. The classic approach is '/cd/yanni-tribute', but the newer approach '/cd/instance/yanni-tribute' is preferred (where cd is the name of the class of objects and yanni-tribute is the unique key).

The arguments for '/cd/instance/yanni-tribute' instead of the more traditional '/cd/yanni-tribute' come from three premises:

  1. You cannot use URIs like '/cd/yanni-tribute' and '/cd/search' (or other action that comes directly after the class in the URI) - because this would mix data (the key) with reserved word (the 'search' action) - and would produce a clash if there exists an object with 'search' as its key. A solution for that would be to use the *singular* noun of the class to retrieve one instance, and the *plural* to retrieve multiple instances:
    • `customer/johndoe`
    • `customers/top10`
    There are a few problems with using singular vs. plural to make distinctions:
    • there are words which have the same form for plural and singular:
      • sheep/the_black_one
      • sheep/top?by=wool
      On the other hand, would users mind `/sheeps/`top10? If you want URLs to be human-guessable, they might: /sheep/`instance/the_black_one` makes /sheep`/search` guessable, while sheep`/the_black_one` doesn't make `/sheeps/search` particularly guessable.
    • English-speakers may think of the plural of 'person' to be 'people', but other speakers may try 'persons'. In that case you'd have to do a redirect.
    To handle plurals, use [Lingua::EN::Inflect](http://search.cpan.org/dist/Lingua-EN-Inflect/).
  2. Ruling out nouns like /cd/`search` or /customers/`top10` and trying to fit everything into the REST verbs (GET, PUT, POST, DELETE) may be too restrictive. Also, by the time a new action is needed, an object with that name may have been created already (/users/`top`, where `top` is a common name in a non-English language).
  3. URIs like /customer/instance/johndoe allow for additional actions on /customer.

Aspects open for debate:

  1. /foods/instance/orange-juice/edit, or /foods/instance/edit/orange-juice?
    • dandv: let's avoid 'instance': /foods/view/orange-juice; /foods/edit/orange-juice. 43things.com uses 'view' instead of 'instance': http://www.43things.com/things/view/1229280/
    • dhoss: seconded, "instance" is too vague, the action (edit, view, etc) is much more specific and intuitive IMHO.
  2. How do I handle multi page forms? /customers/1234/name ? /customers/1234/addresses/9876 ?
  3. kaare: /customers/1234 or /customers/jasonic - Is 1234 or jasonic the primary key, or just any key?
    • zby: I think there is a rough consensus that this needs to be any unique key - but that there always should be just one used. So the developer should choose one unique key and stick to it - other ways of addressing the same thing should redirect.
  4. What happens when the key noun gets renamed? e.g. /customers/janedoe/ becomes /customers/janesmith/?
    • dandv: keep the old key only as a redirect to the new key but allow creation of a new item with the old key (now deemed as genuine)

Preliminary URI naming guidelines

As a result of the aspects discussed above, it seems that a good URI naming convention would be:

/cd/view/yanni-tribute
/cd/edit[_form]/yanni-tribute
/cd/search/yanni-tribute
/cd/create_form

Note that the editing URL is /cd/edit_form/yanni-tribute (in accordance with CatalystX::CRUD::REST), with "edit_form" being a noun, which is RESTful. However, that's awkward for the user, who would most likely prefer "edit". Now, "edit" is primarily a verb, but also a noun (see dictionary.com definition #9, "an instance of or the work of editing: automated machinery that allows a rapid edit of incoming news"). The fact that "edit" is mostly a verb could be a conceptual problem with REST, strictly speaking, but that is splitting hairs. In practice, there's nothing that makes /cd/edit/yanni-tribute unRESTful.

Criticism welcome.
-- dandv

karpet: CX::CRUD::REST follows the RESTful convention of keeping verbs out of the URL. The HTTP method is the verb; the URL is the noun.

zby: First of all 'view' is confusing - as you can see above people read it as an 'action' and then propose to add other actions in that place. That's why I would stick to 'instance' or perhaps 'by_id'. Second edit(_form) is a view on the object (special rendering of the object's data in an HTML form). I have to admit to karpet that CX::CRUD::REST mentioned as inspiration by you - does indeed have truly RESTful interface. So here it is:

POST      /foo                -> create new record
GET       /foo                -> list all records
PUT       /foo/<pk>           -> update record
DELETE    /foo/<pk>           -> delete record
GET       /foo/<pk>           -> view record
GET       /foo/<pk>/edit_form -> edit record form
GET       /foo/create_form    -> create record form

The only change I would make there is to add 'instance' or 'by_id' to '/foo/<pk>' so that 'create_form' (or perhaps other special views and extensions) don't clash with possible '<pk>'. If nobody objects I am going to replace the proposal and wipe the discussion - so that we can restart it with a clean state.


kd says ...

Catalyst's strong point is its flexibility. Scaffolding type helpers generally get in the way of this flexibility because they introduce assumptions, and your assumptions are not my assumptions.

So if this was going to be implemented incrementally the first thing would be to demonstrate how to interface Catalyst::Controller::REST to a single database table with a web service (GET POST PUT DELETE)

The second step would be to show how to do this equivalently with a a web browser, again with a simple database table.

Next up we do the same stuff demonstrating how to handle 1-1 and 1-? relationships

After that we do the same including ?-? relationships in the database.

zby: My plan is to deliver methods for creating/saving complex objects - what I need from the CRUD developers is a design where I'll be able to just override one or two methods and have this functionality added. The whole 'infrastructure' in the CRUD (retrieving the object, methods to URI mapping, redirecting after POST, compatibility with browsers etc) should be oblivious to the type of the object that is created/updated/deleted.

At this point we have enough information so that we know how to abstract this to a helper that will help initially.

My tags:
 
Popular tags:
  namespace REST URI design space
Powered by Catalyst
Powered by MojoMojo Hosted by Shadowcat - Managed by Nordaaker