The Siege Project: Siege.ServiceLocation, Part 2 – Contextual Registration and Resolution

The Siege Project

Some house cleaning items before we get started on this post.

  • You can download the source code to the currently published components here: http://github.com/MarcusTheBold/Siege
  • You can download the binaries to the currently published components here: http://github.com/MarcusTheBold/Siege/downloads
  • I’m working on picking out an OSS license. I hope to have one up soon.
  • Some of the feedback I’ve received is to give examples of how to use this. I will put up a quick-start example in the near future and write a ‘getting started’ blog post as well.

 

What is Contextual Registration?

Contextual Registration, simply put, is telling the SiegeContainer that there are certain criteria that have to be met in order for a certain type to resolved. These criteria are completely arbitrary and up to the user. Perhaps you only want to resolve a particular type if the user is a super user? Maybe you integrate with multiple 3rd party vendors for a particular service and want to resolve one based on data associated with the logged in user. Perhaps you want your system to serve up different types for different states of the app, to mock 3rd party integration points or to respond to a resource being unavailable. All of these scenarios are expressable with contextual registration.

Let’s look at the example I hinted at in my last post.

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

So what does this mean? Basically, there are a handful of implementations of IUseCase packaged with Siege.ServiceLocation. The two main ones are default use cases and conditional uses cases. When you do something like, Given<Interface>.Then<ConcreteType>(), you create a default use case for the locator to use when none of the conditional use cases are satisfied.

When you use the above registration, the locator registers the type with the underlying adapter and tracks the conditions around the resolution. The literal translation of this registration is “When I ask for an instance of IOrderFulfillmentService, check to see if the vendor is offline. If it is, give me an instance of DeferredOrderFulfillmentService. If not, return me the default (if one is defined).” Using this, you can set up behaviors in your system that are tracked by the locator and resolved in your system as context begins to apply.

Where I work, we use contextual registration as a way to augment our default registrations in one of our systems, which come in the form of a pre-wired container. We change the look and feel of the website based on attributes associated with the user that is logged in. We accomplish this in large part by using contextual registration. This has been useful for us in helping to avoid having to write additional conditional logic to determine how to deliver the user experience based on data points associated with the person that is logged in.

 

Tracking Context for the SiegeContainer

Contextual Registration is all good and well but at the end of the day your application needs to be able to determine what types to resolve during its execution. To do this, The SiegeContainer class uses an interface called IContextStore. This interface defines how the SiegeContainer interacts with whatever underlying mechanism you decide to use to track context in your application. By default, when you instantiate a SiegeContainer, it uses an implementation called GlobalContextStore.

public SiegeContainer(IServiceLocatorAdapter serviceLocator) : this(serviceLocator, new GlobalContextStore())
{
}

public SiegeContainer(IServiceLocatorAdapter serviceLocator, IContextStore contextStore)
{
}

This context store is a very simple implementation that tracks context for the container irrespective of user session. By providing different implementations of IContextStore, you can change the way that SiegeContainer manages context for your application. This is designed to create extensibility for Siege.ServiceLocation; if there is behavior that you need that is not covered by an existing implementation, creating your own should be very easy. The definition of IContextStore looks like this:

public interface IContextStore
{
    void Add(object contextItem);
    List<object> Items { get; }
}

At our company, we use a different implementation – HttpSessionStore – to manage context on a per-user session basis. As users log in, the relevant information about them is added to the context store for use in later resolutions. I will include this context store as part of the http integration project accompanying Part 4, when it is published.

 

How do I add context to a context store?

This really depends on how you set up your implementation of IContextStore, or which implementation of IContextStore you are using (if you haven’t provided your own). There are a few ways of going about this. HttpSessionStore pulls items from the Session; Adding an item to the Session effectively adds it to the context. The SiegeContainer itself has a convenience method for this:

public void AddContext(object contextItem)
{
}

Alternatively, you could add things directly through your context store instance itself. Simply register the implementation of IContextStore you desire with the container and then inject it directly into a class that requires context to be updated.

locator.Register(Given<IContextStore>.Then<HttpSessionStore>());

or

locator.Register(Given<IContextStore>.Then(locator.ContextStore));

Using this approach, you could monitor the status of your vendor integration in the following manner:

public class VendorMonitor

{

    private IContextStore contextStore;

 

    public VendorMonitor(IContextStore contextStore)

    {

        this.contextStore = contextStore;

    }

 

    public void Monitor()

    {

        if(IsVendorDown()) contextStore.Add(VendorStatus.Offline);

    }
}

When the context store is updated, SiegeContainer will start serving different interfaces based on the rules specified for the various use cases registered and the context that exists in the container.

 

Contextual Resolution

So how do we go about resolving types that have been contextually registered? This turns out to be very easy. No different methods are needed to be called to resolve types contextually; Simply specify a contextual condition through registration using the Given-When-Then syntax and call GetInstance<T>() on the SiegeContainer. If the condition is satisfied, the container will return the type specified by your rule. If not, it will return the default (if specified). If multiple conditions apply, it will select the first applicable one. This is currently NOT the case for functions like GetAllInstances<T>. I am considering adding a function called GetAllContextualInstances<T> to cover this scenario.

The part (to me) that is cool is this – If you had, say, a controller class that was injected with IOrderFulfillmentService, and you resolved that controller, when the IOrderFulfillmentService is identified as a dependency, it is resolved contextually to the appropriate type. This means that contextual resolution applies not only at the root level of an object graph, but at every level a dependency is resolved and fulfilled by the container. If your object structure is 10 levels deep, and each level has constructor injected depenendencies, any of those dependencies that are contextually registered will be compared against the tracked context and resolved either to a default use case or a conditional use case. Siege works with the provided adapters to ensure that every dependency, as it is resolved, is fulfilled contextually if applicable; not only the type you are requesting, but also the types it relies on as dependencies, and the types that THOSE types rely on as dependencies.

Looking Ahead

In my next post, I will be covering how Siege.ServiceLocation has native support for extending itself to support custom use case implementations. This post will get a little deeper into the guts of how the project works and how it interacts with adapters. I hope these posts have been helpful thus far in communicating the intent of the library, how it is used, what you as a developer can do with it and how it adheres to my philosophy of simplicity, consumability and extensibility.

Until next time, 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.
  • http://sharpoverride.blogspot.com Mihai

    Looks pretty interesting. One argument I’d like to leave is that you should implement an adapter for UnityContainer.

    The reason for a UnitContainerAdapter is simple, most firms have the firm belief that if it’s from Microsoft than it is better than anything else. So as a developer you won’t be able to propose the use of X or Y unless it’s from Microsoft.

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

    @Mihai: There were two reasons I didn’t implement an AutofacAdapter or a UnityAdapter. First, I’ve never used them, so I’d have to learn them (not a big deal). Secondly, I felt that if this project had enough support from the community, and people wanted a Unity or Autofac adapter, one would be put together. Maybe I should put together a post on how to make an adapter?

  • azaslow

    I am working on something similar to but not as elegant as what you’re describing. I have two questions about what you’re doing:

    1) Is there a way to use different context store objects with a single container or is there a way to share registrations between containers?

    I have found that it is very useful to be able to perform all of the registrations once and then use those common registrations under different contexts at the same time (in my case I create multiple containers each with its own context and each using a single shared registration).

    For instance on a web server which is receiving multiple requests from different users you want to resolve using different contexts for each request but you don’t want to have to re-register everything for each request.

    2) In the “When” part of the registration can only objects which are part of the context store be resolved or are the resolved objects registered with the container itself?

    For instance, in your example where you are getting VendorStatus, must VendorStatus be an object in the context store or can it be any object which is retrieved from the container? I think that being able to make a decision based on an object in the container is more robust than limiting decisions to items in the context store alone.

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

    @azaslow – #1 is something I’ve been thinking about for a while but wasn’t sure I wanted to complicate things. I think my instinct right now is to leave it down to specific implementations of an IContextStore to determine how to manage it. But I’m not sure that’s enough.

    If I were to do a Per-Request, Per-Session, Per-Instance model, with three context stores (for example), would I predetermined a precedence for how I check? Would it be RequestContext before SessionContext before ApplicationContext? Would that force a user into a specific model (something I don’t want to do).

    Or could I allow users to add more than one context store? If I do that, do I need to allow users to specify a precedence between them? How do I make sure the correct store is looked at first for resolutions if multiple contexts are able to satisfy a use case, but they ultimately resolve to different types?

    Those are the questions I’ve been debating. So in the end I’ve settled that until a better way is pointed out, I’ll put the burden on the consumer to define an implementation of IContextStore that satisfies their needs. Maybe someone will create a ComplexContextStore that has an internal list of IContextStores for different scopes, and can it determine how to return its items in the correct fashion to the SiegeContainer. I dunno. I’m open to ideas.

    For #2… I hadn’t really considered that idea. Right now I suspect an object has to exist in context for it to be evaluated. I agree that it is more robust, but what I’m curious to see is a scenario where you’d do this. That will help me understand better what the goal is behind it. :)

  • azaslow

    For #1 I don’t think that I expressed my question well. What you’re saying is interesting, but not what I was thinking.

    What I’m thinking about is something which addresses the need that I think the httpSessionStore is trying to achive (i.e. providing different context values depending on the active user or other varying context). I assume that your httpSessionStore returns different values based on the active session. What I was asking was for was actually using different context stores of the same type with the same container (or with multiple containers which use the same registration catalog).

    The way that I implemented this is that I have a root container which has no context and where everything is registered. Later when a context is created a child container is created from the root container using the given context. This child container inherits the registrations from the root container. Once the child container is created its context cannot be changed (although I probably could allow changing the context with a little work).

    For #2 I started to write a scenario and then I realized that what I’m asking goes beyond what you’re trying to do. I think that what I’m looking for is simply to specify a factory to build the object and for the container (and context store) to be passed to the factory so that the factory can utilize the container to build the required object.

    Thanks for your response. I think that what you’re trying to do is something many people would like.

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

    Interesting!

    For #1 yes, context is tracked per user session. Turns out that what you’re asking for on #2 is possible with this framework (I’ll be covering areas like this in the next post, and I’ll post a comment afterward explaining how it applies to your scenario).