Limitations of Declerative Coding
David Hayden is talking about AOP, and he mentioned Caching declaratively, which sturk a chord, and I had to write a post about it. I want to talk about a completely different aspect of declerative coding, how far you should go with it. Just to be clear, this is fake code, and it has little to do with David's post other than using the scenario to make a point.
Let us take the code example that David has:
public class NewsController { private INewsService _service; public NewsController(INewsService service) { _service = service; } [CachingCallHandler] public NewsHeadlineCollection GetLatestNewsHeadlines() { return _service.GetLatestNews(); } }
Now, assume that you want to specify an expiration time? No problem, just put it in the attribute:
public NewsHeadlineCollection GetLatestNewsHeadlines()
{
return _service.GetLatestNews();
}
What if I want to add a bit of smarts to the caching and only cache costly calls?
public NewsHeadlineCollection GetLatestNewsHeadlines()
{
return _service.GetLatestNews();
}
Now I want to handle dependencies:
DependencyStatement="SELECT * FROM HeadLines Where datePublished > getdate()-1")]
public NewsHeadlineCollection GetLatestNewsHeadlines()
{
return _service.GetLatestNews();
}
But wait, I have a bit of business logic here as well, if there is a developing story, I want to skip caching altogether:
DependencyStatement="SELECT * FROM HeadLines Where datePublished > getdate()-1")]
public NewsHeadlineCollection GetLatestNewsHeadlines()
{
if( _service.BigStoryNowDeveloping)
CachingCallHandler.Current.SkipCaching();
return _service.GetLatestNews();
}
And now I have a different case, I want to call the GetLatestNewsHeadlines() and get the uncached results, maybe I am building a ticker, and it really need to be up to date. Now I need:
return newsController.GetLatestNews();
This is a fake scenario, obviously, but just note how each additional business requirements make declerative more complex. I am not showing the code for the CachingCallHanlder, but you can be that somewhere its developer would be groaning for each such new requirement.
For myself, the minute that I put the connection string name hard coded SQL, that went way over the line. Even if I could skip it and use just the table name directly, that would be too much. And what happens if I need expiration from sources that do not support cache dependecies natively?
In short, I really love declerative coding, and I tend to overuse it at times, but it is really important to understand that you probably shouldn't try declerative coding for everything. If the scenario is simple and straightforward, that is a good candidate for ocde reduction and simplification. If it isn't, imperative coding (you know, if and else, as well as a while or two) are going to be more intent revealing, and would be easier to maintain.
Comments
Sometimes I think we jump through rings of fire and eat little pieces of glass in an effort to find a pure, unleaky, elegantly decoupled abstraction. When the amount of effort that goes into this process becomes excessive and beings to produce a lot of complexity, sometimes IMO it makes more sense to not hide from the underlying database engine or other dependencies and just accept that it's not a perfect world. Maybe the piece of software you're describing is really nothing more than a thin layer of eye candy over a SQL statement anyway.
Heretical, perhaps, and I wouldn't go this far most of the time; but my main point is, there are far, far worse things lurking in code bases than inline SQL statements. Conventional wisdom needs to be judiciously broken now and then.
I read the comment with interest, fully agreeing with you, until the last statement.
Yes, there are very good reasons to breaking the rules, but breaking DRY and decoupling should really make you think before you try it.
Oren,
I definitely agree with your example. However, just to be fair, if someone wanted to go ahead and implement a declarative caching strategy (or whatever strategy is in question), then one can probably encapsulate the different "policies" (i.e. expiration time policy, execution time policy, dependency query, etc) into distinct policy objects. You can, in turn, compose these policies (i.e. a chain of policies that can be evaluated to produce a boolean stating whether caching should be supported or not) and mark your field/property/method/whatever with an attribute that takes a type of policy that you want to use (i.e. [CachingPolicy(typeof(MyCachingPolicy)] ...).
Additionally, one can create a policy composer that can read the app.config (or config from a custom provider) and create the chain of policies (Spring.NET could be used here, as could Windsor I suppose).
Of course I'm just throwing out some ideas, but I certainly understand your main point that sometimes creating what could be perceived as an elegant design could, in fact, turn out to be not so "elegant." :)
Comment preview