ServiceLocator as an IQueryable

I had an idea few days ago, about using an Enumerable Queryable as ServiceLocator, lets say a sequence of things that you can query.

I will explain this idea through simple tests. Suppose we have the following interface:

public interface IContainer
{
    IEnumerable<T> GetServices<T>();
    T GetService<T>();
}

1-It is a bad thing to ask to our ServiceLocator for all services so:

[Test]
public void WhenEnumeratingWithoutTypeThenThrowException()
{
    var container = new Mock<IContainer>();
    
    var sl = new QueryableLocator<object>(container.Object);
    
    sl.Executing(q => q.ToArray())
    .Throws<InvalidOperationException>()
    .And.ValueOf.Message
        .Should().Be.EqualTo("You must start with an OfType call.");
}

Prety easy to understand, if I try to enumerate for ALL services, just throw an exception

2-How to query all instances of a given type registered in the container.

[Test]
public void WhenEnumeratingWithOfTypeThenReturnAllInstancesOfTheGivenType()
{
    var container = new Mock<IContainer>();
    var serviceInstances = new []{"a", "b"};
    container.Setup(c => c.GetServices<string>())
             .Returns(serviceInstances);

    var sl = new QueryableLocator<object>(container.Object);
    sl.OfType<string>().ToArray()
            .Should().Have.SameValuesAs(serviceInstances);
}

I can query for all services implementing a given interface. Same stuff is on CommonServiceLocator.

3-How to get an instance of a given type

[Test]
public void WhenEnumeratingWithOfTypeAndCallingSingleThenResolveOnlyOneInstance()
{
    var container = new Mock<IContainer>();
    container.Setup(c => c.GetService<string>())
            .Returns("a");

    var sl = new QueryableLocator<object>(container.Object);
    sl.OfType<string>().Single()
        .Should().Be.Equals("a");
}

This is the most widely used scenario, doing sl.OfType().Single() is the same than commonServiceLocator.GetInstance()

4-Throw an exception when you try to do a Select, Count, Where, etc

[Test]
public void WhenExecutingAnUnrelatedLinqMethodThenThrowException()
{
    var container = new Mock<IContainer>();
    
    var query = new QueryableLocator<object>(container.Object);

    query.Executing(q => q.Count(s => true))
        .Throws<InvalidOperationException>()
        .And.ValueOf.Message
            .Should().Be.EqualTo("Count is not allowed over the service locator.");
}

Ok, this is all for now.

Advantages of QueryableServiceLocator over CommonServiceLocator

There are two big advantages:

  • You don’t need an extra assembly Microsoft.CommonServiceLocator. In fact IoC containers can provide their own implementation, so there is not need for an implementor assembly.
  • Easy to create Factories, you don’t need to write another interface/implementation for factories. Do you want a MessageHandlerFactory? var messageHandlerFactory = serviceLocator.OfType<IMessageHandler>().

TODOs

This is a rough idea, so far I don’t know how to handle the following two scenarios:

  • Retrieve a service by key, generally speaking a string key.
  • Create a factory of an open generic type, like “IRepository<>”

 

The implementation is right here. The class you might be interested on is this one.

Ideas?


blog comments powered by Disqus
  • Categories

  • Archives