The Siege Project: Siege.ServiceLocation Part 1 – Introduction and General Use

The Siege Project

Siege.ServiceLocation

In my last post, I introduced the Siege project. While that post dealt with the goals, values and philosophies of the project, this post serves as a hands-on look at the first component to be released under the Siege umbrella. This component serves to a lesser extent as a general abstraction for service locators and dependency injection containers, providing a simple syntax for component registration. To a greater extent, this library introduces a broader type of contextual registration and allows consumers to extend the registration syntax and resolution mechanisms to account for their own custom use cases.

What is it not? 

I think it’s important to start off with this, because I want to set expectations clearly. This is not another IoC framework. When building this my goal was not to build yet-another-IoC from the ground up. There are plenty of projects out there that do this very well already: Castle Windsor, StructureMap, and Ninject, just to name a few. These are all good frameworks that perform their function well, and the community honestly is not in need of another one of these. My intent with this project is to be able to leverage these existing frameworks and gain the benefit of their functionality — to stand on the shoulders of giants.

So what is it? 

The Siege.ServiceLocation component is an abstraction mechanism coupled with a way to extend the existing frameworks with additional functionality. First, this component abstracts a developer away from the need to learn another subset of frameworks, with their own styles and approaches to registration and resolution of types. Secondly, Siege.ServiceLocation introduces contextual resolution to selectively resolve types based on run-time state of an application or user session. Finally, it allows developers to extend its behavior with their own custom registration and resolution behaviors.

We use this at our company to write loosely coupled frameworks that can be integrated with our various projects. Where I work, we use more than one IoC, so rather than having to rewrite a lot of registration and resolution code, we simply use a different adapter when the container is constructed.

Let’s look at some code.

 

SiegeContainer

At the heart of Siege.ServiceLocation is the SiegeContainer class. This serves as a wrapper class for the various adapters for the different underlying IoC providers. Setting it up to use one of the various providers is easy. This is the constructor for the SiegeContainer:

        public SiegeContainer(IServiceLocatorAdapter serviceLocator)
        {
        } 

The IServiceLocatorAdapter interface provides the mechanism by which we integrate with existing IoC frameworks. For example, to use Ninject as a provider for SiegeContainer, simply instantiate like this:

        var locator = new SiegeContainer(new NinjectAdapter());

If you need to use a preconfigured kernel, this is also easy to do:

        var kernel = new StandardKernel();
        var locator = new SiegeContainer(new NinjectAdapter(kernel));

From here there are two methods that you can use to get started:

        IServiceLocator Register<TService>(IUseCase<TService> useCase);
        TService GetInstance<TService>();

Included in this assembly is a syntax to help build instances of IUseCase for the consumer. This syntax aligns with my goal of simplicity and covering the 80% of use cases that I mentioned in my previous post. I personally do not like verbose registration syntaxes, xml-based registration, or overly-fluent registrations. While I understand the goals and reasons for them, and I can see their use, I do not personally feel like the value added overcomes the inherent cost of learning and understanding the API. So, for my syntax, I followed the simple BDD formula. Given, When and Then.

 

Given<T>

Siege.ServiceLocation includes a helper class that is used for the purpose of building instances of IUseCase for the registration of types. Out of the box, this syntax supports both basic registration and contextual registration. While I will devote another post to the contextual resolution of items, I will cover briefly the method by which you contextually register items and what the purpose of contextual resolution is.

Basic (Default) Registration

Basic registration links types to implentations. This can happen between interfaces and classes, classes and subclasses, and a class and itself. Currently Siege has no mechanism to auto-register types, and a type must be either registered through Siege or registered through the underlying provider in order to be resolved. It looks like this:

        Given<Interface>.Then<Type>();

and registration itself looks like this:

        locator.Register(Given<Interface>.Then<Type>());

Under the covers, the container coordinates with the adapter to fulfill registration of the type. It also tracks the type internally for its own orchestration of the resolution of deep object graphs.

Contextual Registration

Contextual registration links types to implementations which are later resolved only if the specified criteria are met. The criteria for these conditional resolutions is completely arbitrary and defined by the consumer. As a developer, you can specify certain types to be returned under as simple or complex criteria as you want. Simply add context as you go to the locator and it will use that context to determine which rules have been met during resolution. SiegeContainer analyzes resolution of every dependency in an object graph no matter how deep and resolves types depending on whether or a condition has been successfully met or a default case needs to be triggered.

The syntax for the helper, Given<T>, looks like this:

        locator.Register(Given<Interface>
                     
.When<ArbitraryContextItem>(contextItem == someValue);
                      .Then<Type>());

To give a purely contrived, but less abstract example, imagine you have an ordering system that integrates with a third party to fulfill requests online in real time. Sometimes this vendor is down, and you want to save the orders for later processing when this occurs.

        locator
                .
Register(
Given<IOrderFulfillmentService>.Then<RealtimeOrderFulfillmentService>();
     
           .Register(Given<IOrderFulfillmentService>
                                     
.When<VendorStatus>(status == VendorStatus.Offline);
                                      .Then<DeferredOrderFulfillmentService>());

In this case, the application would contextually know to process orders in real-time or to defer them to a later time based on the status of a 3rd party integration component. This is all handled by the container, so you don’t have to build additional logic into your system for determining how to handle different states of the system; This scenario assumes that there is some component performing a health check and if it fails, it notifies the locator that the state of the application is changed so it can respond accordingly. There are many different use cases for such contextually-based resolution; because the conditions are completely arbitrary, it is up to the imagination of the developer on how to use (or abuse) this feature. I will go over, in greater depth, contextual registration and resolution in the following blog post.

Extending the container

Siege.ServiceLocation is built with extensibility in mind. To that end, it has the capacity to incorporate and use custom implementations of IUseCase. While this will be covered in a seperate post, it allows a consumer to extend the container to understand how to register custom scenarios and how to resolve them.  This is meant to allow myself and other members of the community to easily provide extensions to this framework without having to constantly update the source code to deliver new functionality to users.

Looking ahead

In upcoming posts, I will delve deeper into how contextual registration/resolution works, how to extend the container to incorporate custom use cases and how to integrate the framework into ASP.NET MVC. My goal is to explain how these aspects of the system work in a clear and concise manner that adheres to the main pillars of the Siege design philosophy: Keep it easy to understand and consume. Please check this framework out and leave any feedback you have in the comments section.

Happy coding!

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

This entry was posted in IoC, Siege. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Paco

    Do you know about Common Service Locator? And if you do, why don’t you use it?

  • http://www.lostechies.com/members/mbratton/default.aspx mbratton

    I do; And this project implements that interface. The intent of the project is beyond the boundary of just abstraction, though.

  • Tariq

    This is great!. Something i’ve been thinking about for a while. It would be nice to just write the registrations once and then switch implementations of IoC. It was a pain swtiching 80 types from unity to AutoFac. Is this going to be opensource where people can contribute?

  • http://www.lostechies.com/members/mbratton/default.aspx mbratton

    @Tariq: Yes, this will be open source, and people will be able to contribute. I am still thinking through how to set it up, but I don’t think I want to make Siege.ServiceLocation a growing set of features, but might rather do a SiegeContrib for things like new IUseCase implementations, etc.

    For something like an AutoFacAdapter, I think a seperate assembly that implements IServiceLocatorAdapter would be completely inline with what I’ve done with the other adapters (Ninject, StructureMap and Windsor).

    People will definitely be encouraged to contribute.