Update:
For some reason Community Server decided that it doesn't want to display the style for the code snippet nor the IFrame for the skydrive link. Here is the final file. Read on for explanation of what it is and what it does.
Browsing though the blog of my friend and co-work, Jon Fuller, I noticed this post on Dependency Injection and Service Location. Especially interesting was this quote:
I was having a discussion with a colleague the other day about DI and Service Location in the context of the question (posed by a third person):
Which DI container/framework should I choose?
His answer:
None; just roll your own and use simple Service Location
It definitely sounded like I’m the colleague in question here. He then goes on to argue against my love of the Service Locator pattern. I think I should clarify what I meant with my statement. For reference, here is my code for a simple service locator. The usage is simple, first I register a service with the Container:
ServiceContainer.RegisterService<ICalculatorService>(new ConcreteCalculatorService());
Then anyone who wants an instance of the ICalculatorService can simply ask for it:
var calc = ServiceLocator.GetService<ICalculatorService>();
This method works for a vast majority of the simple cases where you want to provide a Separation of Concerns. In most cases you don’t need advanced lifetime management (e.g. a single instance of a service will do just fine). The reason I suggested rolling their own was that I assumed from the question the person had no experience with an IOC container prior. It takes time to grok an IOC container, there are a lot of moving parts, and for a simple case, this solution works just fine.
So what happens when I want to allow for more intricate cases. Like different lifecycles for service (per request, per transaction, singleton, etc). Well the choice boils down to extending/enhancing your service locator implementation, or using the Common Service Locator library in conjunction with a container of your choice. You have to decide when the cost of maintaining your own outweighs the benefits. Adding support for a per request lifecycle is simple enough as well however. Here are the changes to the Service Container that supports both a single instance and per request lifecycle:
private TService RetrieveService<TService>()
{
Func<TService> retVal;
_ServicesLock.EnterReadLock();
try
{
retVal = (Func<TService>)_Services[typeof(TService)];
}
catch (Exception)
{
throw new Exception(
String.Format(
"Service Type {0} not registered with container.", typeof(TService).Name));
}
finally
{
_ServicesLock.ExitReadLock();
}
return retVal();
}
public static void RegisterService<TService>(Func<TService> creator)
{
_Container.AddService(creator);
}
public static void RegisterService<TService>(TService instance)
{
RegisterService(()=>instance);
}
I just added an overload to the static RegisterService method that takes a Func that returns an instance of the service, I then change the original method to call the new overload and change the private RetrieveService function to do things properly. To register per request, you now make this simple call
ServiceContainer.RegisterService<ICalculatorService>(()=>new ConcreteCalculatorService());
Using the service is the same as before Now let’s address some of Jon’s concerns regarding the alleged shortcomings of the Service Locator pattern.
Some of his concern stems from the API for the service locator. (Why pass in a separate key when you already have a ready made key in the form of the service type. Also, I’m assuming the “movies.txt” is the parameter for creating an instance of the concrete IMovieFinder. Again, this is not an issue because the instance is created at registration (or using the modified version, through the creator delegate).
With regards to testability, when writing a unit test it’s easy enough to configure a Service Locator to return mocks. Just register the mocks in your setup and call clear on your teardown.
That leaves us with discoverability for consumers of your code. Documentation is a good place to start. Also, to me putting the dependencies in the constructor opens the door for things like this:
var lister =
new MovieLister(
new CsvMovieFinder(
new AzureBlobFileRetrieval(
new AzureKey("account","secretKey"));
True this could be caught in a code review. But it can also be outright prevented by using this great thing called encapsulation. When someone creates the MovieLister that uses the ServiceLocator and debugs, they will get a nice friendly message informing them that they need to register an IMovieFinder with the service container (hopefully they know to do so in the Main() function or wherever your project does it’s container configuration).
To me, there is no major detriment to using either style. Rolling your own involves dealing with maintenance of that solution, but it’s easy enough to swap it out with a third-party container once you have the need for it. Here’s the code for my basic service container. A lot of other scenarios are trivial to implement here as well. And I’ll share them with you in a later post.