Yet Another Service Locator

I have been writing a bunch of small samples apps, trying to improve my understanding of volatility-based decomposition solutions.  As part of this effort, I needed a proxy to link calls to services without the client directly calling the services.  This is a fundamental detail in any microservice implementation and there are libraries to enable this, if I where building Service Fabric applications.  But that isn’t what I am trying to do.  I need a tool that is light-weight, super-easy to use, and something that at least vaguely follows the SF communication patterns.  

I looked around, did some reading, and cobbled together what you will find below.   My primary source is Stefano Ricciardi.  90% of what I have comes from him in fact or in spirit.

I hope this helps.  I added a few notes at the bottom to explain what is happening and why.

Enjoy.


 

My service locator has two methods:

public interface IServiceLocator
{
  T GetService<T>();
  void Register<T>(Type type);
}

Registration is fairly simple.

public class Hosting
{
  public static void Register()
  {
    ServiceLocator.Instance.Register<IYourServiceInterface>(typeof(YourService));
  }

  static void Main(string[] args)
  {
    Register();
    Thread.Sleep(TimeSpan.MaxValue);
  }
}

Consumption becomes this.

var yourServiceProxy = ServiceLocator.Instance.GetService<IYourServiceInterface>();

You could also inject the ServiceLocator instance in the constructor aka Dependency Injection and use this syntax.

var yourServiceProxy = serviceLocator.GetService<IYourService>();

Below is the code for the service locator.

public class ServiceLocator : IServiceLocator
{

  private readonly IDictionary<Type, Type> servicesType;
  private readonly IDictionary<Type, object> instantiatedServices;

  private static readonly object syncRoot = new object();

  private static IServiceLocator instance;
  public static IServiceLocator Instance
  {
    get
    {
      if (instance == null)
      {
        lock (syncRoot)
        {
          if (instance == null)
          {
            instance = new ServiceLocator();
          }
        }
      }
      return instance;
    }
  }
  
  private ServiceLocator()
  {
    servicesType = new ConcurrentDictionary<Type, Type>();
    instantiatedServices = new ConcurrentDictionary<Type, object>();
  }

  public T GetService<T>()
  {
    if (instantiatedServices.ContainsKey(typeof(T)))
    {
      return (T)instantiatedServices[typeof(T)];
    }
    try
    {
      ConstructorInfo constructor = servicesType[typeof(T)].GetConstructor(new Type[0]);
      Debug.Assert(constructor != null, $"Cannot find a suitable constructor for {typeof(T)}");
      T service = (T)constructor.Invoke(null);
      instantiatedServices.Add(typeof(T), service);
      return service;
    }
    catch (KeyNotFoundException)
    {
      throw new ApplicationException("The requested services is not registered");
    }
  }

  public void Register<T>(Type type)
  {
    Debug.Assert(typeof(T).IsInterface, "Type is not an interface.");
    Debug.Assert(type.GetInterfaces().Contains(typeof (IService)), "Instance is not does not support IService");
    servicesType.Add(typeof(T), type);
  }

}

You will need to create an IService class.  This is to conform with Service Fabric service definitions.  If you don’t want to use it, delete the interface and the code in the Register().

public interface IService { }

Notes:

This service locator uses a lazy loading approach.  The services are registered by type to link the interface to the implementation.  When a client requests an instance of the interface, the locator checks to see if an instance exists.  If yes, then it returns the instance.  If not, it creates an instance from the registered type.  That instance is then cached and returned to the caller.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.