wake-up-neo.com

Ioc / DI - Warum muss ich alle Layer / Baugruppen im Einstiegspunkt der Anwendung referenzieren?

(Im Zusammenhang mit dieser Frage EF4: Warum muss die Proxy-Erstellung aktiviert sein, wenn das verzögerte Laden aktiviert ist? ).

Ich bin neu bei DI, also nimm es mit. Ich verstehe, dass der Container für die Instanziierung aller meiner registrierten Typen zuständig ist. Dazu ist jedoch ein Verweis auf alle DLLs in meiner Lösung und deren Verweise erforderlich.

Wenn ich keinen DI-Container verwenden würde, müsste ich in meiner MVC3-App nicht auf die EntityFramework-Bibliothek verweisen, sondern nur auf meine Business-Schicht, die auf meine DAL/Repo-Schicht verweist.

Ich weiß, dass am Ende des Tages alle DLLs im bin-Ordner enthalten sind, aber mein Problem ist, dass ich explizit über "Verweis hinzufügen" in VS darauf verweisen muss, um einen WAP mit allen erforderlichen Dateien veröffentlichen zu können.

112
diegohb

Wenn ich keinen DI-Container verwenden würde, müsste ich in meiner MVC3-App nicht auf die EntityFramework-Bibliothek verweisen, sondern nur auf meine Business-Schicht, die auf meine DAL/Repo-Schicht verweist.

Ja, genau das ist die Situation, in der DI so schwer zu vermeiden ist :)

Bei eng gekoppeltem Code enthält jede Bibliothek möglicherweise nur wenige Verweise, aber auch diese Verweise enthalten andere Verweise, wodurch ein umfassendes Abhängigkeitsdiagramm wie das folgende erstellt wird:

Deep Graph

Da der Abhängigkeitsgraph tief ist, bedeutet dies, dass die meisten Bibliotheken viele andere Abhängigkeiten mitschleppen - z. im Diagramm zieht sich Bibliothek C mit Bibliothek H, Bibliothek E, Bibliothek J, Bibliothek M, Bibliothek K und Bibliothek N. Dies macht es schwieriger, jede Bibliothek unabhängig vom Rest wiederzuverwenden - zum Beispiel beim Komponententest .

Wenn Sie in einer lose gekoppelten Anwendung alle Verweise auf Composition Root verschieben, wird der Abhängigkeitsgraph stark abgeflacht :

Shallow Graph

Wie die grüne Farbe zeigt, ist es jetzt möglich, Library C wiederzuverwenden, ohne unerwünschte Abhängigkeiten mitzunehmen.

Trotzdem müssen Sie bei vielen DI-Containern nicht have alle erforderlichen Bibliotheken mit harten Referenzen versehen. Stattdessen können Sie die späte Bindung entweder in Form eines konventionellen Assembler-Scans (bevorzugt) oder einer XML-Konfiguration verwenden.

Wenn Sie dies tun, müssen Sie jedoch daran denken, die Assemblys in den Bin-Ordner der Anwendung zu kopieren, da dies nicht mehr automatisch geschieht. Persönlich finde ich es selten wert, dass zusätzliche Mühe.

Eine ausführlichere Version dieser Antwort finden Sie in dieser Auszug aus meinem Buch Abhängigkeitsinjektion, Prinzipien, Praktiken, Muster .

185
Mark Seemann

Wenn ich keinen DI-Container verwenden würde, müsste ich in meiner MVC3-App nicht auf die EntityFramework-Bibliothek verweisen

Selbst wenn Sie einen DI-Container verwenden, müssen Sie nicht zulassen, dass Ihr MVC3-Projekt auf EF verweist, aber Sie müssen dies (implizit) durch Implementieren des Befehls Composition Root (des Startpfads, in dem Sie Ihr Objekt erstellen) tun Grafiken) in Ihrem MVC3-Projekt. Wenn Sie beim Schutz Ihrer Architekturgrenzen mit Assemblys sehr streng vorgehen, können Sie entweder das Composition Root-Element oder das MVC-Präsentationsmaterial in eine Klassenbibliothek verschieben.

In der ersten Option lässt Ihr MVC3-Projekt auf diese separate 'Bootstrapper'-Assembly verweisen, und es verweist auf alle anderen Assemblys in Ihrer Lösung sowie auf Ihre DI-Container-Bibliothek. Das Problem dabei ist, dass dieses Bootstrapper-Projekt nicht auf Typen verweisen kann, die sich im MVC3-Projekt befinden (da dies zu einer zyklischen Assembly-Abhängigkeit führt). Diese Typen müssen in das Bootstrapper-Projekt verschoben werden (das möglicherweise auf System.Web.Mvc verweisen muss), oder Sie müssen einen kleinen Teil der Containerkonfiguration in Ihrer MVC3-App behalten. Beachten Sie außerdem, dass Ihr MVC-Projekt weiterhin indirekt über die neue Bootstrapper-Assembly auf alle anderen Assemblys verweist, da Assembly-Abhängigkeiten transitiv sind.

Obwohl es sinnvoll ist, den Kompositionsstamm in einer separaten Assembly zu platzieren, verschieben die meisten DI-Puristen (einschließlich mir) den Kompositionsstamm normalerweise nur in eine Klassenbibliothek, wenn mehrere Endanwendungen vorhanden sind (z. B. eine Webanwendung + ein Webdienst + ein Windows-Dienst) ), die dieselbe Business-Schicht verwenden. Wenn ich eine einzelne Anwendung habe, behalte ich die Composition Root in meiner Endanwendung.

Die zweite Option besteht darin, alle MVC-bezogenen Klassen (Ansichten, Controller usw.) aus dem Startprojekt in eine Klassenbibliothek zu verschieben. Dadurch bleibt diese neue Präsentationsebenen-Baugruppe vom Rest der Anwendung getrennt. Ihr Webanwendungsprojekt selbst wird zu einer sehr dünnen Shell mit der erforderlichen Startlogik. Das Webanwendungsprojekt ist das Kompositionsstammverzeichnis, das auf alle anderen Assemblys verweist.

Das Extrahieren der Präsentationslogik in eine Klassenbibliothek kann die Arbeit mit MVC erschweren. Es wird schwieriger sein, alles zu verkabeln, da sich Controller und Ansichten, Bilder, CSS-Dateien usw. nicht im Startprojekt befinden. Dies ist wahrscheinlich machbar, wird jedoch mehr Zeit in Anspruch nehmen, um es einzurichten.

Beide Optionen haben ihre Nachteile, und deshalb rate ich im Allgemeinen, nur das Composition Root im Webprojekt zu belassen. Viele Entwickler möchten nicht, dass ihre MVC-Assembly von der DAL-Assembly abhängt, aber das ist eigentlich kein Problem. Vergessen Sie nicht, dass Assemblys ein Bereitstellungsartefakt sind. Sie teilen Code in mehrere Assemblys auf, damit Code separat bereitgestellt werden kann. Eine architektonische Ebene hingegen ist ein logisches Artefakt. Es ist sehr gut möglich (und üblich), mehrere Ebenen in derselben Baugruppe zu haben.

In diesem Fall befinden sich der Composition Root (Layer) und der Presentation Layer im selben Webanwendungsprojekt (also in derselben Assembly). Und obwohl diese Assembly auf die Assembly verweist, die die DAL enthält, verweist die Präsentationsebene immer noch nicht auf die Datenzugriffsebene . Das ist ein großer Unterschied.

Wenn wir dies tun, verlieren wir natürlich die Fähigkeit des Compilers, diese Architekturregel zur Kompilierungszeit zu überprüfen, aber dies sollte kein Problem sein. Die meisten Architekturregeln können vom Compiler nicht überprüft werden, und es gibt immer so etwas wie gesunden Menschenverstand. Und wenn es in Ihrem Team keinen gesunden Menschenverstand gibt, können Sie immer Code-Reviews verwenden (die IMO von jedem Team auch immer ausführen sollte). Sie können auch ein Tool wie NDepend (kommerziell) verwenden, mit dem Sie Ihre Architekturregeln überprüfen können. Wenn Sie NDepend in Ihren Erstellungsprozess integrieren, kann es Sie warnen, wenn jemand Code überprüft hat, der gegen eine solche Architekturregel verstößt.

Eine ausführlichere Diskussion über die Funktionsweise der Composition Root finden Sie in Kapitel 4 meines Buches Abhängigkeitsinjektion, Prinzipien, Praktiken, Muster .

63
Steven

Wenn ich keinen DI-Container verwenden würde, müsste ich in meiner MVC3-App nicht auf die EntityFramework-Bibliothek verweisen, sondern nur auf meine Business-Schicht, die auf meine DAL/Repo-Schicht verweist.

Sie können ein separates Projekt mit dem Namen "DependencyResolver" erstellen. In diesem Projekt müssen Sie auf alle Ihre Bibliotheken verweisen.

Jetzt benötigt der UI-Layer kein NHibernate/EF oder eine andere nicht UI-relevante Bibliothek mit Ausnahme von Castle Windsor, auf die verwiesen werden soll.

Wenn Sie Castle Windsor und DependencyResolver vor Ihrem UI-Layer verbergen möchten, können Sie ein HttpModule schreiben, das das IoC-Registrierungsmaterial aufruft.

Ich habe nur ein Beispiel für 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);
          ...
    }
 }

Die DefaultControllerFactory verwendet den IoC-Container nicht direkt, sondern delegiert ihn an IoC-Containermethoden.

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;
    }
}

Der GetController -Delegat wird in einer StructureMap-Registrierung festgelegt (in Windsor sollte es sich um ein Installationsprogramm handeln).

5
Rookian
  • Es gibt eine Abhängigkeit: Wenn ein Objekt ein anderes Objekt instanziiert.
  • Es gibt keine Abhängigkeit: Wenn ein Objekt eine Abstraktion erwartet (Konstruktorinjektion, Methodeninjektion ...)
  • Assemblyverweise (Verweise auf DLLs, Webservices usw.) sind unabhängig vom Abhängigkeitskonzept, da die Ebene auf diese verweisen muss, um eine Abstraktion aufzulösen und den Code kompilieren zu können.
0
riadh gomri