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.
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.
On human-friendly, clean URLs:
- http://seo2.0.onreact.com/top-10-fatal-url-design-mistakes: Numbers vs. speaking URLs:
Decide, 123 or angelina-jolie-naked, which URL speaks your language, which one you'll rather click?
On RESTful URI design:
- basic guidelines on constructing RESTful URLs
- when to use query parameters (mainly for filtering a view; beware that the query string part may mark the resource as uncacheable)
- singular versus plurals (slightly mistaken, read below for thoughts on plurals)
- Huge Catalyst mailing list thread on REST
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:
- 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:
- there are words which have the same form for plural and singular:
- 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.
- 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).
- URIs like /customer/instance/johndoe allow for additional actions on /customer.
Aspects open for debate:
- /foods/instance/orange-juice/edit, or /foods/instance/edit/orange-juice?
- How do I handle multi page forms? /customers/1234/name ? /customers/1234/addresses/9876 ?
- 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.
- 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
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.
Showing changes from previous revision.