DI kontenery a architektura pluginowa - albo jak to zrobic inaczej?

0

cześć
krótka piłka, jest iluś tam klientów, którzy realizują zadania po swojemu, jest też mechanizm, który uruchamia proces dla klienta.
I teraz trzeba połączyć to w całość

pluginy to taki wydaje się że naturalny wybór aby to zrealizować - silnik ma "kontrakt", pluginy ten kontrakt realizuja na swój sposób, silnik odpala proces per klient i w teorii wszystko śmiga
ale jak w to wpleść DI kontener, bo wtedy wystepuje problem - wiele konkretyzacji tego samego interfejsu

poszukalem w necie (trochę) i odpowiedzią wydają się być np moduły z Autofac (https://autofaccn.readthedocs.io/en/latest/configuration/modules.html?fbclid=IwAR1D6Y41HiBy5tsL8GUgnWQ_a4DQz03f1un0hJOdDbWtxH5brdF5Dwjs-Vw)

a do szanownych forumowiczów mam pytanie

jak sobie z tym można poradzić?
czy pluginy to dobra droga? bo to z tego co wiem potencjalne ryzyko, a i zawsze może się coś sypnąć na runtime i spotkalem się z opiniami, że najlepiej ładować wszystko w jedno, a pluginy to ostatecznosć

z góry dziękuję za odpowiedź

0
mussel napisał(a):

pluginy to taki wydaje się że naturalny wybór aby to zrealizować - silnik ma "kontrakt", pluginy ten kontrakt realizuja na swój sposób, silnik odpala proces per klient i w teorii wszystko śmiga
ale jak w to wpleść DI kontener, bo wtedy wystepuje problem - wiele konkretyzacji tego samego interfejsu

poszukalem w necie (trochę) i odpowiedzią wydają się być np moduły z Autofac (https://autofaccn.readthedocs.io/en/latest/configuration/modules.html?fbclid=IwAR1D6Y41HiBy5tsL8GUgnWQ_a4DQz03f1un0hJOdDbWtxH5brdF5Dwjs-Vw)

Pamiętaj, że taki plugin będzie miał dostęp do wszystkich twoich zależności. Stwarza to zagrożenie, że będzie on miał dostęp do zasobu, do którego nie powinien mieć dostępu (np. bezpośredni dostęp do bazy danych). Mógłbyś dać jakieś ograniczenie, na przykład wymagany bezparametrowy konstruktor plugina.

jak sobie z tym można poradzić?
czy pluginy to dobra droga? bo to z tego co wiem potencjalne ryzyko, a i zawsze może się coś sypnąć na runtime i spotkalem się z opiniami, że najlepiej ładować wszystko w jedno, a pluginy to ostatecznosć

Ciężko odpowiedzieć na to pytanie nie wiedząc kto będzie mógł pisać i uruchamiać te pluginy. Użytkownicy? Jeśli tak to jest jakieś ryzyko, że będą mieli dostęp do zbyt wielu zasobów oraz, że wywalą aplikację.

0

Brzmi trochę jak wzorzec strategi na moje ucho.

Jak mówisz plugin to ja myślę dll'ki dostarczane przez zewnętrznych dostawców, ładowane przez refleksję z odrobiną security (odpowiednio obciosane AppDomain).

0

Mi też to wygląda na strategię, ale nie znamy szczegółów. Może kodu jest tyle do zaimplementowania, że dużo łatwiej zrobić to w pluginach...
Po prostu weź Autofac do swojego silnika. Stwórz sobie potem jakieś dwa interfejsy, np:

public interface IServiceCollection
{
    void RegisterType<TInterface, TClass>()
        where TInterface : class
        where TClass : class, TInterface;

    void RegisterType<TClass>() where TClass : class;
    void RegisterType(Type t);
    void RegisterType<TInterface, TClass>(object key);
    void RegisterWithParameter<TClass>(string paramName, object param);

    void RegisterTransient(Type t);
    void RegisterSingleton<TInterface, TClass>()
        where TInterface : class
        where TClass : class, TInterface;

    void RegisterSingleton<TClass>() where TClass : class;
    void RegisterInstance<TType>(TType obj) where TType : class;
}

Ten służy do rejestracji zależności i drugi:

public interface IObjectFactory : IDisposable
{
    T Resolve<T>();
    T Resolve<T>(object key);
    T ResolveWithParam<T>(params object[] parameters);
}

Ten służy do pobierania zależności.

Teraz możesz stworzyć klasę
IoCContainer:

public class IocContainer : IServiceCollection, IObjectFactory
{
    ContainerBuilder autofacBuilder;
    IContainer autofac;

    public IocContainer(ContainerBuilder afBuilder)
    {
        autofacBuilder = afBuilder;
        autofacBuilder.RegisterInstance<IObjectFactory>(this);
        autofacBuilder.RegisterInstance<IServiceCollection>(this);
    }

//i inne metody
//pamiętaj o Disposable
}

W swojej aplikacji (w silniku) tworzysz to:

ContainerBuilder builder = new ContainerBuilder();
factory = new IocContainer(builder);

I do pluginów przekazujesz już interfejsy. Dzięki klasie IocContainer możesz potem sprawdzać, czy ten, kto próbuje resolvnąć obiekt, może to zrobić. Można dodać jakiś dodatkowy parametr zarówno do rejestracji jak i resolvovania. Jakiś unikalny GUID dla pluginu, czy coś takiego.

0

@Juhas: A jaka jest zaleta robienia takiego wrappera na IoC? Czy pluginy nie mogłyby mieć po prostu swoich kontenerów zależności, takich jakie chcą? Ich cykl życia mógłby być kontrolowany np. przez jakieś metody interfejsu wedle uznania autora plugina (jakieś Start(), Stop(), czy coś takiego).

0

Pewnie, że mogłyby mieć. Pytanie było jak sobie poradzić z IOC w takiej architekturze, więc dałem odpowiedź :) Moim zdaniem posiadanie wielu kontenerów w wielu pluginach nie jest dobrym podejściem, bo:

  • silnik pełni tutaj rolę frameworka
  • frameworki mają zazwyczaj własne kontenery (lub używają tego z .NetCore)
  • pluginy i tak będą używały pewnie jednego albo dwóch kontenerów - a problem może się pojawić przy ogarnianiu dllek.
0

@Juhas: No nie wiem, jakoś nie przemawiają do mnie te argumenty. Jakbym pisał takiego plugina, to chciałbym mieć wybór takiego IoC, jaki najbardziej mi się podoba (albo w ogóle, jeśli nie potrzebuję IoC).

W ogóle ciężko odpowiedzieć na pytanie @mussel, bo to zależy od tego, kto będzie pisał te pluginy i jaka będzie ich odpowiedzialność. Jeśli pluginy mają być pisane przez użytkowników i uruchamiane na instancjach programu na ich własnym komputerze, to niech sobie piszą co chcą. Najwyżej program im się wyłoży, ewentualnie nadpiszą bazę danych i nikt do nich nie będzie miał pretensji. Ale jeśli jest ryzyko, że uszkodzą lub uzyskają dostęp do jakiś wspólnych danych (albo nawet jedynie skraszują wspólną instancję aplikacji) to warto to przemyśleć. Wtedy chyba lepiej zamiast architektury pluginów udostępnić chronione publiczne API.

0

dziękuję wszystkim za odpowiedzi
też coś poszukałem, poszperałem

i odpowiedź jest - wg mnie rzecz jasna - contextual binding
czyli resolvuje zależnosci stosownie do kontekstu

takie coś ma np ninject
a w autofac można to osiągnąć np tak
https://autofaccn.readthedocs.io/en/latest/faq/select-by-context.html

1 użytkowników online, w tym zalogowanych: 0, gości: 1