Background
I've been working on an extensible system monitoring application called Odin. It has a Rich Client UI written with Adobe Flex 2.0.1 and a bunch of ASP.Net Web Services on the back-end. One of the challenges has been to unify the conventions used across the front-end and the back-end.
I want it to feel like I'm writing one application split across two tiers rather than two completely different applications that happen to share a web service protocol.
Plugin Architecture
Since the monitoring application is extensible both on the client and on the server, it needs some kind of plugin model that spans both worlds. Plugins are really just a collection of late-bound components. This is a perfect situation to leverage an Inversion of Control container to do all of the heavy lifting.
IoC and DI on the Server
On the server side, I use the amazing Castle Windsor container. Initialization proceeds in two phases.
- First, I create a
WindsorContainer
using configuration information sourced from the applications's Web.config or *.exe.config file. This configuration registers all of the foundational components I need to assemble the application such as a logger and plugin resolver. - Second, the registered
IPluginResolver
component goes out to discover all plugins. Currently theDefaultPlugingResolver
just looks for *.plugin files. These files are basically just Windsor configuration files with a few extra extensions. The plugin resolver then constructs anIPluginDescriptor
object for each plugin it found. - Third, I create another
WindsorContainer
as a child of the first one. The nested container will receive all of the components that are registered by the plugins as they are loaded. This is the container that will be used to resolve components for the rest of the application's lifetime. - Fourth, the
IPluginLoader
finally runs to load all fully-satisfied plugins that are enabled for use with the application. It ensures that the plugin's assemblies can be resolved then it imports the plugin's component configuration into Windsor. It also merges adds the plugin's additional configuration sections to the masterIConfigurationManager
.
Using Castle Windsor for inversion of control and dependency injection I get to write very natural code like the following. Dependency injection will take care of providing the constructor parameters and filling in optional configuration.
/// <summary> /// The default issue tracking integration consults a list of <see cref="IIssueTracker" /> /// services to obtain information about an issue. Caches issue tracking data for a short /// period to improve performance. /// </summary> public class DefaultIssueTrackingIntegration : IIssueTrackingIntegration { private ICache cache; private ILogger logger; // --- snip --- // public DefaultIssueTrackingIntegration(ICache cache, ILogger logger) { this.cache = cache; this.logger = logger; } // --- snip --- // }
The nifty thing is that a plugin can install a new issue tracking extension just by registering a component in its *.plugin file. Like this:
<component id="Core.IssueTracking.Jira" service="Odin.Core.Integration.IssueTracking.IIssueTracker, Odin.Core" type="Odin.Plugins.Jira.Core.JiraIssueTracker, Odin.Plugins.Jira"> <parameters> <WebAppRootUrl>#{JIRA_WebAppRootUrl}</WebAppRootUrl> <UserName>#{JIRA_UserName}</UserName> <Password>#{JIRA_Password}</Password> </parameters> </component>
IoC and DI on the Client
This is where is gets interesting. The client UI is written with Adobe Flex pluggable too! I could invent a whole new way to do this or I could just use an Inversion of Control container like I do on the server. The latter has a nice sound to it. After all, Inversion of Control is useful for way more than just assembling plugins: it's the cornerstone of my system architecture.
As of the time of this writing, I am not aware of any published IoC containers for ActionScript 3. I have found posts to the effect so others have certainly been doing this.
When I started working on Odin's client-side, I wrote a miniature IoC container. I could register components by specifying the service type and the component type and then later I could resolve singleton instances of those components. It was very very simple.
Components.registerComponent(IPluginLoader, DefaultPluginLoader); // --- snip --- // var pluginLoader:IPluginLoader = IPluginLoader(Components.resolve(IPluginLoader)); var progressMonitor:IProgressMonitor = new ProgressMonitorDialog().progressMonitor; pluginLoader.loadPlugins(progressMonitor);
This worked great! But soon I found I needed to add component keys so I could distinguish between two components that implement the same service. Then I needed to add support for components with a Transient lifestyle (instead of always Singleton) and for External Instances. But there was one thing missing: no Dependency Injection! That's right, I had a static reference to the IoC container and I just queried it every time I needed a component that it managed.
So last night I added support for Constructor Dependency Injection. As a bonus, I refactored the whole thing and added it to my Castle FlexBridge project. You'll be hearing more about that one soon. The short story is that Castle FlexBridge is an Open Source AMF3 remoting gateway for .Net. It also includes client-side components to simplify application development. Now it includes my Castle-inspired Inversion of Control container kernel.
Now I can write things like this on the client side too! The FlexBridge IoC kernel will take care of satisfying all of the constructor parameter dependencies.
/** * The default plugin loader gathers plugin SWF Urls from the * pluginUrls argument of FlashVars as a semi-colon delimited string or Urls. */ public class DefaultPluginLoader implements IPluginLoader { private var _configurationManager:IConfigurationManager; private var _kernel:IKernel; // --- snip --- // public function DefaultPluginLoader(kernel:IKernel, configurationManager:IConfigurationManager) { _kernel = kernel; _configurationManager = configurationManager; } // --- snip --- // }Huzzah!
3 comments:
Could you please show me the code to set the values of those parameters?
WindsorContainer.Kernel.AddComponentInstance()?
On the client it looks just like the snippet I showed with Component.registerComponent() specifying the service and component types.
(Actually, come to think of it, I think I refactored this API some time ago to include a component key. But it's essentially the same.)
On the server we're using Windsor configuration XML files. They're a little special because they've been embedded into "plugin" XML files which have a bit of extra metadata and are resolved dynamically at runtime. But they contain all the usual sections like <components>>and <facilities>.
Does that help?
i'm very intersted in your plug-in architecture with ioc,especialy about how to implement it in Windsor,so could you show some detail
Post a Comment