Mike Bynum
Mike Bynum

Reputation: 773

Plugin Achitecture Extensibilty

I am working with MEF to get a plug in architecture going. I want to design in some extensibility. I want to extend initialization.

What I have is a "driver" which repetively collects data from some source. These are my plugins. Each of these plug ins needs to be initialized. Right now I have an interface that these plugins are required to implement.

interface IDriverLiveCollection
{
    ILiveCollection GetCollection(ILog logger, IDriverConfig config);
}

This Interface is basically creating an instance of a ILiveCollection from the plugin. for better understanding ILiveCollection looks like this.

interface ILiveCollection
{
    void GetData(Parameter param, DataWriter writer);

    void ShutDown();
}

And also the initialization loop:

foreach(IDriverConfig config in DriverConfigs)
{
    //Use MEF to load correct driver
    var collector = this.DriverLoader(config.DriverId).GetCollection(new Logger, config);

    // someTimer is an IObservable<Parameter> that triggers to tell when to collect data.
    someTimer.Subscribe((param)=> collector.GetData(param, new DataWriter(param)));
}

The problem is that some driver may require more information than their configuration in order to initialize. For example, some driver would like a set of parameters given to them during initialization.

I could easily extend the interface to now look like:

interface IDriverLiveCollection
{
    ILiveCollection GetCollection(ILog logger, IDriverConfig config, IEnumerable<Parameter> params);
}

The down side to this approach is that the public interface has changed and now i need to recompile EVERY driver even though none of the have needed this parameter list in order to function before. I intend on having ALOT of drivers and I will also not have any control over who writes drivers.

I thought up another solution. I could create interfaces and inside my loop between when I call Get Collection and before i subscribe to the timer, i could check if the ILiveCollection object also extends one of these interfaces:

interface InitWithParameters
{
    void InitParams(IEnumerable<Parameter> params);
}

in my loop:

foreach(IDriverConfig config in DriverConfigs)
{
    //Use MEF to load correct driver
    var collector = this.DriverLoader(config.DriverId).GetCollection(new Logger, config);

    // Check to see if this thing cares about params.
    if(collector is InitWithParameters)
    {
        ((InitWithparameters)collector).InitParams(ListOfParams);
    }

    // Continue with newly added interfaces.

    // someTimer is an IObservable<Parameter> that triggers to tell when to collect data.
    someTimer.Subscribe((param)=> collector.GetData(param, new DataWriter(param)));
}

The difference here is that I will not need to recompile every driver in order to get this to work. Old drivers will simply not be of type InitWithParameters and will not be called that way while new drivers will be able to take advantage of the new interface. If an old driver wants to take advantage, then it can simply implement that interface and be recompiled. The bottom line: I will not need to recompile drivers UNLESS they want the functionality.

The downsides that I have recognized are: I will obviously need to recompile which ever program is in this loop. There becomes a versioning issue when a new driver is used with an old version of the program with the loop which could result in some issues. and finally I have to hold a huge list of every possible type in the program with the loop as these things grow.

Is there a better way to do this?

Edit Additional Info:

I am attempting to use MEF on the IDriverLiveCollection, not on ILiveCollection since IDriverLiveCollection allows me to construct a specific ILiveCollection with custom initialization parameters.

It is possible to have 2 ILiveCollections of the same type (2 FooLiveCollections) each with a different ILog and IDriverConfig and potentially IEnumerable. I would like to be able to specify these during the "initialization loop" and not at the time of composition of the plugins.

Upvotes: 4

Views: 163

Answers (1)

Reed Copsey
Reed Copsey

Reputation: 564891

If you just use [ImportMany] directly on your ILiveCollection interface, you could handle this entire infrastructure via the [ImportingConstructor] attribute.

This allows your plugins to specify exactly what they need to be able to be constructed, without having to provide and construct the types later.

Effectively, your host application would need nothing but:

// This gets all plugins
[ImportMany]
IEnumerable<ILiveCollection> LiveCollections { get; set; }

Each plugin would then have their type that exported this, ie:

[Export(typeof(ILiveCollection))]
public class FooLiveCollection : ILiveCollection
{
    [ImportingConstructor]
    public FooLiveCollection(ILog logger, IDriverConfig config)
    {
       // ...

Or, alternatively, a plugin could leave off one constructor argument, or add laters (without effecting previous plugins), ie:

    [ImportingConstructor]
    public BarLiveCollection(ILog logger, IDriverConfig config, IBarSpecificValue barParam)
    {
       // ...

Upvotes: 2

Related Questions