2008-03-24 20:30 UTC URLs do not belong in the Views

Posted in: , , ,
It's bothered me for some time to hardcode URLs in my views. It means that if I want to revise the linking structure, I may need to modify the views in a huge number of places. At the same time I don't like the thought of putting it in the model either.

In the entry Enforcing Strict Model-View Separation in Template Engines I point to an interview with, and a paper by, Terence Parr that articulated this issue much better than I had fleshed it out in my mind.

He also proposed a solution: Introduce an extra layer - Renderers - that turn model objects or attributes into a displayable format. An example might be formatting a date string the appropriate way.

His work is centered on Java, and so the solution he proposes (see section 8.1 in his paper) is suggesting having the controller wrapping attributes of the model in separate objects. This is clearly the most flexible solution, in that it allows different controllers to provide different renderers, or use different renderers at different times.

How does this apply to the URL issue? It would be trivial to wrap model objects with a renderer who knows how to turn the model object into a URL referring to it. As an experiment I tried extracting the URL's for the Ruby app running this blog, and it showed me two things:

  • It's certainly doable.
  • It's a useful exercise even if I ultimately don't make the change, in that it exposed areas where I clearly hadn't thought the linking structure through very well - especially it made me notice areas in my admin code that could be made far more restful and consistent.

A nice thing with Ruby, though, is also that while you lose some flexibility if you don't wrap the objects, you can achieve much of the same separation by reopening classes. Meta-programming techniques and DSL techniques also means that you could make it very easy to add this type of functionality in a way very close to aspect oriented programming, containing all the URL generating code in a single location.

Which lead me to think that it would be nice if I could combine this with my routing, but I'm not sure if that'd be good or not - it would lead to intermingling concepts relating to the controllers and the model.

Does the URLs even belong tied to the model, and not in the controllers?

It's a tricky situation. On one hand, the controllers should handle the overall interaction. On the other hand, in all the web applications I've worked on the systems have been very data centric, and URL schemes have always revolved around the model. REST also favors a data centric approach.

But this is less of a problem than it might seem at first if you first employ separation of concerns by clearly handling the URL formatting either in a separate renderer class or as a separate aspect of the model classes through reopening the classes.

Especially if you pass a renderer (or wrap the model objects that gets passed to the view in a renderer object), the controller remains in the driving seat completely. Imagine an API like this for the view:

renderer.url(some_model_object)

Interesting opportunities does open up to tie this to the routing and still relatively loosely couple the classes. In fact, I would argue that a method like this reduces coupling between the controller and views: If you put URLs in your views, and you change the routing or what aspects are handled by what controllers, the URLs may change and the views may have to change with them. Contain the URLs in a rendering object passed from the controller, and you centralize those changes.

If the routing mechanism is designed to do it, it could conceivably provide a rendering class if the routing information is extended somewhat. On this blog "/tag/(.*)" maps to the Tag model class, and the (.*) group refers to the "name" attribute of the Tag model. If the router instead of mapping /tag/(.*) only to a controller, also contained the knowledge of which model and attribute the static and dynamic parts of the URL map to (if any), then the rendering could be automated.

Arguably, the router is part of the controller infrastructure (technically you'd call it a front controller) and centralizing limited knowledge of the model there is not a big concern, as long as this knowledge is provided by the application and not encoded in the routing component itself.

To take an example from my current router for this blog, suppose I turned this:

  map '/tag/(.*)',      C::TagController
  map '/(.*)',          C::ShowItem

.. into this:

  map '/tag/(.*)',      C::TagController, M::Tag, :name
  map '/(.*)',          C::ShowItem, M::Item, :url

.. to denote that our hypothetical renderer should turn renderer.url(some_item_object) into "/#{some_item_object.url}" and renderer.url(some_tag_object) into "/tag/#{some_tag_object.name}"

An alternative would be to change from straight regexps to something requiring a bit more parsing:

  map '/tag/{M::Tag.name}', C::TagController

... which also opens up the possibility of passing an already instantiated model object in to the controller. The downside of that route is that it requires the dispatcher to know how to create model objects, which either means it needs a factory or it would become tied to a specific mechanism for creating model objects. WIth that choice in mind the earlier syntax example seems more attractive - you trade a little bit of simplicitly in the routing for a whole lot reduction in coupling.



Comments - Newest first

2008-03-26 15:36 UTC
Vidar Hokstad
Mike, Thanks for the comment - I'll have to take a closer look at Agavi. I'm moving more and more away from PHP, but it's always worthwhile to look at how people do things.
2008-03-26 15:22 UTC
Mike Seth
This is exactly what Agavi does. In fact, there are named routes *and* a renderer mechanism. Routing is truly awesome, since routes can be nested, and you can walk the named route tree upwards when generating an URL; there is even a callback mechanism which allows you customizing URL recognition and generation
2008-03-26 15:21 UTC
Vidar Hokstad
Bryanl, if you read my comments above, I have already addressed why I don't consider named routes a good solution, especially when it's so easy to do in a way that produces less coupling (in fact, digging through the Rails source to look at the routing mechanisms just made me more convinced that my argument in "Why Rails is total overkill and why I love Rack" makes sense - when I've got time I'll revisit my views on coupling vs. cohesion in more detail)

That said, both Rack and Rails still inhabit a tiny niche in the web development world, and actually using named routes would be a big step forward for practically every single large web app code base I've seen.

2008-03-26 14:49 UTC
This is exactly what named routes are for.
2008-03-26 13:39 UTC
Vidar Hokstad
I was in a hurry when I wrote the comment above, so let me expand on it a bit:

The Rails and web.py approaches are clearly superior to using bare URLs. I don't think anyone can dispute that. I must admit I didn't even think of them when I wrote the original entry, though I knew of both, and was at one point reasonably familiar with the Rails routing (though I haven't kept up to date).

Tieing the views to symbolic names set in the front controller (routes) is far preferable to tieing views to the actual routes, and on checking the Rails docs again I do see you can ask for a url for a model object, which is great.

Digging in the source makes it look to me as if it only maps to the name of the model class, though (I hope I'm wrong). If so, that adds extra smell to me - I don't want my views breaking because I change the name of a model class.

At the same time, looking at it instantly made me worry about the level of coupling. The thing stopping from jumping right in was exactly that I don't want to replace one design smell with another, and tieing the views or controllers tightly to the routing mechanism rate pretty high on that list.

The purpose of the "Renderer" aspect in the paper I referred to is to uncouple this rendering aspect from all of the model, view and controller, and that was my starting point too.

Why does this matter?

It matters because there's no reason to couple the two. You don't buy anything. The view only needs a lookup mechanism that is dependent only on the model. The controller doesn't need to know any of the details, but _may_ want to actually modify aspects of the rendering.

An example of the latter, from actual projects I've worked on, is that it's not uncommon to want to explicitly re-route URLs via CDN's or redirect scripts. For a webmail app I worked on, we forced all a lot of links through a redirect because of stats requirements or to "wash data" out of the referrer URLs (Yahoo is an example of a company that's big on redirecting links - it has server farms dedicated to handle redirects). Webmail apps generally do the same for external links. Using a single mechanism to prevent accidental leaks from certain parts of an app is then a prudent security measure.

For another service I worked on, certain parts of the model would in certain circumstances act as a proxy for an external resource, and when linking directly _some_ of the objects should link to an external URL rather than an app internal one.

In those cases loose couple becomes valuable, because it allows the decision of how and whether to modify a mapping to be taken at any point in the chain without coordination - you just wrap the renderer object. Having the initial renderer generated from the routes is good. Tieing your model, views, or even controllers to a specific front controller or framework idea of what urls should look like is not.

(As a side note, when actually revisiting the Rails routing, and looking at the code, I was pretty appalled at the amount of type checks I found, instead of capability tests - i.e. explicit checks for Hash and Array classes instead of checking for the API functions needed, but that's a separate issue)

2008-03-26 10:57 UTC
Vidar Hokstad
Funny. I don't use Java. There are few languages I dislike more.

I'm not claiming it's something new, but given the amount of "naked urls" I come across in peoples views it's pretty clear that encapsulating the URLs outside the views is not all that common.

As for web.py, the approach is explicitly not the direction I'm heading in, unless something has changed over what I've seen (admittedly I don't spend much time looking at web.py).

Asking for a named route means the view need to know about a mapping between the url scheme and the model and the naming. Unless you then make the mapping completely abstract I feel that means you are entangling the view and the responsibilities of the controller.

I believe the same is true about Rails, but again I haven't looked very closely. Removing literal url's from the view isn't the goal. Removing any knowledge of URL's is the goal. Just adding a level of indirection doesn't achieve all that much.

2008-03-26 05:12 UTC
Britney Spears
Or web.py or....

what you need is to leave Java

2008-03-26 03:03 UTC
Isn't this what Rails already has in place with it's named routes and route path helpers?

Post a Comment

Basic HTML allowed. Javascript required for anti-spam check (I am testing a new anti-spam measure. Problems commenting? Please e-mail me: vidar@hokstad.com)

About me

E-mail: vidar@hokstad.com Skype: vhokstad
Twitter: vhokstad
View my LinkedIn profile.

I was born April 21st, 1975, in Oslo, Norway. Since 2000 I've been living in London, UK. I'm married and we just had our first child, Tristan Ikemefuna Hokstad.

I'm working for Aardvark Media as Director of Technology. I'm also currently on the board of SpatialQ, a startup in the GIS space, and an advisor to Skoach, a startup doing a time management app for people with ADD.

Twitter Updates

    follow me on Twitter