Ioc / DI - Pourquoi dois-je référencer toutes les couches / assemblages dans le point d'entrée de l'application?

123

(Lié à cette question, EF4: pourquoi la création de proxy doit-elle être activée lorsque le chargement différé est activé? ).

Je suis nouveau dans DI, alors soyez patient avec moi. Je comprends que le conteneur est chargé d'instancier tous mes types enregistrés, mais pour ce faire, il nécessite une référence à toutes les DLL de ma solution et à leurs références.

Si je n'utilisais pas de conteneur DI, je n'aurais pas à référencer la bibliothèque EntityFramework dans mon application MVC3, uniquement ma couche métier, qui ferait référence à ma couche DAL / Repo.

Je sais qu'en fin de compte, toutes les DLL sont incluses dans le dossier bin mais mon problème est de devoir le référencer explicitement via "ajouter une référence" dans VS afin de pouvoir publier un WAP avec tous les fichiers nécessaires.

diegohb
la source
1
Cet extrait du livre Dependency Injection in .NET, deuxième édition est une version plus élaborée des réponses de Mark et de moi-même. Il décrit en détail le concept de la racine de composition et pourquoi laisser le chemin de démarrage de l'application dépendre de tous les autres modules est en fait une bonne chose.
Steven
J'ai lu cet extrait de lien et le chapitre 1, je vais acheter le livre car j'ai vraiment apprécié les analogies et les explications simples à la question complexe de la DI. Je pense que vous devriez suggérer une nouvelle réponse, répondre clairement "vous n'avez pas à référencer tous les calques / assemblages dans la couche logique d'entrée à moins qu'il ne s'agisse également de votre racine de composition", créez un lien vers l'extrait et publiez l'image Figure 3, du extrait.
diegohb

Réponses:

194

Si je n'utilisais pas de conteneur DI, je n'aurais pas à référencer la bibliothèque EntityFramework dans mon application MVC3, uniquement ma couche métier qui ferait référence à ma couche DAL / Repo.

Oui, c'est exactement la situation que DI s'efforce d'éviter :)

Avec un code étroitement couplé, chaque bibliothèque peut n'avoir que quelques références, mais celles-ci ont à nouveau d'autres références, créant un graphe profond de dépendances, comme ceci:

Graphique profond

Du fait que le graphe de dépendance est profond, cela signifie que la plupart des bibliothèques glisser le long d' un grand nombre d'autres dépendances - par exemple , dans le diagramme, la bibliothèque C traîne Bibliothèque H, E Library, Bibliothèque J, M Library, Bibliothèque K et Bibliothèque N . Cela rend plus difficile la réutilisation de chaque bibliothèque indépendamment du reste - par exemple dans les tests unitaires .

Cependant, dans une application faiblement couplée, en déplaçant toutes les références vers la racine de composition , le graphe de dépendance est fortement aplati :

Graphique peu profond

Comme l'illustre la couleur verte, il est maintenant possible de réutiliser la bibliothèque C sans faire glisser les dépendances indésirables.

Cependant, tout ce que dit, avec de nombreux conteneurs DI, vous n'avez d'ajouter des références difficiles à toutes les bibliothèques requises. Au lieu de cela, vous pouvez utiliser une liaison tardive sous la forme d'une analyse d'assemblage basée sur des conventions (préférée) ou d'une configuration XML.

Lorsque vous faites cela, cependant, vous devez vous rappeler de copier les assemblys dans le dossier bin de l'application, car cela ne se produit plus automatiquement. Personnellement, je trouve rarement que cela vaut la peine.

Une version plus élaborée de cette réponse peut être trouvée dans cet extrait de mon livre Dependency Injection, Principles, Practices, Patterns .

Mark Seemann
la source
3
Merci beaucoup, cela a maintenant un sens parfait. J'avais besoin de savoir si c'était par conception. En ce qui concerne l'application correcte des dépendances, j'avais implémenté un projet séparé avec mon bootstrapper DI comme Steven mentionné ci-dessous où je référence le reste des bibliothèques. Ce projet est référencé par l'application de point d'entrée et à la fin de la version complète, toutes les dll nécessaires se trouvent dans le dossier bin. Merci!
diegohb
2
@Mark Seemann Cette question / réponse est-elle spécifique à Microsoft? Je voudrais savoir si cette idée de déplacer toutes les dépendances vers le «point d'entrée de l'application» a du sens pour un projet Java EE / Spring utilisant Maven… merci!
Grégoire C du
5
Cette réponse s'applique au-delà de .NET. Vous voudrez peut-être vous référer au chapitre sur les principes de conception de package de Robert C. Martin dans, par exemple , Développement de logiciel Agile, principes, modèles et pratiques
Mark Seemann
7
@AndyDangerGagne La racine de composition est un modèle DI - l' opposé de Service Locator . Du point de vue de la racine de composition, aucun des types n'est polymorphe; la racine de composition voit tous les types comme des types concrets, et par conséquent, le principe de substitution de Liskov ne s'applique pas à elle.
Mark Seemann le
4
En règle générale, les interfaces doivent être définies par les clients qui les utilisent ( APP, ch. 11 ), donc si la bibliothèque J a besoin d'une interface, elle doit être définie dans la bibliothèque J. C'est un corollaire du principe d'inversion de dépendance.
Mark Seemann
65

Si je n'utilisais pas de conteneur DI, je n'aurais pas à référencer la bibliothèque EntityFramework dans mon application MVC3

Même lorsque vous utilisez un conteneur DI, vous n'avez pas à laisser votre projet MVC3 référencer EF, mais vous choisissez (implicitement) de le faire en implémentant la racine de composition (le chemin de démarrage où vous composez vos graphiques d'objets) dans votre projet MVC3. Si vous êtes très strict sur la protection de vos limites architecturales à l'aide d'assemblages, vous pouvez déplacer votre logique de présentation vers un autre projet.

Lorsque vous déplacez toute la logique liée à MVC (contrôleurs, etc.) du projet de démarrage vers une bibliothèque de classes, cela permet à cet assemblage de couche de présentation de rester déconnecté du reste de l'application. Votre projet d'application Web lui-même deviendra une coque très fine avec la logique de démarrage requise. Le projet d'application Web sera la racine de composition qui référence tous les autres assemblys.

L'extraction de la logique de présentation dans une bibliothèque de classes peut compliquer les choses lorsque vous travaillez avec MVC. Il sera plus difficile de tout câbler, car les contrôleurs ne sont pas dans le projet de démarrage (alors que les vues, les images, les fichiers css doivent probablement rester dans le projet de démarrage). Ceci est probablement faisable mais prendra plus de temps à mettre en place.

En raison des inconvénients, je conseille généralement de conserver simplement la racine de composition dans le projet Web. De nombreux développeurs ne veulent pas que leur assembly MVC dépende de l'assembly DAL, mais ce n'est pas vraiment un problème. N'oubliez pas que les assemblys sont un artefact de déploiement ; vous divisez le code en plusieurs assemblys pour permettre au code d'être déployé séparément. Une couche architecturale, en revanche, est un artefact logique . Il est très bien possible (et courant) d'avoir plusieurs couches dans le même assemblage.

Dans ce cas, nous finirons par avoir la racine de composition (couche) et la couche de présentation dans le même projet d'application Web (donc dans le même assemblage). Et même si cet assemblage fait référence à l'assemblage contenant la DAL, la couche de présentation ne fait toujours pas référence à la couche d' accès aux données . C'est une grande distinction.

Bien sûr, lorsque nous faisons cela, nous perdons la possibilité pour le compilateur de vérifier cette règle architecturale au moment de la compilation, mais cela ne devrait pas être un problème. La plupart des règles architecturales ne peuvent en fait pas être vérifiées par le compilateur et il y a toujours quelque chose comme le bon sens. Et s'il n'y a pas de bon sens dans votre équipe, vous pouvez toujours utiliser des révisions de code (ce que chaque équipe devrait toujours faire à l'OMI). Vous pouvez également utiliser un outil tel que NDepend (qui est commercial), qui vous aide à vérifier vos règles architecturales. Lorsque vous intégrez NDepend à votre processus de construction, il peut vous avertir lorsque quelqu'un a archivé du code qui enfreint une telle règle architecturale.

Vous pouvez lire une discussion plus élaborée sur le fonctionnement de la racine de composition dans le chapitre 4 de mon livre Injection de dépendances, principes, pratiques, modèles .

Steven
la source
Un projet séparé pour le bootstrap était ma solution car nous n'avons pas ndepend et je ne l'ai jamais utilisé auparavant. Je vais cependant l'examiner car cela semble être une meilleure façon d'accomplir ce que j'essaie de faire quand il n'y aura qu'une seule application finale.
diegohb
1
Le dernier paragraphe est génial et commence à m'aider à changer d'avis sur la façon dont je suis strict avec le maintien des couches dans des assemblages séparés. Avoir deux ou plusieurs couches logiques dans un assemblage est en fait très bien si vous utilisez d'autres processus autour de l'écriture de code (tels que les révisions de code) pour vous assurer qu'il n'y a pas de référence aux classes DAL dans votre code d'interface utilisateur et vice versa.
BenM
6

Si je n'utilisais pas de conteneur DI, je n'aurais pas à référencer la bibliothèque EntityFramework dans mon application MVC3, uniquement ma couche métier qui ferait référence à ma couche DAL / Repo.

Vous pouvez créer un projet séparé appelé "DependencyResolver". Dans ce projet, vous devez référencer toutes vos bibliothèques.

Désormais, la couche d'interface utilisateur n'a pas besoin de NHibernate / EF ou de toute autre bibliothèque non pertinente à l'interface utilisateur, à l'exception de Castle Windsor, pour être référencée.

Si vous souhaitez masquer Castle Windsor et DependencyResolver de votre couche d'interface utilisateur, vous pouvez écrire un HttpModule qui appelle le registre IoC.

Je n'ai qu'un exemple pour 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 n'utilise pas directement le conteneur IoC, mais il délègue aux méthodes de conteneur 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;
    }
}

Le GetControllerdélégué est défini dans un registre StructureMap (à Windsor, il devrait s'agir d'un programme d'installation).

Recrue
la source
1
j'aime ça encore mieux que ce que j'ai fini par faire, les modules sont super. alors où puis-je appeler Container.Dispose ()? Evénement ApplicationEnd ou EndRequest dans le module ...?
diegohb
1
@Steven Parce que Global.asax est dans votre couche d'interface utilisateur MVC. Le HttpModule serait dans le projet DependencyResolver.
Rookian
1
Le petit avantage est que personne ne peut utiliser le conteneur IoC dans l'interface utilisateur. C'est-à-dire que personne ne peut utiliser le conteneur IoC comme localisateur de services dans l'interface utilisateur.
Rookian
1
En outre, il interdit aux développeurs d'utiliser accidentellement le code DAL dans la couche d'interface utilisateur car il n'y a pas de référence ferme à l'assembly dans l'interface utilisateur.
diegohb
1
J'ai compris comment faire la même chose en utilisant l'API d'inscription générique de Bootstrapper. Mon projet d'interface utilisateur fait référence à Bootstrapper, le projet de résolution de dépendances où je connecte mes enregistrements, et des projets dans mon Core (pour les interfaces) mais rien d'autre, pas même mon Framework DI (SimpleInjector). J'utilise OutputTo nuget pour copier des dll dans le dossier bin.
diegohb du
0
  • Il existe une dépendance: si un objet instancie un autre objet.
  • Il n'y a pas de dépendance: si un objet attend une abstraction (injection de contructeur, injection de méthode ...)
  • Les références d'assembly (référençant dll, webservices ..) sont indépendantes du concept de dépendance, car pour résoudre une abstraction et pouvoir compiler le code, la couche doit la référencer.
riadh gomri
la source