When designing a framework or reusable components, a basic design principle is to keep an eye on the component's dependencies. These dependencies towards other components should always be minimized. Having external dependencies will force the user of the component to depend on these as well, which is generally not desirable for many reasons: sensitivity to change, release cycles and versioning, conflicts with other components… This is the well-known high cohesion/loose coupling story.
I was recently (today) confronted with a component whose design does not quite follow that principle: the Enterprise Library Exception Handling Application Block. While I find that the component does what it has to do rather well, I don't appreciate at all its dependencies. My idea was to write an exception handling aspect using PostSharp and the Exception Handling block. Rather simple, no? Well, no, not so simple, as I have a number of constraints: my architecture uses Dependency Injection, and I don't want any instrumentation for now to name two. These constraints have the effect to make the block in its current design useless for me, as it has direct dependencies on ObjectBuilder2 and Unity and instrumentation primitives. In other words, it is tightly coupled to its surrounding runtime environment. Of course, my DI container is not Unity (I use Spring.NET), and I do not want in any way to depend on it!
I think that when you just want to implement a consistent exception handling across an application (using a component that handles this task well), that should not force you to use DI, and certainly not a specific DI container. This goes against some good design principles.
What I could admit is a dependency towards an abstract assembly that expresses the need of the block for a DI infrastructure (e.g. through attributes), but not that the block depends directly on a particular implementation of such an important infrastructure component as a DI container. Now, I'm stuck with two possibilities: modify the block code to extract the depending code in its own separate assembly (let's hope it is well isolated, I'll have to check this), or redevelop my own block implementation (sad because I like how the core concern of the block is handled…).
And by the way, how do I get rid of those instrumentation calls? It's not that I do not recognize there is value in that stuff, but I do not need it in a (unit) test environment, and I don't want to install any perf counters or configure WMI on my laptop just to handle exceptions. In previous versions of the Enterprise Library, we could recompile it and exclude the instrumentation using conditional compilation, but this does not seem to be possible any more.
Conclusion: always actively manage dependencies (minimize them), and when designing a framework or a reusable component, always think that the user must only pay for what he actually uses.
I would be curious to let NDepend run on the whole Enterprise Library and see what the results are. Maybe if I have spare time ;-)…
No comments:
Post a Comment