Showing posts with label Design. Show all posts
Showing posts with label Design. Show all posts

23 March 2013

Customizing the Ninject kernel configuration for use with WCF proxies

On several of our projects, we are happily using Ninject for our DI needs. Some service implementations we wrote actually call other services using WCF proxies created through an IChannelFactory. Those proxies are injected as dependencies by Ninject where required, and their lifetime is controlled using the InRequestScope() extension for ASP.NET MVC controllers or WCF services.

However, one thing that worried me was the well-known fact that Dispose() cannot be called on proxies without paying some attention to exceptions (see here). Unfortunately (and quite normally), the Ninject StandardKernel is not aware of the gory details of cleaning up correctly WCF objects implementing ICommunicationObject, like proxies. Indeed, as proxies also implement IDisposable, they are by default taken care of by the DisposableStrategy of Ninject, which does a simple call to Dispose() when the object’s lifetime is over.

Fortunately, the Ninject architecture is highly modular and it is very easy to add, remove or replace components in the kernel. A Ninject kernel implements the IKernel interface, and Ninject provides an abstract base class and a full implementation (the StandardKernel).

A kernel internally uses a set of components that provide implementations of various Ninject interfaces it needs. The actual implementations to be used are chosen in the AddComponents() method shown above. The kind of components that take care of object setup and teardown are the ones that implement the IActivationStrategy interface, like the DisposableStrategy, shown below.

The StandardKernel uses a set of activation strategies that are brought into it in its AddComponents() method:

Given this design, it is very easy to replace the DisposableStrategy by a new one that is aware of ICommunicationObject behavior:

We then derive from StandardKernel (or from KernelBase) to use the new activation strategy:

Et voilĂ ! No more nasty CommunicationObjectFaultedException when trying to Dispose of a faulted channel. By the way, this is also how we can remove things we do not need from the StandardKernel. In our case, we know we are going to use exclusively construction injection (no method or property injection), and we will not need support for using one of the Ninject object lifecycle interfaces (IStartable, IInitializable). Therefore we removed the components (activation and planning strategies) dealing with these things from our custom kernel. This is also a nice way to enforce those design decisions (constructor injection only, no dependency on Ninject lifecycle interfaces), just by simply removing the features from the container itself.

As a conclusion, I must say that I really like Ninject. Its clear design makes it easy to use, extend, and customize. It maybe consumes a few more CPU cycles than other DI containers, but it does its job very well, and is an ideal choice to introduce DI in a team that is not familiar with the concept.

31 July 2008

It’s all about coupling…

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 ;-)…