Ioc / DI - Perché devo fare riferimento a tutti i layer / assembly nel punto di ingresso dell'applicazione?


123

(In relazione a questa domanda, EF4: perché la creazione del proxy deve essere abilitata quando è abilitato il caricamento lento? ).

Sono nuovo in DI, quindi abbi pazienza. Comprendo che il contenitore è incaricato di creare un'istanza di tutti i miei tipi registrati, ma per farlo richiede un riferimento a tutte le DLL nella mia soluzione e ai loro riferimenti.

Se non stessi utilizzando un contenitore DI, non avrei dovuto fare riferimento alla libreria EntityFramework nella mia app MVC3, ma solo al mio livello aziendale, che farebbe riferimento al mio livello DAL / Repo.

So che alla fine della giornata tutte le DLL sono incluse nella cartella bin, ma il mio problema è doverle fare riferimento esplicitamente tramite "aggiungi riferimento" in VS per poter pubblicare un WAP con tutti i file necessari.


1
Questo estratto dal libro Dependency Injection in .NET, seconda edizione è una versione più elaborata delle risposte sia di Mark che di me. Descrive in dettaglio il concetto di Composition Root e perché lasciare che il percorso di avvio dell'applicazione dipenda da ogni altro modulo è effettivamente una buona cosa.
Steven

Ho letto quel link estratto e il capitolo 1, acquisterò il libro perché mi sono davvero piaciute le analogie e le semplici spiegazioni alla complessa questione della DI. Penso che dovresti suggerire una nuova risposta, rispondi chiaramente "non devi fare riferimento a tutti i livelli / assiemi nel livello logico di ingresso a meno che non sia anche la radice della tua composizione", collega l'estratto e pubblica l'immagine Figura 3, dal estratto.
diegohb

Risposte:


194

Se non stessi utilizzando un contenitore DI, non avrei dovuto fare riferimento alla libreria EntityFramework nella mia app MVC3, solo il mio livello aziendale che farebbe riferimento al mio livello DAL / Repo.

Sì, è esattamente la situazione che DI lavora così duramente per evitare :)

Con codice strettamente accoppiato, ogni libreria può avere solo pochi riferimenti, ma anche questi hanno altri riferimenti, creando un grafico profondo delle dipendenze, come questo:

Grafico profondo

Poiché il grafico delle dipendenze è profondo, significa che la maggior parte biblioteche trascinano molte altre dipendenze - ad esempio nel diagramma, biblioteca C trascina libreria H, biblioteca E, biblioteca J, biblioteca M, biblioteca K e biblioteca N . Ciò rende più difficile riutilizzare ogni libreria indipendentemente dal resto, ad esempio nei test di unità .

Tuttavia, in un'applicazione debolmente accoppiata, spostando tutti i riferimenti alla radice della composizione , il grafico delle dipendenze viene notevolmente appiattito :

Grafico superficiale

Come illustrato dal colore verde, ora è possibile riutilizzare la Libreria C senza trascinare dipendenze indesiderate.

Tuttavia, tutto ciò detto, con molti contenitori DI, non è necessario aggiungere riferimenti a tutte le librerie richieste. È invece possibile utilizzare l' associazione tardiva sotto forma di scansione dell'assembly basata su convenzione (preferita) o configurazione XML.

Quando lo fai, tuttavia, devi ricordarti di copiare gli assembly nella cartella bin dell'applicazione, perché ciò non avviene più automaticamente. Personalmente, trovo raramente che valga questo sforzo extra.

Una versione più elaborata di questa risposta può essere trovata in questo estratto dal mio libro Dependency Injection, Principles, Practices, Patterns .


3
Grazie mille, ora ha perfettamente senso .. Avevo bisogno di sapere se questo era di progettazione. Per quanto riguarda l'applicazione del corretto utilizzo delle dipendenze, avevo implementato un progetto separato con il mio bootstrapper DI come Steven menzionato di seguito, dove faccio riferimento al resto delle librerie. A questo progetto fa riferimento l'app del punto di ingresso e alla fine della compilazione completa, tutte le DLL necessarie si trovano nella cartella bin. Grazie!
diegohb

2
@Mark Seemann Questa domanda / risposta è specifica per Microsoft? Vorrei sapere se questa idea di spostare tutte le dipendenze nel "punto di ingresso dell'applicazione" ha senso per un progetto Java EE / Spring utilizzando Maven… grazie!
Grégoire C

5
Questa risposta si applica oltre .NET. Puoi fare riferimento al capitolo Principles of Package Design di Robert C.Martin,
Mark Seemann

7
@AndyDangerGagne The Composition Root è un pattern DI, l' opposto di Service Locator . Dal punto di vista della radice della composizione, nessuno dei tipi è polimorfico; la radice della composizione vede tutti i tipi come tipi concreti e, quindi, il principio di sostituzione di Liskov non si applica ad esso.
Mark Seemann

4
Come regola generale, le interfacce dovrebbero essere definite dai client che le utilizzano ( APP, cap. 11 ), quindi se la libreria J necessita di un'interfaccia, dovrebbe essere definita nella libreria J. Questo è un corollario del principio di inversione delle dipendenze.
Mark Seemann

65

Se non stessi utilizzando un contenitore DI, non avrei dovuto fare riferimento alla libreria EntityFramework nella mia app MVC3

Anche quando si utilizza un contenitore DI, non è necessario lasciare che il progetto MVC3 faccia riferimento a EF, ma si sceglie (implicitamente) di farlo implementando la radice della composizione (il percorso di avvio in cui si compongono i grafici degli oggetti) all'interno del progetto MVC3. Se sei molto severo sulla protezione dei tuoi confini architettonici utilizzando gli assembly, puoi spostare la logica di presentazione in un progetto diverso.

Quando si sposta tutta la logica correlata a MVC (controller, ecc.) Dal progetto di avvio a una libreria di classi, si consente a questo assembly del livello di presentazione di rimanere disconnesso dal resto dell'applicazione. Il progetto dell'applicazione web stesso diventerà un guscio molto sottile con la logica di avvio richiesta. Il progetto dell'applicazione Web sarà la radice della composizione che fa riferimento a tutti gli altri assembly.

L'estrazione della logica di presentazione in una libreria di classi può complicare le cose quando si lavora con MVC. Sarà più difficile collegare tutto, poiché i controller non sono nel progetto di avvio (mentre viste, immagini, file css, devono probabilmente rimanere nel progetto di avvio). Questo è probabilmente fattibile ma richiederà più tempo per la configurazione.

A causa degli svantaggi, in genere consiglio di mantenere la radice della composizione nel progetto web. Molti sviluppatori non vogliono che il loro assembly MVC dipenda dall'assembly DAL, ma non è davvero un problema. Non dimenticare che gli assembly sono un artefatto di distribuzione ; si divide il codice in più assembly per consentire la distribuzione separata del codice. Uno strato architettonico d'altra parte è un artefatto logico . È molto possibile (e comune) avere più livelli nello stesso assieme.

In questo caso finiremo per avere la radice della composizione (livello) e il livello di presentazione nello stesso progetto di applicazione web (quindi nello stesso assieme). E anche se quell'assembly fa riferimento all'assembly contenente il DAL, il livello di presentazione non fa ancora riferimento al livello di accesso ai dati . Questa è una grande distinzione.

Ovviamente, quando lo facciamo, perdiamo la capacità del compilatore di controllare questa regola architettonica in fase di compilazione, ma questo non dovrebbe essere un problema. La maggior parte delle regole architetturali in realtà non può essere verificata dal compilatore e c'è sempre qualcosa come il buon senso. E se non c'è buon senso nel tuo team, puoi sempre usare le revisioni del codice (che ogni team dovrebbe fare sempre IMO). Puoi anche utilizzare uno strumento come NDepend (che è commerciale), che ti aiuta a verificare le tue regole architetturali. Quando si integra NDepend con il processo di compilazione, può avvisarti quando qualcuno ha controllato il codice che viola tale regola architettonica.

Puoi leggere una discussione più elaborata su come funziona la radice della composizione nel capitolo 4 del mio libro Dependency Injection, Principles, Practices, Patterns .


Un progetto separato per il bootstrap è stata la mia soluzione poiché non abbiamo ndepend e non l'ho mai usato prima. Lo esaminerò però poiché sembra un modo migliore per realizzare ciò che sto cercando di fare quando ci sarà solo 1 applicazione finale.
diegohb

1
L'ultimo paragrafo è fantastico e sta iniziando ad aiutarmi a cambiare idea su quanto sono severo nel mantenere i livelli in assiemi separati. Avere due o più livelli logici in un assembly va effettivamente bene se si utilizzano altri processi attorno alla scrittura del codice (come le revisioni del codice) per assicurarsi che non vi siano riferimenti alle classi DAL nel codice dell'interfaccia utente e viceversa.
BenM

6

Se non stessi utilizzando un contenitore DI, non avrei dovuto fare riferimento alla libreria EntityFramework nella mia app MVC3, solo il mio livello aziendale che farebbe riferimento al mio livello DAL / Repo.

È possibile creare un progetto separato chiamato "DependencyResolver". In questo progetto devi fare riferimento a tutte le tue librerie.

Ora il livello dell'interfaccia utente non necessita di NHibernate / EF o di qualsiasi altra libreria non pertinente per l'interfaccia utente ad eccezione di Castle Windsor per essere referenziato.

Se si desidera nascondere Castle Windsor e DependencyResolver dal livello dell'interfaccia utente, è possibile scrivere un HttpModule che richiami gli elementi del registro di IoC.

Ho solo un esempio per 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 non usa direttamente il contenitore IoC, ma delega ai metodi del contenitore 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;
    }
}

Il GetControllerdelegato è impostato in un registro StructureMap (in Windsor dovrebbe essere un programma di installazione).


1
mi piace anche meglio di quello che ho finito per fare, i moduli sono fantastici. quindi dove dovrei fare la chiamata a Container.Dispose ()? Evento ApplicationEnd o EndRequest all'interno del modulo ...?
diegohb

1
@Steven Perché Global.asax è nel tuo livello dell'interfaccia utente MVC. HttpModule sarebbe nel progetto DependencyResolver.
Rookian

1
Il piccolo vantaggio è che nessuno può utilizzare il contenitore IoC nell'interfaccia utente. Cioè nessuno è in grado di utilizzare il contenitore IoC come localizzatore di servizi nell'interfaccia utente.
Rookian

1
Inoltre, impedisce agli sviluppatori di utilizzare accidentalmente il codice DAL nel livello dell'interfaccia utente poiché non vi è alcun riferimento reale all'assembly nell'interfaccia utente.
diegohb

1
Ho capito come fare la stessa cosa usando l'API di registrazione generica di Bootstrapper. Il mio progetto UI fa riferimento a Bootstrapper, il progetto di risoluzione delle dipendenze in cui collego le mie registrazioni e progetti nel mio Core (per le interfacce) ma nient'altro, nemmeno il mio DI Framework (SimpleInjector). Sto usando OutputTo nuget per copiare dll nella cartella bin.
diegohb

0
  • C'è una dipendenza: se un oggetto crea un'istanza di un altro oggetto.
  • Non c'è dipendenza: se un oggetto si aspetta un'astrazione (iniezione del costruttore, iniezione del metodo ...)
  • I riferimenti ad assembly (che fanno riferimento a dll, webservices ..) sono indipendenti dal concetto di dipendenza, perché per risolvere un'astrazione ed essere in grado di compilare il codice, il livello deve fare riferimento ad essa.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.