The Siege Project
- Introduction
- Siege.ServiceLocation Part 1 – Introduction and General Use
- Siege.ServiceLocation Part 2 – Contextual Registration and Resolution
- Siege.ServiceLocation Part 3 – Extending the container with custom use cases
- Siege.ServiceLocation Part 4 – Integrating Siege.ServiceLocation with ASP.NET MVC
- Siege.ServiceLocation Part 5 – A guide to getting started with Siege.ServiceLocation
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!
Post Footer automatically generated by Add Post Footer Plugin for wordpress.
