我有一个可由用户配置的数据库应用程序 - 其中一些选项是从不同的外部插件系统中选择的.
我有一个基本插件类型,我的数据库架构具有相同的插件记录类型具有相同的字段.我有一个PlugingMananger在应用程序启动时加载插件(通过IoC容器)并将它们链接到数据库(基本上将磁盘插件中的字段复制到数据库).
public interface IPlugin
{
Guid Id{ get; }
Version Version { get; }
string Name { get; }
string Description { get; }
}
Run Code Online (Sandbox Code Playgroud)
然后可以使用检索插件PlugingMananger.GetPlugin(Guid pluginId, Guid userId),其中用户ID是可以调用插件动作的多个用户之一的用户ID.
应用程序已经预先声明了一组已知的接口,每个接口都特定于某个函数(格式化程序,外部数据,数据发送器等),如果插件实现了一个未知的服务接口,那么它将被忽略:
public interface IAccountsPlugin : IPlugin
{
IEnumerable<SyncDto> GetData();
bool Init();
bool Shutdown();
}
Run Code Online (Sandbox Code Playgroud)
插件还可以PluginSettingAttribute在多用户系统中为每个用户定义设置属性- 这些属性是在为特定用户检索插件时设置的,以及PluginPropertyAttribute用于所有用户通用的属性和插件一次设置的只读属性当插件在应用程序启动时注册.
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"; }
}
}
Run Code Online (Sandbox Code Playgroud)
以下是我在寻求指导的问题和方面:
通过上述将IoC容器中的插件链接到数据库的抽象,用户可以选择数据库字段Customer.ExternalAccountsPlugin = idOfExternalPlugin.这感觉很重 - 有没有其他系统实现这一点的简单方法(例如,SharePoint有很多用户数据库引用的插件)?
我的应用程序在编译时指示它支持的接口并忽略所有其他接口 - 我已经看到一些系统声称可以使用开放插件完全扩展,我认为这意味着许多松散类型的接口和转换,是否存在中间路径这两个选项将允许在不重新编译但仍使用具体接口的情况下发布未来的更新?
我的插件可能包含元数据(PluginProperty或PluginSetting),我不确定存储它的最佳位置,无论是在插件元数据表中(会使linq查询更复杂)还是直接在插件数据库记录行中(简单的linq查询PluginManager.GetPluginsOfType<IAccounts>.Where(x => x.ShortCode = "externaldata").FirstOrDefault();,这是用作最佳做法?
由于插件功能和接口在很大程度上依赖于数据库模式,我建议的限制插件使用特定模式修订的方法是什么?我会将此架构修订保留为数据库中设置表中的单行,并在每次发布后手动更新吗?插件是否支持最大模式版本,或者应用程序是否支持已知插件版本的列表?
1) 抱歉,我不确定。但是,我非常确定,在由自定义插件创建或处理数据的软件中,它们会按照您所描述的方式处理插件。这个想法是,如果用户加载数据但缺少该特定插件,则数据不会损坏,并且不允许用户修改该数据。(我想到的一个例子是一般的 3D 软件)
2)只给出非常严格的接口实现,当然,高度限制了插件的创建。(例如:Excel,我无法创建新的单元格类型)它没有好坏之分,它很大程度上取决于您想要从中得到什么,这是一个选择。如果您希望插件创建者只能通过某些非常特定的管道访问数据,限制他可以创建的数据类型,那么它就符合您的设计。否则,如果您的目标是开放您的软件以进行改进,那么您还应该公开一些您认为足够安全以供外部使用的类和方法。(例如:Maya,我可以创建一个从基类派生的新实体类型,而不仅仅是一个接口)
3)嗯,这确实取决于很多事情,不是吗?序列化数据时,您可以创建一个包装器,其中包含特定插件的所有信息、ID、元数据以及您认为需要的任何其他信息。我会这样做,因为它会更容易检索,但这是满足您需要的最佳方式吗?如果没有更多信息,很难说。
4) Firefox 就是一个很好的例子。较小的版本增量不会改变插件兼容性。中等版本增量测试来自数据库,考虑到插件的实现,插件是否仍然有效。如果插件没有实现更改的内容,它仍然有效。主要版本增量需要重新编译所有插件才能使用新定义。从我的角度来看,这是一个很好的中间立场,允许开发人员不必总是重新编译,但它使主软件的开发稍微复杂一些,因为必须提前计划更改。这个想法是平衡软件开发人员和插件开发人员之间的 PitA(痛苦)因素。
嗯……那是我长期收藏的 2 美分。