I’ve been bothering Tim Ewald for a while to post a followup to my comments about AOP and code generation, since he was the one that inspired me to write about it in the first place. Well, he’s having trouble with his blog, so I’m posting it here. Now if only Ted Neward would get off his butt and post his response. :)
From Tim Ewald:
I've been hanging out with Craig a lot lately, he's doing some work for our team. He posted his thoughts on AOP a couple weeks ago and mentioned my work in that area. We've been carrying on the conversation ever since, and I promised him an online response.
I did a ton of work with AOP when I wrote Transactional COM+. By the end of that project, I had reached one main conclusion: call-interception is not a good, general purpose extensibility point. COM+ (at the time) was an entirely closed system with a fixed set of services that could be used in conjunction with one another. Even in that very finite universe, the interplay between the services is quite complex. Making that universe infinitely large by allowing third parties to mix in additional interceptors with arbitrary behaviors‚ is a mistake. The complexity goes through the roof, because, as Craig noted, the services typically are not orthogonal.
Is interceptor-based extensibility any worse than other types of extensibility? I think so. Method-level interceptors change the path from a call-site to a method's code. It's pretty hard to write reasonable calling code if you don't know how method invocation actually works and it varies on a case-by-case basis.
Probably the best example of the potential pitfalls is error handling. I want to get the exceptions thrown by the methods I call. If there are n interceptors between me and the method, that may or may not happen. Maybe an interceptor will catch the original exception, and throw a new exception. If I don't know that happened, how will I ever track down the real error? And what if the original function didn't cause an exception at all, it just left the world in a state that some interceptor didn't like? How do I know what happened? The path from a call-site to method code should be sacred. Interceptors can alter in ways that are not clear (and often are not documented), in essence encouraging "programming by side effect", which is a really bad idea.
COM+ 1.5 added support for "Services without Components", aka CoEnterServiceDomain, and Everett exposes this new functionality as managed code. No components means no interception, which I like. These APIs were really designed to allow COM+ services to be deeply integrated with low-level plumbing, but I like to use them in my own application code. Craig and I wrote an MSDN Magazine piece about how this works. However, judging by the ratings, most readers don't find the idea too attractive (or they just don't like our prose ;-).