I’m not a big fan of the Zope 3 Component Architecture. A couple packages came out recently that made it clearer to me why.
The packages in question are repoze.bfg.httprequest and repoze.bfg.restrequest. restrequest is easier to understand because it is more concrete: basically it adds marker interfaces to your request object for the request method, and you can add request_type=IGetRequest to your view declarations, hence declaring that a view is only applicable to that request method. You can do this with a decorator like:
@bfg_view(request_type=IGETRequest, for_=models.IMyModel) def my_view_get(context, request): return Response("GETn")
Right now repoze.bfg (a web framework) uses interfaces to do it’s dispatch to views, so this adds the ability to dispatch on request method. The existence of these packages bothers me a great deal, and it took a while to figure out why. These are entirely packages, with docs, tests, etc., that express something trivial. If repoze.bfg supported conditionals on views — functions that would indicate if the view was applicable to a request — then you could express this like:
@bfg_view(for_requests=lambda req: req.method=='GET', for_=models.IMyModel) def my_view_get(context, request): return Response("GETn")
This is basically what Routes has done, though it additionally added request-method-dispatch as a primitive (just a keyword like method=’GET’). I think this is a much nicer construct, and no extra packages are required. More importantly, it doesn’t just read well (arguably IGETRequest reads a bit better), but it is easy to understand the mechanism and to extend or adjust that mechanism. If you want to dispatch based on the Accept header, you just use for_requests=lambda req: ‘application/xml’ in req.accept. If you don’t want to write lambdas, you can just abstract this in the obvious way in a separate function or a class with a __call__ attribute.
OK, so that’s simple enough: repoze.bfg should support this construct, and it’s just an unfortunately and hopefully temporary awkwardness that caused these libraries to be written. In theory anyone could, right now, write an alternative implementation of @bfg_view that accepts this. Of course, anyone could spend an hour or two and write a new framework from scratch, which makes this a less compelling argument: if someone decides to use repoze.bfg instead of writing their own framework or using a different framework, it’s not because they want to write or maintain their own infrastructure, or be in the situation where they’d ask “can you help me, but oh by the way I’m not actually using your stuff”?
The other reason for this, I think, is ZCML. Here’s another way of expressing method-based dispatch:
<bfg:view for=".models.IMyModel" request_type="repoze.bfg.restrequest.interfaces.IGETRequest" view=".views.my_view_get" />
There’s no place to put functions into ZCML, but there is room for interfaces. And more generally, interfaces are the tool that comes to mind most commonly when using the Component Architecture. I think it leads to this kind of anti-pattern all too often, the use of marker interfaces and the accompanying indirection when a simple function would do. There’s a claim that interfaces allow for more flexibility, but this is simply false: functions are completely general and ameniable to any kind of abstraction you want. Interfaces add indirection, and sometimes that’s useful, but in many cases like this the indirection is added before there’s any need for it, and when there might never be a need for it.
It can be hard for me sometimes to articulate what bothers me about Zope 3-style architectures, because it’s hard to point to what exactly the alternative would be. Usually the alternative is: a few lines of code. Code is a powerful and extremely general abstraction, and especially in a dynamic language like Python the amount of abstraction you can add is substantial. And you don’t need to add the abstraction until you need it. So usually the alternative I see to the Component Architecture is simply a few lines of code, written for the specific case that is at hand.