José F. Romaniello

Las aventuras y desventuras de un codificador.

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?

| More

2 comentarios:

That's a beautiful idea. In fact, you can get rid of the GetService (singular) method and make it an extension method instead.

Now that you have a single-method interface, you could even consider further reducing this and represent it as a Func<ienumerable>> instead. This means that you don't need to actually define the IContainer interface. If you stay with an interface, you would need an assembly in which to define that interface and then you would be back to square one because you would need all DI Containers (Castle Windsor, StructureMap, etc.) to reference that assembly to play along. However, a Func is already in the BCL, so that's much more lightweight.

I'm not quite clear on why you need IQueryable. I would think IEnumerable should be more than enough...

So this could potentially address the Resolve part of a common container API. However, what we solve here is Service Location, and that's actually a pattern we have much better ways to avoid: use of proper DI.

A common registration API is much more difficult, but I can only applaud this piece of creative thinking! Good work!</ienumerable>

José F. Romaniello dijo...

<span></span>
<span>
<p><span>The idea about this class is that IoC containers provide their own implementation, the IContainer is for my "example", the common denominator here for me is IQueryable. I don't want people implementing IContainer for castle or so.</span>
</p><p> 
</p><p>I use Iqueryable over IEnumerable, because with IEnumerable i can't make use of the "OfType<>" method without resolving all services.
</p><p> 
</p><p>Sometimes in one place I need to get an instance of ,... let said the "main presenter", for the first time. And sometimes I need an abstract factory, to be injected in my presenters for short-lived objects. The idea is quite simply, IQueryable<object> is like a servicelocator, IQueryable<isomething> is a factory of ISomethings (comes from sl.OfType<isomething>. 
<p> 
</p><p>I agree with you about a common registration api, it will be really hard and probably will not have any sense.
</p><p> 
</p><p>Thank you very much for your comments :)
</p></isomething></isomething></object></p></span>

Publicar un comentario