It seems like building a REST application is The Right Thing To Do. I tend to want to create a controller for each entity in my object model, and the rationale behind the REST paradigm gives this idea some support.
I also appreciate the "Pylons is for HTTP and nothing more" convention. I am, however, left to wonder how I should go about building all the other stuff that we need from a web application, the machinery that lives between the database and the templates in particular. Assume for a moment that we are writing a Facebook work- alike in Pylons. Then, a GET of http://pylonsfacebook.com/user/brianthelion would be a request - and I'll use Facebook-speak here - to view BrianTheLion's "profile." If we're following the REST formula that we've argued for, then UserController.show(id=brianthelion) is getting executed. Awesome. At this point in the example, though, there is giant code-hole. On Facebook, a user's profile is an extremely complicated UI. It's a "hybrid view" (is there a better term?) of data from many different model entities: user, event, photo, message, mail, news, etc, etc. To build our work-alike, we need to take information about the user, the events he's invited to, the photo's he's in, the things his friends are saying, etc, and render all of that to fun-to-look-at HTML. So, the context of our PylonsFacebook, the question is: How does UserController.show() go about producing this immensely complicated output? How should all of the code between HTTP and HTML - and there's a lot of it; the majority, in fact - be encapsulated? How can we best make it reusable? SQLAlchemy aside, this territory seemed pretty lawless to me. In my naivety I came up with the idea of "named views." For example, a GET of http://pylonsfacebook.com/photo/981388324/icon and http://pylonsfacebook.com/photo/981388324/full would render the "icon" and "full res" views of that photo, respectively. Pretty self- explanatory. This made some nice sense because then for the PhotoController I'd have a "show_icon" method and a "show_fullres" method, as well as a "photo.mako" with "icon" and "fullres" defs. The rendered HTML output of the aforementioned URLs served as nice visual building blocks for more complicated web pages, and GET-ing them allowed me to test the code easily. I ran into problems, though, when I started needing to build "hybrid" views, named views that depended on other named views. For example, http://pylonsfacebook.com/user/brianthelion/photos would call UserController.show_photos(id=brianthelion). It's output would depend on the output of, say, PhotoController.show_icon(id=29459285) and PhotoController.show_icon(id=299498594). So then I'd need to instantiate a PhotoController from within my UserController, and that seemed - at least in MY mind - to break the Pylons paradigm. Any thoughts? Thanks a ton for everything that has already been offered up! Cheers, ~br On Jul 23, 8:17 am, Diana Clarke <[email protected]> wrote: > I've written two pylons apps now: the first a typical web application, > and the second a RESTful API. > > In both cases I started with: > > paster create -t pylons helloworld > > followed by (for the web application): > > paster controller hello > > or (for the RESTful API): > > paster restcontroller hello hellos > > These code generation tools allowed me to get up and running fast, and > made it easier to find applicable examples on the net since I was > essentially following a set convention. I also had enough domain > specific work to do as it was, and trusted that there were smart peeps > behind pylons and paster, and that they had already iterated on this > part of the stack for me. > > The generated code from 'restcontroller' was especially helpful -- it > made building a RESTful API a simple paint-by-numbers exercise. > > HTTP_DELETE maps to delete(): you just need to implement some > delete resource code > HTTP_POST maps to create(): you just need to implement some create > resource code > HTTP_PUT maps to update(): you just need to implement some update > resource code > .... > > http://wiki.pylonshq.com/display/pylonscookbook/How+map.resource+enab... > > Until I had a better idea about what I wanted from the controllers, I > just did what the generated code comments told me to do, but at the > same time was careful to move similar code to the BaseController > (base.py) and use consistent form parameter naming conventions across > resources. > > Eventually I had N controllers that each implemented index(), show(), > create(), update(), and delete(). Each of these methods was only 3 > lines of code: > > line 1: gather up the form parameters, or payload > line 2: pass them to the service layer for processing > line 3: serialize the response (json or XML) > > I've since been able to move pretty much all of this code to the > BaseController (still just ~100 lines of code), and each of the N rest > controllers just extend BaseController and define the service they > should defer to for processing. > > So, in my case, I was able to get away with dead simple controllers: a > couple of lines max with only an __init__() method. > > My controllers are now, by definition, consistent -- making it easy to > write libraries on the flip-side to consume them. > > -- show() for every resource accepts "include" options allowing you > to ask for nested child resources in one HTTP request rather than > making two requests (one for each resource). For example: get a folder > and the folder's child files in one request. > > -- index() for every resource accepts search filters, sorting options, > include options, paging options etc and always returns a collection > with the resulting pagination info (page, pages, per_page, total). > > etc... > > So, I guess my answer to what belongs in a pylons controller is almost > *nothing*. My general rule of thumb is that I use pylons for all > things HTTP and nothing more: > > - HTTP URIs (id, format) > - HTTP methods (PUT, POST, GET...) > - HTTP routing (map.resource) > - HTTP form parameters (request.params.get) > - HTTP payload (request.body) > - HTTP response codes (401 Unauthorized...) > > The payload gets turned into an incoming DTO (data transfer object), > the service knows what to do with it and returns an outgoing DTO, the > DTO knows how to serialize itself based on the format. > > def show(self, id, format='json'): > > The services I keep mentioning are in another python application > completely -- this is to encourage the "pylons is for HTTP and nothing > more" convention. > > The services are actually pretty complex in cases: persisting data to > a cloud and databases (SQLAlchemy), firing events to RabbitMQ, > triggering sub-actions, enforcing ACLs, validation, business logic, > etc. In my opinion, none of this belongs in a controller. > > BTW, I mean no offense with: "pylons is for HTTP and nothing more". > It's just a convention we employ for this particular project to keep > the responsibilities clear. And it is definitely more applicable for a > RESTful API vs. a web application with views using Mako templates for > example. In that case, my controllers do have a bunch of tmpl_context > assignments. > > Cheers, > > --diana -- You received this message because you are subscribed to the Google Groups "pylons-discuss" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/pylons-discuss?hl=en.
