Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Plugin architecture and references to user configurable database settings

Tags:

c#

.net

plugins

I have a database application that is configurable by the user - some of these options are selecting from different external plugin systems.

I have a base Plugin type, my database schema has the same Plugin record type with the same fields. I have a PlugingMananger to load plugins (via an IoC container) at application start and link them to the database (essentially copies the fields form the plugin on disk to the database).

public interface IPlugin
{
    Guid Id{ get; }
    Version Version { get; }
    string Name { get; }
    string Description { get; }
}

Plugins can then be retrieved using PlugingMananger.GetPlugin(Guid pluginId, Guid userId), where the user ID is that of the one of the multiple users who a plugin action may be called for.

A set of known interfaces have been declared by the application in advance each specific to a certain function (formatter, external data, data sender etc), if the plugin implements a service interface which is not known then it will be ignored:

public interface IAccountsPlugin : IPlugin
{
    IEnumerable<SyncDto> GetData();
    bool Init();
    bool Shutdown();
}

Plugins can also have settings attributes PluginSettingAttribute defined per user in the multi-user system - these properties are set when a plugin is retrieved for a specific user, and a PluginPropertyAttribute for properties which are common across all users and read-only set by the plugin one time when the plugin is registered at application startup.

public class ExternalDataConnector : IAccountsPlugin
{
    public IEnumerable<AccountSyncDto> GetData() { return null; }
    public void Init() { }
    public void Shutdown() { }

    private string ExternalSystemUsername;
    // PluginSettingAttribute will create a row in the settings table, settingId
    // will be set to provided constructor parameter. this field will be written to
    // when a plugin is retrieved by the plugin manager with the value for the
    // requesting user that was retrieved from the database.
    [PluginSetting("ExternalSystemUsernameSettingName")]
    public string ExternalSystemUsername
    {
        get { return ExternalSystemUsername }
        set { ExternalSystemUsername = value; } 
    }

    // PluginPropertyAttribute will create a row in the attributes table common for all users
    [PluginProperty("ShortCodeName")]
    public string ShortCode
    {
        get { return "externaldata"; }
    }

    public Version PluginVersion
    {
        get { return new Version(1, 0, 0, 0); }
    }

    public string PluginName
    {
        get { return "Data connector"; }
    }

    public string PluginDescription
    {
        get { return "Connector for collecting data"; }
    }
}

Here are my questions and areas I am seeking guidance on:

  1. With the above abstraction of linking plugins in an IoC container to database, the user can select the database field Customer.ExternalAccountsPlugin = idOfExternalPlugin. This feels heavy - is there a simpler way that other systems achieve this (SharePoint for instance has lots of plugins that are referenced by the user database)?

  2. My application dictates at compile time the interfaces that it supports and ignores all others - I have seen some systems claim to be fully expandable with open plugins which I presume would mean lots of loosely typed interfaces and casting, is there a half-way ground between the two options that would allow future updates to be issued without recompile but still use concrete interfaces?

  3. My plugins could contain metadata (PluginProperty or PluginSetting) and I am unsure the best place to store this, either in a plugin metadata table (would make linq queries more complex) or direct in the plugin database record row (easy linq queries PluginManager.GetPluginsOfType<IAccounts>.Where(x => x.ShortCode = "externaldata").FirstOrDefault();, which is used as best practice?

  4. Since plugins capabilities and interfaces rely so heavily on the database schema, what is the recommended way I can limit a plugin for use with a specific schema revision? Would I keep this schema revision as a single row in a settings table in the database and update this manually after each release? Would the plugin support a maximum schema version, or would the application support a list of known plugin versions?

like image 226
morleyc Avatar asked Oct 16 '12 20:10

morleyc


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr. Stroustroupe.

Is C programming hard?

C is more difficult to learn than JavaScript, but it's a valuable skill to have because most programming languages are actually implemented in C. This is because C is a “machine-level” language. So learning it will teach you how a computer works and will actually make learning new languages in the future easier.


1 Answers

1) I'm sorry, but I don't know for sure. However, I'm pretty sure, in software that have data created or handled by custom plugin, they handle the plugin the way you described. The idea being, if a user load the data but is missing that specific plugin, the data doesn't become corrupted and the user isn't allowed to modify that data. (An example that comes to my minds is 3D softwares in general)

2) Only giving a very strict interface implementation, of course, highly restrict the plugin creation. (Ex.: Excel, I can't create a new cell type) It's not bad or good, it highly depends what you want from it, it's a choice. If you want the plugin creator to only access the data by some very specific pipes, limit the types of data he can create, then it goes with your design. Otherwise, if you goal is to open your software to improvement, then you should also expose some classes and methods you judge safe enough to be used externally. (Ex.: Maya, I can create a new entity type that derive from a base class, not just an interface)

3) Well, it does depends of a lot of things, no? When serializing your data, you could create a wrapper that contain all information for a specific plugin, ID, MetaData and whatever else you would judge needed. I would go that way, as it would be easier to retrieve, but is it the best way for what you need? Hard to say without more informations.

4) A good example of that is Firefox. Smaller version increment doesn't change the plugin compatibility. Medium version increment tests from a database if the plugin is still valid considering what it implements. If the plugin isn't implementing something that change, it is still valid. Major version increment requires a recompile of all plugins to use the new definition. From my point of view, it's a nice middle ground that allow devs to not always recompile, but it makes the development of the main software slightly more tricky as changes must be planned ahead. The idea is to balance the PitA (Pain in the Ass) factor between the software dev and the plugin dev.

Well... that was my long collection of 2 cents.

like image 77
LightStriker Avatar answered Oct 27 '22 04:10

LightStriker