Saturday 2 April 2016

Composition with MEF

I was working on a project recently where I was asked to write a nice standalone greenfield app.  I thought 'brilliant, I can do this the way I like.  Nice clean code, follow patterns I choose' etc,etc.  Only restrictions were that I should follow the corporate model for things like logging, storage access and the corporate line was that as few third party dependencies should be introduced as possible.

The application was a desktop app,so my choice was to use WPF, follow a MVVM pattern and obviously write the code following the SOLID principles.  Well here came the first challenge.  Dependency injection and inversion of control are things that were not practiced in the business that I was working in.  This mean that I needed to introduce the concept.  Eliminating 3rd party, and most specifically, open source technologies, I was left with a choice of MEF or possible Unity as my DI technology (or IOC container if you prefer to think of it that way).

Now whilst I had used Unity in the past, I had not fond it the easiest to work with, but with the restrictions listed above it left me with only MEF as an alternative.  Many moons ago I had investigated MEF,and I liked the concept, but at the time, having only used it in practice tutorials and never in a production system (we all remember the calculator example that was around when MEF first came to prominenece) I wasnt sure it was the best choice.  Despite my reservations, I was sat next to a MEF evangelist, and I was convinced it was the way to go.  Little did I know at that stage that it would prove an inspired choice.

Soon I came to my first real challenge that MEF came to rescue me from.  I mentioned above the corporate model for logging,this involved simply using a library written in-house that wraps log4net and adds some custom logic before logging.  The pattern adopted to perform this was to instantiate a static member in each class of the system to perform any logging required by that class:



The problem was that I didnt want to instantiate it directly in this way.  I wanted to compose each object using DI.  I wanted to be able to unit test all classes without the need for concrete implementations of the dependencies of the class.  That meant to me that I needed to only have dependencies of  class that were defined by an interface.  A simple contract between the objects.  Assumptions would be made in the unit tests that all logic external to the class under test was flawless, and as such could be mocked from the interface alone using any given mocking framework.

So the question was how do I do this when the logging library does not expose an interface, and furthermore the constructor of the logging class requires the type of the object that is to contain it.  The first thing to do was extract an interface from the logging class.  By luck there was in fact only one such class, so the extraction of an interface was very straightforward.  And the organisation had adopted a policy of creating nuget packages for any shared libraries used in the organisation, so use of this new library wold be adopted for any project undergoing development.

Next came the problem of injecting the logging object.  How could I inject an instance of the logger class to the constructor of an object and know the type of the object in advance to construct the logger?  I could configure my DI container with n logger instances, each constructed with a given type as its constructor argument for the n types I have in my system.  This seemed stupid and a maintenance nightmare.  I would need to change my DI container setup for each class I wrote.  I cold create a new constructor for the logger class, a default one, and pass the type in via a method or property.  But this wold involve the developer always remembering to perform this extra step of setting the type on the logger class. An unnecessary additional step that wold invariably be missed and lead to countless bugs.

In steps MEF with it great range of possibilities,  Not only does it allow you to inject objects via a constructor (or to a property, although I am not a fan of this.  I feel if a class needs another object, this should be clearly conveyed to the developer by appearing in the constructor argument list.  I prefer the external dependencies of my classes to be immutable.  Property injection to me seems to result in a class that will do something that is not at all clear to the developer, almost like the person writing the class wants to hide something.  As a side note it can be used to be round circular construction dependencies, but to me if you have such a circular dependency your design is wrong and you need to rethink your architecture at that low class level.),and back to my point, it allows you to inject anything to the constructor.  You can inject a simple value type.

You can inject a delegate, and that is where the solution to my problem came from.  I chose to inject a function that takes a type as a parameter and returns an object in the form of a reference to the new logging interface:




But how would I set up my MEF container to have such an entity (for want of a better term)?  WellI created a static class in the logging library that exports the function.  This wold be picked up by the population of the MEF container, and injected into any class the requires it.

This is nothing revolutionary in terms of how to construct an object.  It is simply the factory pattern, here the factory is the function.  But in terms of injecting an object that needs to know something about the target of the injection I feel it is very neat.  If you want to inject a different implementation of the function, you could, if you want to introduce a new implementation of the logging interface this would simply involve changing the factory function.  Clearly I have written this code in the simplest way,and to make it more extensible I might choose to pass more parameters to the factory function, which by changing it wold break any current use of it, however I don't foresee a need for this any time soon, and I believe in only building for what the requirements need now. I don't try to cater for every 'what if' that might rear up in the future as I could never think of them all, never mind code for them all.

No comments:

Post a Comment