Архитектура плагина С# и ссылки на настраиваемые параметры базы данных пользователя

У меня есть приложение базы данных, которое настраивается пользователем - некоторые из этих параметров выбирают из разных внешних систем плагинов.

У меня есть базовый тип плагина, моя схема базы данных имеет тот же тип записи плагина с теми же полями. У меня есть PlugingMananger для загрузки плагинов (через контейнер IoC) при запуске приложения и привязки их к базе данных (по существу копирует поля из плагина на диске в базу данных).

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

Затем можно загрузить плагины с помощью PlugingMananger.GetPlugin(Guid pluginId, Guid userId), где идентификатор пользователя - это имя одного из нескольких пользователей, для которого может быть вызвано действие плагина.

Приложением заранее объявлено множество известных интерфейсов, каждое из которых связано с определенной функцией (форматирование, внешние данные, отправитель данных и т.д.), если плагин реализует интерфейс службы, который неизвестен, тогда он будет проигнорирован:

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

Плагины могут также иметь атрибуты параметров 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"; }
    }
}

Вот мои вопросы и области, на которые я ищу руководство:

  • С приведенной выше абстракцией связывания плагинов в контейнере IoC с базой данных пользователь может выбрать поле базы данных Customer.ExternalAccountsPlugin = idOfExternalPlugin. Это тяжело - есть ли более простой способ достижения другими системами (у SharePoint, например, есть много плагинов, на которые ссылается пользовательская база данных)?

  • Мое приложение диктует во время компиляции интерфейсы, которые он поддерживает и игнорирует все остальные. Я видел, что некоторые системы утверждают, что они полностью расширяемы с помощью открытых плагинов, которые, как я полагаю, означают множество свободно типизированных интерфейсов и кастинг, есть ли на полпути между двумя параметрами, позволяющими выпускать будущие обновления без повторной компиляции, но при этом использовать конкретные интерфейсы?

  • Мои плагины могут содержать метаданные (PluginProperty или PluginSetting), и я не уверен, что это лучшее место для хранения этого файла, либо в таблице метаданных плагина (сделайте запросы linq более сложными), либо прямо в строке записи базы данных плагина ( easy linq queries PluginManager.GetPluginsOfType<IAccounts>.Where(x => x.ShortCode = "externaldata").FirstOrDefault();, который используется как лучшая практика?

  • Поскольку возможности и интерфейсы плагинов сильно зависят от схемы базы данных, каков рекомендуемый способ, я могу ограничить плагин для использования с конкретной ревизией схемы? Могу ли я сохранить эту ревизию схемы как одну строку в таблице настроек в базе данных и обновить ее вручную после каждой версии? Будет ли плагин поддерживать максимальную версию схемы или приложение будет поддерживать список известных версий плагинов?

Ответ 1

1) Извините, но я точно не знаю. Тем не менее, я уверен, в программном обеспечении, которое создало или обрабатывало пользовательский плагин, они обрабатывают плагин так, как вы описали. Идея заключается в том, что если пользователь загружает данные, но не имеет этого конкретного плагина, данные не повреждаются, и пользователю не разрешается изменять эти данные. (Примером, который приходит мне на ум, является программное обеспечение 3D вообще)

2) Только очень строгая реализация интерфейса, конечно, сильно ограничивает создание плагина. (Например: Excel, я не могу создать новый тип ячейки) Это не плохо или хорошо, это сильно зависит от того, что вы хотите от него, это выбор. Если вы хотите, чтобы создатель плагина имел доступ только к данным по некоторым конкретным каналам, ограничьте типы данных, которые он может создать, а затем он идет с вашим дизайном. В противном случае, если вы хотите открыть свое программное обеспечение для улучшения, вам также следует выставить несколько классов и методов, которые вы судите достаточно хорошо, чтобы их можно было использовать извне. (Например: Maya, я могу создать новый тип сущности, который происходит от базового класса, а не только от интерфейса)

3) Ну, это зависит от многих вещей, нет? При сериализации ваших данных вы можете создать оболочку, содержащую всю информацию для определенного плагина, идентификатора, метаданных и того, что вы считаете нужным. Я бы пошел таким образом, так как было бы легче получить, но это лучший способ для того, что вам нужно? Трудно сказать без дополнительной информации.

4) Хорошим примером этого является Firefox. Меньшая приращение версии не изменяет совместимость с плагином. Средние тесты приращения версии из базы данных, если плагин по-прежнему действителен, учитывая то, что он реализует. Если плагин не реализует что-то, что меняется, оно все еще действует. Приращение основной версии требует перекомпиляции всех плагинов для использования нового определения. С моей точки зрения, это хорошая промежуточная площадка, которая позволяет разработчикам не всегда перекомпилировать, но это делает разработку основного программного обеспечения немного более сложной, поскольку изменения должны быть запланированы заранее. Идея состоит в том, чтобы сбалансировать коэффициент PitA (Pain in the Ass) между программным обеспечением dev и плагином dev.

Ну... это была моя длинная коллекция в 2 цента.