Service Locator

The core offers the namespace ServiceLocator to help you build bridge classes. The current version does not cover constructor with argument because I just implemented what I am using right now. I know that It will be helpful if I include the way to handle constructor argument, and I will do that later.

Let me explain what needs I am covering using my service locator library, so you can understand what it does.

I got a tasks from the project manager requesting a different behavior of the application in a certain time on night. So in the days coming the request changed adding to many behavior in different time on night. All the logic to be changed on night were moved to service classes with interfaces, also the new logic was added implementing the same interface of the initial classes. In that way the main application knows the interface, and I was able to switch to different classes on night. Now I can control how the application works.

With those changes applied I start working in a service locator that is going to define the service instance active, and the service locator provide it to the service bridge. The bridge get the active instance of the service from the service locator.

Bridge using the service locator
public class ServiceBridge
{
    private IService service;

    public ServiceBridge()
    {
        Locator serviceLocator = new Locator();
        this.service = (IService)serviceLocator.Service;
    }

    public Customer GetFirstCustomer()
    {
        return this.service.Get();
    }
}


if you see the code above, the locator works as a factory providing the service instance to the bridge class.

Manipulating the service locator
// Instance of the service version 1
var environment = new LocatorEnvironment(new LocatorTarget(typeof(ServiceVersion1)));
Customer cust = new ServiceBridge().GetFirstCustomer();

// Instance of the service version 2
var environment = new LocatorEnvironment(new LocatorTarget(typeof(ServiceVersion2)));
Customer cust = new ServiceBridge().GetFirstCustomer();

Service Locator Classes Information

The pattern is handled by 3 different classes:
  1. LocatorTarget
  2. Locator
  3. LocatorEnvironment

LocatorTarget
This class create the instance of the services. You can provide a Type or the assembly information as argument to the constructor.
// Type
var target = new LocatorTarget(typeof(ServiceVersion1));

// Assembly
var target = new LocatorTarget("Project.Test", "Project.Test.ServiceVersion1");

// Retrieve the object
var service = (ServiceVersion1)target.LoadObject();


Locator
This class provide a better sense to the pattern but it is really a wrapper between the application and the LocatorTarget. If you create a instance of it without argument in the constructor, the locator will request the LocatorTarget to the LocatorEnvironment otherwise you must define the LocatorTarget as argument in the constructor.

This class has 2 version: normal and generic. The normal always return an object in the property Service and the generic return what you define.

// Define the LocatorTarget
var locator = new Locator(new LocatorTarget(typeof(DBAccessLayer)));

// Retrieve the LocatorTarget from the LocatorEnvironment
// it is used in the bridge class created above
var locator = new Locator();

Real World Example

Dependency Injection is just amazing to disengage the logic dependency. With this in mind we can create a custom factory class to handle the known types to the used by our different layers.

Creating a configuration section, we can add our service information as follow
<services>
    <service name="CustomerService" assembly="Logic" class="Logic.Service.CustomerService" />
    <service name="CustomerRepository" assembly="Logic" class="Logic.Data.CustomerRepository" />
</services>


The Configuration Section for handling the xml config above.
public class ServiceConfigurationSection : ConfigurationSection
{
    [ConfigurationProperty("services", IsRequired = true),
    ConfigurationCollection(typeof(GenericElementCollection<ServiceElement>), AddItemName = "service", ClearItemsName = "clear", RemoveItemName = "remove")]
    public GenericElementCollection<ServiceElement> Databases
    {
        get { return (GenericElementCollection<ServiceElement>)this["services"]; }
        set { this["services"] = value; }
    }

    public static ServiceConfigurationSection Load()
    {
        return (ServiceConfigurationSection)System.Configuration.ConfigurationManager.GetSection("dependency");
    }
}

public class ServiceElement : ConfigurationElement
{
    [ConfigurationProperty("name", IsRequired = true)]
    public String Name
    {
        get { return (String)this["name"]; }
        set { this["name"] = value; }
    }

    [ConfigurationProperty("assembly", IsRequired = true)]
    public String Assembly
    {
        get { return (String)this["assembly"]; }
        set { this["assembly"] = value; }
    }

    [ConfigurationProperty("class", IsRequired = true)]
    public String Class
    {
        get { return (String)this["class"]; }
        set { this["class"] = value; }
    }

    public override string ToString()
    {
        return Name.ToString();
    }
}


Now we can create our DependencyFactory
public class DependencyFactory
{
    private Dictionary<string, ServiceElement> services;

    public DependencyFactory()
    {
        this.services = new Dictionary<string, ServiceElement>();
    }

    public T CreateService<T>(string name)
    {
        var element = GetServiceElement(name);
        var target = new LocatorTarget(element.Assembly, element.Class);
        var locator = new Locator(target);

        return (T)locator.Service;
    }

    private ServiceElement GetServiceElement(string name)
    {
        if (!services.ContainsKey(name))
        {
            var element = ServiceConfigurationSection.Load().Services[name];
            services.Add(name, element);
        }

        return services[name];
    }
}


Now that we have the configuration section and the dependency factory created, we can implement the dependency injection.
var dependency = new DependencyFactory();

// Create the customer service instance
ICustomerService customerService = dependency.CreateService<ICustomerService>("CustomerService");

// Create the customer repository instance
ICustomerRepository customerRepository = dependency.CreateService<ICustomerRepository>("CustomerRepository");


As usual, the customer service has a instance of the customer repository as follow:
public class CustomerService : ICustomerService
{
    public int GetCustomerID(string username) 
    { 
        return new CustomerRepository().GetCustomerID(username); 
    }
}

Now we can replace the hard-coded initialization implementing the dependency factory as follow:
public class CustomerService : ICustomerService
{
    public int GetCustomerID(string username) 
    {
        var dependency = new DependencyFactory();
        ICustomerRepository repository = dependency.CreateService<ICustomerRepository>("CustomerRepository");

        return repository.GetCustomerID(username); 
    }
}


Just take note that it is just an example, and don't take the DependencyFactory class as last version, I just created in my test project.

Remember to provide your feedback and rates because that help me to improve the library.

Last edited Dec 30, 2012 at 6:41 PM by dwaks, version 8

Comments

No comments yet.