Ioc / DI - Dlaczego muszę odwoływać się do wszystkich warstw / zestawów w punkcie wejścia aplikacji?

123

(W związku z tym pytaniem, EF4: dlaczego tworzenie serwera proxy musi być włączone, gdy jest włączone ładowanie z opóźnieniem? ).

Jestem nowy w DI, więc wytrzymaj ze mną. Rozumiem, że kontener jest odpowiedzialny za tworzenie instancji wszystkich moich zarejestrowanych typów, ale w tym celu wymaga odwołania do wszystkich bibliotek DLL w moim rozwiązaniu i ich odwołań.

Gdybym nie używał kontenera DI, nie musiałbym odwoływać się do biblioteki EntityFramework w mojej aplikacji MVC3, tylko do mojej warstwy biznesowej, która odwoływałaby się do mojej warstwy DAL / Repo.

Wiem, że na koniec dnia wszystkie biblioteki DLL znajdują się w folderze bin, ale mój problem polega na tym, że muszę jawnie odwoływać się do niego za pomocą opcji „dodaj odniesienie” w VS, aby móc opublikować WAP ze wszystkimi niezbędnymi plikami.

diegohb
źródło
1
Ten fragment książki Dependency Injection in .NET, drugie wydanie, jest bardziej rozbudowaną wersją odpowiedzi zarówno Marka, jak i mnie. Opisuje szczegółowo koncepcję katalogu głównego kompozycji i dlaczego pozwalanie ścieżce startowej aplikacji zależeć od każdego innego modułu jest w rzeczywistości dobrą rzeczą.
Steven
Przeczytałem ten link z fragmentem i rozdział 1. Będę kupować książkę, ponieważ bardzo podobały mi się analogie i proste wyjaśnienia złożonej sprawy DI. Myślę, że powinieneś zasugerować nową odpowiedź, odpowiedz wyraźnie „nie musisz odwoływać się do wszystkich warstw / zespołów we wstępnej warstwie logicznej, chyba że jest to również katalog główny kompozycji”, łącze do fragmentu i opublikowanie obrazu Rysunek 3, z fragment.
diegohb

Odpowiedzi:

194

Gdybym nie korzystał z kontenera DI, nie musiałbym odwoływać się do biblioteki EntityFramework w mojej aplikacji MVC3, tylko do mojej warstwy biznesowej, która odwoływałaby się do mojej warstwy DAL / Repo.

Tak, to jest dokładnie sytuacja, której DI tak bardzo stara się uniknąć :)

W przypadku ściśle powiązanego kodu każda biblioteka może mieć tylko kilka odniesień, ale te ponownie mają inne odniesienia, tworząc głęboki wykres zależności, taki jak ten:

Głęboki wykres

Ponieważ wykres zależności głęboko, oznacza to, że większość bibliotek przeciągania wraz z wieloma innymi zależne - na przykład w schemacie biblioteki C wlecze Biblioteka H, E, Library Biblioteka J, M, Library Biblioteka K i biblioteki N . Utrudnia to ponowne użycie każdej biblioteki niezależnie od reszty - na przykład w testach jednostkowych .

Jednak w luźno powiązanej aplikacji, po przeniesieniu wszystkich odwołań do katalogu głównego kompozycji , wykres zależności jest poważnie spłaszczony :

Płytki wykres

Jak ilustruje zielony kolor, teraz możliwe jest ponowne użycie Biblioteki C bez przeciągania niechcianych zależności.

Jednak wszystko to powiedziawszy, w przypadku wielu kontenerów DI nie trzeba dodawać twardych odwołań do wszystkich wymaganych bibliotek. Zamiast tego można użyć późnego wiązania w postaci skanowania zestawu opartego na konwencji (preferowane) lub konfiguracji XML.

Kiedy to zrobisz, musisz jednak pamiętać o skopiowaniu zestawów do folderu bin aplikacji, ponieważ nie dzieje się to już automatycznie. Osobiście rzadko uważam, że jest to warte dodatkowego wysiłku.

Bardziej rozbudowaną wersję tej odpowiedzi można znaleźć w tym fragmencie mojej książki Dependency Injection, Principles, Practices, Patterns .

Mark Seemann
źródło
3
Wielkie dzięki, teraz ma to sens. Musiałem wiedzieć, czy było to zgodne z projektem. Jeśli chodzi o wymuszanie prawidłowego użycia zależności, zaimplementowałem osobny projekt z moim programem ładującym DI, jak wspomniany poniżej Steven, w którym odwołuję się do pozostałych bibliotek. Ten projekt jest określany przez aplikację punktu wejścia i na końcu pełnej kompilacji powoduje to, że wszystkie niezbędne biblioteki DLL znajdują się w folderze bin. dzięki!
diegohb
2
@Mark Seemann Czy to pytanie / odpowiedź dotyczy firmy Microsoft? Chciałbym wiedzieć, czy pomysł przeniesienia wszystkich zależności do „punktu wejścia aplikacji” ma sens w projekcie Java EE / Spring wykorzystującym Maven… dzięki!
Grégoire C
5
Ta odpowiedź dotyczy nie tylko platformy .NET. Możesz odnieść się do rozdziału Zasady projektowania pakietów Roberta C. Martina w np. Zwinne tworzenie oprogramowania, zasady, wzorce i praktyki
Mark Seemann,
7
@AndyDangerGagne Katalog główny kompozycji jest wzorcem DI - przeciwieństwem Lokalizatora usług . Z punktu widzenia korzenia kompozycji żaden z typów nie jest polimorficzny; Kompozycja korzeń traktuje wszystkie typy jako typy konkretne, a zatem zasada podstawienia Liskova nie ma do niej zastosowania.
Mark Seemann,
4
Z reguły interfejsy powinny być definiowane przez klientów ich używających ( APP, rozdz. 11 ), więc jeśli Biblioteka J potrzebuje interfejsu, to powinna być zdefiniowana w Bibliotece J. Jest to konsekwencją zasady odwrócenia zależności.
Mark Seemann
65

Gdybym nie korzystał z kontenera DI, nie musiałbym odwoływać się do biblioteki EntityFramework w mojej aplikacji MVC3

Nawet w przypadku korzystania z kontenera DI nie musisz zezwalać na odwołanie do EF projektu MVC3, ale (niejawnie) decydujesz się to zrobić, implementując element główny kompozycji (ścieżkę startową, w której tworzysz wykresy obiektów) w projekcie MVC3. Jeśli bardzo rygorystycznie podchodzisz do ochrony granic architektonicznych za pomocą zespołów, możesz przenieść logikę prezentacji do innego projektu.

Po przeniesieniu całej logiki związanej z MVC (kontrolery itp.) Z projektu startowego do biblioteki klas, pozwala to zestawowi warstwy prezentacji pozostać odłączonym od reszty aplikacji. Twój projekt aplikacji internetowej sam stanie się bardzo cienką powłoką z wymaganą logiką uruchamiania. Projekt aplikacji internetowej będzie katalogiem głównym kompozycji, który będzie odwoływał się do wszystkich innych zestawów.

Wyodrębnienie logiki prezentacji do biblioteki klas może skomplikować sprawę podczas pracy z MVC. Trudniej będzie wszystko połączyć, ponieważ kontrolerów nie ma w projekcie startowym (podczas gdy widoki, obrazy, pliki css muszą prawdopodobnie pozostać w projekcie startowym). Jest to prawdopodobnie wykonalne, ale konfiguracja zajmie więcej czasu.

Ze względu na wady generalnie radzę po prostu zachować główny katalog kompozycji w projekcie internetowym. Wielu programistów nie chce, aby ich zestaw MVC był zależny od zestawu DAL, ale tak naprawdę nie stanowi to problemu. Nie zapominaj, że zestawy są artefaktem wdrażania ; dzielisz kod na wiele zestawów, aby umożliwić oddzielne wdrażanie kodu. Z drugiej strony warstwa architektoniczna jest logicznym artefaktem. Bardzo możliwe (i powszechne) jest posiadanie wielu warstw w tym samym zespole.

W tym przypadku w końcu będziemy mieć główny element kompozycji (warstwę) i warstwę prezentacji w tym samym projekcie aplikacji internetowej (a więc w tym samym zestawie). Mimo że ten zestaw odwołuje się do zestawu zawierającego DAL, warstwa prezentacji nadal nie odwołuje się do warstwy dostępu do danych . To duże wyróżnienie.

Oczywiście, kiedy to robimy, tracimy zdolność kompilatora do sprawdzenia tej reguły architektonicznej w czasie kompilacji, ale nie powinno to stanowić problemu. Kompilator nie może sprawdzić większości reguł architektonicznych i zawsze istnieje coś takiego jak zdrowy rozsądek. A jeśli w Twoim zespole nie ma zdrowego rozsądku, zawsze możesz skorzystać z przeglądu kodu (który każdy zespół powinien zawsze robić w IMO). Możesz także użyć narzędzia takiego jak NDepend (które jest komercyjne), które pomaga Ci zweryfikować reguły architektoniczne. Integracja NDepend z procesem kompilacji może Cię ostrzec, gdy ktoś włączy kod, który narusza taką zasadę architektoniczną.

Możesz przeczytać bardziej szczegółową dyskusję na temat tego, jak działa Composition Root, w rozdziale 4 mojej książki Dependency Injection, Principles, Practices, Patterns .

Steven
źródło
Osobny projekt bootstrap był moim rozwiązaniem, ponieważ nie mamy ndepend i nigdy wcześniej go nie używałem. Przyjrzę się temu jednak, ponieważ brzmi to jak lepszy sposób na osiągnięcie tego, co próbuję zrobić, gdy będzie tylko jedna aplikacja końcowa.
diegohb
1
Ostatni akapit jest świetny i zaczyna mi pomagać zmienić zdanie na temat tego, jak rygorystycznie przestrzegam zasad utrzymywania warstw w osobnych zespołach. Posiadanie dwóch lub więcej warstw logicznych w jednym zestawie jest w rzeczywistości w porządku, jeśli używasz innych procesów związanych z pisaniem kodu (takich jak przeglądy kodu), aby upewnić się, że w kodzie interfejsu użytkownika nie ma odwołań do klas DAL i odwrotnie.
BenM
6

Gdybym nie korzystał z kontenera DI, nie musiałbym odwoływać się do biblioteki EntityFramework w mojej aplikacji MVC3, tylko do mojej warstwy biznesowej, która odwoływałaby się do mojej warstwy DAL / Repo.

Możesz utworzyć osobny projekt o nazwie „DependencyResolver”. W tym projekcie musisz odwołać się do wszystkich swoich bibliotek.

Teraz warstwa UI nie potrzebuje NHibernate / EF ani żadnej innej biblioteki, która nie dotyczy interfejsu użytkownika, z wyjątkiem Castle Windsor.

Jeśli chcesz ukryć Castle Windsor i DependencyResolver w warstwie UI, możesz napisać HttpModule, który wywołuje rejestr IoC.

Mam tylko przykład dla StructureMap:

public class DependencyRegistrarModule : IHttpModule
{
    private static bool _dependenciesRegistered;
    private static readonly object Lock = new object();

    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, args) => EnsureDependenciesRegistered();
    }

    public void Dispose() { }

    private static void EnsureDependenciesRegistered()
    {
        if (!_dependenciesRegistered)
        {
            lock (Lock)
            {
                if (!_dependenciesRegistered)
                {
                    ObjectFactory.ResetDefaults();

                    // Register all you dependencies here
                    ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry()));

                    new InitiailizeDefaultFactories().Configure();
                    _dependenciesRegistered = true;
                }
            }
        }
    }
}

public class InitiailizeDefaultFactories
{
    public void Configure()
    {
        StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type);
          ...
    }
 }

DefaultControllerFactory nie używa bezpośrednio kontenera IoC, ale deleguje do metod kontenera IoC.

public class StructureMapControllerFactory : DefaultControllerFactory
{
    public static Func<Type, object> GetController = type =>
    {
        throw new  InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!");
    };

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, controllerType);
        }
        return GetController(controllerType) as Controller;
    }
}

GetControllerDelegat jest w StructureMap rejestru (w Windsor powinno być Installer).

Rookian
źródło
1
Podoba mi się to nawet bardziej niż to, co zrobiłem, moduły są świetne. więc gdzie powinienem wykonać wywołanie Container.Dispose ()? ApplicationEnd lub EndRequest w module ...?
diegohb
1
@Steven Ponieważ Global.asax znajduje się w Twojej warstwie interfejsu użytkownika MVC. HttpModule byłby w projekcie DependencyResolver.
Rookian
1
Niewielką zaletą jest to, że nikt nie może używać kontenera IoC w interfejsie użytkownika. Oznacza to, że nikt nie jest w stanie używać kontenera IoC jako lokalizatora usług w interfejsie użytkownika.
Rookian
1
Ponadto uniemożliwia programistom przypadkowe użycie kodu DAL w warstwie interfejsu użytkownika, ponieważ nie ma stałego odniesienia do zestawu w interfejsie użytkownika.
diegohb
1
Dowiedziałem się, jak zrobić to samo, korzystając z ogólnego API rejestracji Bootstrappera. Mój projekt interfejsu użytkownika odwołuje się do programu Bootstrapper, projektu rozwiązywania zależności, w którym łączę moje rejestracje i projekty w moim rdzeniu (dla interfejsów), ale nic więcej, nawet moja struktura DI (SimpleInjector). Używam nuget OutputTo do kopiowania bibliotek dll do folderu bin.
diegohb,
0
  • Istnieje zależność: jeśli obiekt tworzy instancję innego obiektu.
  • Nie ma zależności: jeśli obiekt oczekuje abstrakcji (wstrzyknięcie konstruktora, wstrzyknięcie metody ...)
  • Odniesienia do zestawów (odwołujące się do bibliotek dll, usług sieciowych ...) są niezależne od koncepcji zależności, ponieważ aby rozwiązać abstrakcję i móc skompilować kod, warstwa musi się do niej odwoływać.
riadh gomri
źródło