Screw the pattern
Gimme a framework!
Keving Teague, I discovered, has a wonderful blog. By pure and utter co-incidence, it looks a little bit like this site, but don't worry about that. If you've read Kevin's great mailing list replies, you'll know that he's an excellent writer. I particularly enjoyed his write-up on the perils of rolling your own intranet. He's been around a bit longer than me, but boy do I know what he's talking about.
Anyway, I disgress.
Kevin wrote about the Movel-View-Controller pattern, and how it's neither a very good pattern nor really a pattern at all. I'm so glad he said that, since I've been reading about MVC for a while now (first in the context of Java GUI applications, then in the context of the web) and whilst I now understand it, I never really liked it. It wasn't obvious to me where a particular piece of code should go, and what should call what. People seemed to disagree: does the view call the controller? Does the controller set up the view and render it? Can the model have methods or is it just data? What will the consequences be if I don't follow one of those prescribed models? I like simple things, things that you don't need to do a double-take to get right. The fact that no-one seems to have a unified story on MVC reflects the fact that, unlike a true pattern that is small and universal, MVC attempts to encapsulate too much.
Let's take a modern Plone content type as an example. I normally advocate that it consists of the following:
- An Archetypes content class. This should be as minimal as possible, and ideally have no code beyond the schema and base class. Archetypes itself, of course, does not adhere to that internally - it has a raft of mix-in classes, but as the developer you don't need to worry about that. Do as we say, not as we do.
- A unique interface implemented by this class. Ideally, that describes the fields of the class, but if you're too lazy to do that, a simple marker interface will do. This is important, since Zope 3 components work in terms of interfaces - and so should your code. If you want to find all objects of that type, don't search for the portal_type, use the object_provides index and pass the interface as a search key. Why? Because there may be other types that act much like your type, and there's no reason why your code shouldn't work in terms of those types if they fulfill the interface.
- Views – the Zope 3 kind. They consist of a template and/or a class that holds view logic. Normally, you have both: the template stays pretty clean, and data is fetched and prepared by methods in the view class. This is nice, because it gives you a natural place to add code. In the old days, we used to have to create a script python or even a whole CMF tool (a big fat persistent object in the root of your site sucked in via acquisition) just to get some logic, so we did convoluted python: expressions in TAL instead and told ourselves we'd refactor later. Right.
- Adapters on the content type interface. This is when it gets a bit tricky for some people. The view class can of course do whatever it needs to its context (and technically, a view is an adapter, but that's irrelevant here), but logic in the view is not primarily meant to be general or re-usable. If you have "business logic" (whatever the hell that means) that is more specific than just the particular presentation of that content type, it's better to put it in an adapter. Other code (such as your views, but really anything) will adapt from your content type's interface to a small, well-defined interface that describes how to perform this particular logic. The Component Architecture picks the right adapter that's capable of performing that operation (say, send an email with recent changes) given a particular type of input/context (say, your content type, which may be, say, an issue tracker ticket). In the old days, we didn't have adapters so we put all kinds of methods on the content type class. Again, that's fine, except it gets hard to re-use and it gets even harder to swap your type out for something else since it now has such a "fat" interface.
Okay, so that's a little bit like MVC, but not really. It's pretty clear what's the model (the Archetypes content object – it holds the data), and what's the view (the view, funnily enough). Various adapters providing various functionality are not really controllers, since they don't control application flow. Perhaps the Zope publisher, in conjunction with some (or all) of the code in any view class, are the controller part of the equation. Perhaps Zope just lacks this crucial feature? Or maybe it doesn't matter.
Let's look at another example: Pylons. Pylons isn't so concerned with content objects that live in a hierarchy. Its URLs typically correspond to controllers. You may do something like this:
- You create a controller. It has a name and a specific purpose. When you visit the URL http://mysite.com/thecontroller, Pylons calls the index() method on the controller object and lets it do whatever it wants.
- Quite often, it will set up some state (perhaps stuffing it into the global c variable, which is just a convenient way of passing that state to the display logic: this is a bit similar to how we in Zope 3 views set variables on the view class itself, and access them from a template using view/varname in TAL).
- To set up that state, it may need to talk to a database. Pylons encourages you to use SQLAlchemy to create object/relational mappings, giving you a pretty clean, Pythonic API to access and manipulate database contents.
- Once it's done, it will typically render a template, and the template uses the prepared information to display a page to the user.
So here, it's pretty clear that the controller is king. Views and models are much simpler, and the locus of control lies in the controller class.
So why is it not like this in Zope and (especially) Plone? Well, mostly because the most common (perhaps only) operation is to renderthe current "context" – that is, the object you've presently traversed to via some URL. The Zope publisher does exactly that, looks up the appropriate view for that object (or takes a view you specified as part of the URL) and asks it to render itself.
Of course, you may have a form or some other action that needs to perform some operation. Here, we actually re-use the view concept. Upon traversal, a view is "called", which usually means "render the current context". However, it doesn't need to do just that. It can process form input and perform a redirect or steal all the cookies in your cookie jar. So then, it's a bit like the Pylons controllers: it performs some logic, it pre-calculates the state necessary to render a page, and it either renders a template or redirects to another view.
Why this difference? Probably because Zope (at least the way we use it in the Plone/CMS world) optimises for a particular type of usage and the Zope developers have tried to create a framework that makes it easy and natural to write that. In Zope, we spend a lot of time thinking about our "context" (the content object being rendered/manipulated). With Pylons, you are more likely to be writing an application where the number of permutations of URLs is relatively low, and each URL location has a specific purpose and behaviour.
Which brings me back to the point: I think that all good developers should understand the principle of patterns, and I think that there are a number of core patterns that are very, very useful to have up your sleve and know how to use well. However, the types of applications and systems we write today are too complex. I don't believe in patterns that prescribe a particular solution at that level of complexity and such a low granularity. I want to be led down a path that's consistent and feels intuitive, and I think you only get that from choosing the appropriate framework (and language).
As Kevin points out, MVC, in the context of web applications, is as much a response to the pre-MVC (what Java folks call Model 1) world as anything: it's a call to take separation of concerns seriously. That's the world of naive PHP or JSP, where a page executes top-to-bottom and output happens as the page is built. That's definitely not a way to build complex applications (and I do find it quite funny how people use separate templating laguages on top of PHP, which itself is really just an over-rich templating language) that you want to maintain. A bit like Kevin's first few intranets again.