Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plugin Architecture With Forward & Backward Compatibility

I'm currently working on a C# product that will use a plugin type system. This isn't anything new and I have seen much info around about how to use a interface to implement this functionality quite easily.

I've also seen methods to implement backwards compatibility by updating the interface name, e.g.: Interface change between versions - how to manage?

There are multiple scenarios which I can foresee with our product in regards to version mismatches between the main exe and the plugin.

  1. Main Program same plugin version as plugin
  2. Main Program newer than plugin
  3. Main Program older than plugin

From the info I've been able to gather 1 & 2 work just fine. But I haven't been able to figure out how to correctly implement "forward" compatibility (3) properly.

It is our intention to only ADD methods to the plugin API.

Any ideas would be a great help.

like image 856
CamC Avatar asked Oct 22 '22 14:10

CamC


1 Answers

Isolated PluginAPI DLL

First, Your PluginAPI (containing the interfaces) should be a separate DLL to your main application. Your main application will reference the PluginAPI, and each plugin will reference the PluginAPI. You're most likely already doing this.

Interface Versioning

Second, structurally, you should create a new interface each time you add a new property or method.

For example:

  1. Version 1: Plugins.IPerson
  2. Version 2: Plugins.V2.IPerson : Plugins.IPerson
  3. Version 3: Plugins.V3.IPerson : Plugins.V2.IPerson

In rare cases where you decide to remove or completely redesign your API, example:

  1. Version 4: Plugins.V4.IPerson //Without any Interface inheritance

Isolated PluginAPI DLL Versioning

Finally, I am not 100% sure how versioning of the PluginAPI .dll will go even with this structural architecture of Interface versioning. It may work

OR

You may need to have matching dlls for each version (each referencing the previous version(s)). We will assume that this is the case.

Solution for case 3

So let's now take your case [3], main program older than plugin:

  • Person Plugin implements Plugins.V2.IPlugin and references the V3 .dll (just to make it interesting).
  • Main Program references the V1 .dll
  • The plugin folder will contain the V2 and V3 plugin .dlls
  • The main app folder will only contain the V1 plugin .dll (among other files)
  • Main App will find and load the Person plugin and reference through a V1 definition for the IPerson interface
  • Of course, only V1 methods and properties will be accessible from the plugin to the Main App
  • (Additional methods will be accessible through reflection - not that you would want to)

Bonus Update

When you might use plugins

  • Third-parties extending your system. Source code would be better if that's an option, or if it's web-based, redirect to their URL. This is a dream for many software projects, but you should wait until you have an interested third-party partner before doing the extra work to build the plugin framework.
  • User Editable "Scripts". You should not build your own scripting language, instead you should compiled the user c# code against a restrictive interface in an appdomain that is very restrictive (disabling reflection and others).
  • Security grouping - Your core software might use trusted platform calls. Riskier modules can be separated into another library and optionally excluded by end-users.

When not to use Plugins

I am an advocate for less-is-more. Don't overengineer. If you are building modular software that's great, use classes and namespaces (don't get carried away with interfaces). "Modular" means you are striving to adhere to SOLID principles, but that doesn't mean you need Plugin architecture. Even inversion of control is overkill in many situations.

If you plan to open to third-parties in the future, don't make it a plugin architecture to start with. You can build out a plugin framework later in stages: i) derive interfaces; ii) define your plugins with interfaces within the same project; iii) load your internal plugins with a plugin loader class; iv) finally, you can implement an external library loader. Each of these 4 steps leave you with a working system on their own and move you toward a finished plugin system.

Hot Swappable Plugins

When designing a plugin architecture, you may be interested to know that you can make plugins hot swappable:

  1. Without Freeing Memory - Just keep loading the new plugin. This is usually fine, unless it's maybe for a server software which you expect i) to run for a very long time without restarting; AND ii) expect many plugin changes and upgrades during that time. When you load a plugin at runtime, it loads the assembly into memory and cannot be unloaded. See [2] for why.

  2. With Freeing Memory - You can unload an AppDomain. An AppDomain runs in the same process but are reference isolated - you can't reference or call objects directly. Instead calls must be marshalled and data must be serialised in between appdomains. The added complexity is not worth it if you're not going to change plugins often, there is: i) a performance penalty due to marshalling/serialization, ii) much more coding complexity (you can't simply use events and delegates and methods as normal), iii) this all leads to more bugs and makes it more difficult to debug.

So if option [2] entices you, please try [1] first, and use that architecture until you have the problems necessary for [2]. Never over-architect. Trust me, I have built a [2] architecture before during University, it's fun, but in most cases overkill and will likely kill your project (spending too much time on non-business functions).

like image 67
Kind Contributor Avatar answered Oct 27 '22 20:10

Kind Contributor