So, recently I built a lightweight framework for running load tests. Similar to your scenario,
// each major component implements this interface, exposes
// explicit start, stop, pause, resume functionality
public interface IService { }
// first major component
public class ServiceA : IService { }
// second major component, may be run completely independently
// of ServiceA
public class ServiceB : IService { }
// if we want to run both ServiceA and ServiceB, we either
// have our application pull back two IService implementations
// by "id" [which kinda foils Inversion of Control], or we
// create *another* service that simply acts against multiple
// instances of IService. Application implements IService
// itself, and in turn invokes start, stop, pause, and resume
// on all child services
public class Application : IService
{
// we leave sub-service specification to container
// configuration
public Application (IEnumerable<IService> services) { }
}
// windows service execution context, this class basically
// instantiates a container [Castle Windsor], and pulls back
// an implementation of an IService, could be ServiceA,
// ServiceB, or both a la Application
public class WindowsService : ServiceBase { }
So the trick is in two parts
- the "default"
IService
component must be Application
, and
Application
must be given all instances of IService
we need to run
I pretty much drive all my Inversion of Control [IoC] projects by config, but I presume there is an equivalent means of doing so via Castle Windsor's fluent api,
<castle>
<components>
<!-- 1. first trick is easy, make sure Application is declared first -->
<component
id="Application"
service="SomeApp.Services.IService, SomeApp.Interfaces"
type="SomeApp.Services.Application, SomeApp">
<!-- 2. second trick is also easy ... -->
<parameters>
<!-- just specify the ctor parameter ... -->
<services>
<!-- followed by an array of named components! -->
<array>
<item>${ServiceA}</item>
<item>${ServiceB}</item>
</array>
</services>
</parameters>
</component>
<!--
these definitions still register, but are not considered when
a "blind" request is made for IService. Castle Windsor implements
First Come First Serve [FCFS] ordering on registered components
-->
<component
id="ServiceA"
service="SomeApp.Services.IService, SomeApp.Interfaces"
type="SomeApp.Services.ServiceA, SomeApp">
</component>
<component
id="ServiceB"
service="SomeApp.Services.IService, SomeApp.Interfaces"
type="SomeApp.Services.ServiceB, SomeApp">
</component>
</components>
</castle>
For s***s and giggles, looked up the fluent api, equivalent.
// similar to app config approach
_container.
Register (
// 1. register Application first,
Component.
For<IService> ().
ImplementedBy<Application> ().
// 2. now this was interesting, to obtain components
// registered later on, use late-binding lambda expressions!
DynamicParameters
(
// k is a reference to kernel, d is parameters dictionary
(k, d) =>
{
// "services" is name of ctor parameter, and we
// assign this parameter an array of service
// references that are resolved on demand
d["services"] = new IService[]
{
k.Resolve<IService> ("ServiceA"),
k.Resolve<IService> ("ServiceB"),
};
}
),
// register other IService implementations :)
Component.For<IService> ().ImplementedBy<ServiceA> ().Named ("ServcieA"),
Component.For<IService> ().ImplementedBy<ServiceB> ().Named ("ServcieB"));
le fin.