in

ASP.NET Weblogs

J e r o e n ' s   w e b l o g

Reflecting on plug-in systems

After creating a plug-in architecture in .NET based on interfaces, attributes and reflection a while ago, I'm now doing something that looks like it will require a similar approach. This post will discuss some of the things I've learned from the first time around and what I'll do to make my next plug-in framework as robust as possible.

If you're familiar with design patterns then think of a plug-in architecture as an application of the Strategy pattern in the most flexible way, where the actual strategies are implemented in seperate packages from the context, which can then discover and use them at runtime. For instance, when creating a wordprocessor, you might want to add additional formats for the application to load and/or save over time. The typical way to implement this is by defining an interface for an implementation of an algorithm to adhere to. Next you just look for classes that implement this interface - or have any class handed to you and accept it as long as it implements the interface - and use its methods to complete the task. To truly make the system pluggable, you'll want to eliminate recompiles just to add new algorithms by allowing the system to dynamically load classes supporting your plug-in interface at runtime. Once you do that, you've got a plug-in system.

Identifying plug-ins

First, when you get the path to an assembly handed to you (for instance, through an InstallPlugin-method), how do you determine what classes inside it are plug-ins? You need to define an interface that you use to communicate with the plug-in, so all you need to do is look for classes that implement this interface. This is not the ideal method however. Ofcourse plug-in classes must implement an interface, but using that as the only criterium to determine whether it's a plug-in is a bad idea. In the past I've created plug-ins that were actually composites of other classes that all behaved like plug-ins, but just the main composite was actually supposed to be used by the application. Looking for classes implementing the interface would have forced me to define the same interface with a different name just to hide those other classes. Not to mention that having two seperate interfaces that should be identical is just a bunch of strange maintenance bugs waiting to happen. So besides implementing the interface, look for an attribute first. Just declare a PluginAttribute and require plug-in classes to be decorated with it.

There's another advantage to using attributes however: instantiation costs. These attributes can contain a lot of information, such as display name, description, notes, all kinds of filter data to determine when to use them, etc. This means that if you have a list of available plug-ins, all you have to do upon discovery is remember where they are, you don't have to actually instantiate them to retrieve information to show the user of your application. Remember however that you can't just unload an assembly from memory unless you unload the entire application domain. This is actually not so bad because you'll want to load external code into another application domain anyway, to prevent it from taking down your entire application if it messes up. Each plug-in can get its own application domain.

Discovery, instantiation and initialization

On to the instantiation. Where you keep the actual references depends on the application, I usually have a list of objects that contain location information and metadata. When you actually do the different types of work associated with instantiation however is a big deal: make sure you seperate discovery from instantiation and seperate that from initialization. Why? Suppose that during startup, you load all available plug-ins (either by retrieving a bunch of names from a config-file or looking for assemblies in a specific location), instantiate all those classes and call all of their initialization methods (if you do this the constructors may as well be used for all initialization work). Now suppose in the user interface, some functionality normally available and provided by a plug-in is missing. The user will wonder what exactly is the matter. Is the file missing? Can't the application find it? Or maybe this version of my application doesn't work with the plug-in? Perhaps there is some .NET-version mismatch? Or is some resource required for initialization missing? Because everything is done in a row, it's impossible for the end-user to determine what went wrong. Detailed error messages might help with this, but most users I know just call me up to say that "there was an error" instead of actually reading what the error message says, so it's much easier to make sure these types of things are done at different times.

Do the discovery during start-up. That way you know that if a plug-in shows up in the list of available ones that the pathname is good and the assembly it's in could be loaded. When to do instantiation and whether to seperate it from initialization at all depends on the nature of the application and its plug-ins. If initialization is risky (for instance, it requires an online connection or does some communication with an external device), it's best to put this off until the user actually wants to do something that requires the use of the plug-in. Do the instantiation first, show the user that it was a success ("plug-in loaded") and that you're now initializing.

Packaging plug-ins

Plug-ins will have to reference your application to be able to implement your interface and decorate themselves with your attribute. However, you don't want to force plug-in makers to recompile their plug-ins whenever you release a new version of your application, so make sure the interface definitions are in a seperate assembly: since interfaces don't implement anything, these will be relatively stable. Only when you want to actually change the plug-in interface should these plug-ins be recompiled and since the plug-ins will probably have to actually be modified then anyway, that's the right time to do it. So decouple the application from its plug-ins by creating a seperate interface library. Note that this is all a lot less of an issue if you don't use strong-named assemblies. Especially with plug-in systems however, you'll want users to be able to verify who created the plug-ins they intend to use, so strong-naming is often a must.

That's not all there is to it though, because there are bound to be some actual classes that plug-ins will want to use as well. For starters there's the attribute class that they all need, but more often than not, some classes are defined that facilitate communication between application and plug-in. You can't put them in the application assembly because that will make the plug-in dependent on the application implementation again. Putting them in the interface library will make it a lot more likely that this assembly will receive more changes than you might want: classes that actually do something, as opposed to interfaces, often contain bugs and will need to be updated. My choice is usually to put simple classes that are required by plug-ins in the interface library, for instance the attribute class that won't do much but manage a couple of properties. All the other supporting classes should go in a seperate assembly, so that when bugs are fixed in them, only its users will need to be updated as well.

Comments

 

Wilco Bauwer said:

"Each plug-in can get its own application domain."

Hm, even though an app. domain is sort of a light-weight process, I wonder how well this works when you have a lot of plugins.

Also, when you use separate app. domains for plugins, it should be noted that communication between the app. and plugins require a little more thought. Communication across app. domains requires remoting. This means that handling events, passing objects etc. won't work the same way as it does when everything runs within one app. domain.

"My choice is usually to put simple classes that are required by plug-ins in the interface library.."

I prefer to let classes of the application implement certain interfaces, exposed through the "interface/shared lib". The application could then f.e. pass an instance to a plugin during initialization (or if you have all sorts of classes, the app. could pass some kind of factory to the plugin). This way, you still only need to share the interface between app. and plugins.
May 17, 2005 5:15 AM
 

Jeroen van den Bos said:

I have some experience in having up to 20 plug-ins loaded all in their seperate application domain. It doesn't put any unreasonable load on the system, nor does it damage performance - at least, it's probably not a good idea to make lots jumps between appdomains in some intensive innerloop, but good design should prevent that from ever happening :)

Good point about defining interfaces instead of simple shared classes. It gets more of an issue when the shared classes get complex.
May 22, 2005 9:11 AM
 

Thomas Williams said:

Thanks for the very interesting post - I am in the process of implementing first a simple system of plugins (for my application's "Options" form), and then a more complex system that other developers in the office here will use. I am using the MS.INC plug-in library from http://www.ms-inc.net/dotnet.aspx?ProductID=Plugins, but it's good to hear about other's experiences.

Cheers, Thomas
May 25, 2005 1:16 AM
 

Javier Luna said:

I believe that any DataLayer must be a simple code block, that they allow operations against DB.

That code block would not have to know on the Business Entities. Single to specialize it is to execute the operations (Store Procedures and SQL Sentences) against the engine DB (SQL, Oracle, DB2, etc.), with which this setting.

Finally, I invite to you to download the DataLayer.Primitives Public Version.

This is very cool Data Layer :)

DataLayer.Primitives - Readme!
http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=1389

Cheers,

Javier Luna
http://guydotnetxmlwebservices.blogspot.com/
May 26, 2005 11:05 PM

Leave a Comment

(required)  
(optional)
(required)  
Add