Ioc / DI - Почему я должен ссылаться на все слои / сборки в точке входа приложения?

123

(В связи с этим вопросом EF4: почему необходимо включить создание прокси, когда включена отложенная загрузка? ).

Я новичок в DI, так что терпите меня. Я понимаю, что контейнер отвечает за создание экземпляров всех моих зарегистрированных типов, но для этого ему требуется ссылка на все библиотеки DLL в моем решении и их ссылки.

Если бы я не использовал контейнер DI, мне бы не пришлось ссылаться на библиотеку EntityFramework в моем приложении MVC3, только на мой бизнес-уровень, который будет ссылаться на мой уровень DAL / Repo.

Я знаю, что в конце дня все библиотеки DLL включены в папку bin, но моя проблема заключается в том, чтобы ссылаться на нее явно через «добавить ссылку» в VS, чтобы иметь возможность опубликовать WAP со всеми необходимыми файлами.

diegohb
источник
1
Этот отрывок из книги « Внедрение зависимостей в .NET, второе издание» представляет собой более подробную версию ответов как Марка, так и меня. В нем подробно описывается концепция Composition Root и почему разрешение пути запуска приложения зависеть от каждого другого модуля - это на самом деле хорошо.
Стивен
Я прочитал ссылку на отрывок и главу 1, я куплю книгу, так как мне очень понравились аналогии и простые объяснения сложного вопроса DI. Я думаю, вам следует предложить новый ответ, четко ответить «вам не нужно ссылаться на все слои / сборки в логическом уровне входа, если только он не является корнем вашей композиции», ссылку на отрывок и опубликуйте изображение на рис. 3 из выдержка.
diegohb

Ответы:

194

Если бы я не использовал контейнер DI, мне бы не пришлось ссылаться на библиотеку EntityFramework в моем приложении MVC3, только на мой бизнес-уровень, который будет ссылаться на мой уровень DAL / Repo.

Да, это именно та ситуация, которую DI старается избежать :)

При сильно связанном коде каждая библиотека может иметь только несколько ссылок, но у них снова есть другие ссылки, создающие глубокий граф зависимостей, например:

Глубокий график

Поскольку граф зависимостей глубоки, это означает , что большинство библиотек перетащить на много других зависимости - например , на схеме, библиотека С тянется вдоль библиотеки Н, Е, библиотеки библиотеки J, M, Библиотеки Библиотеки K и библиотек N . Это затрудняет повторное использование каждой библиотеки независимо от остальных - например, при модульном тестировании .

Тем не менее, в слабосвязанном приложении, перемещая все ссылки на композиции Root , то граф зависимостей сильно уплощенный :

Мелкий график

Как показано зеленым цветом, теперь можно повторно использовать библиотеку C без перетаскивания каких-либо нежелательных зависимостей.

Однако, несмотря на все вышесказанное, для многих контейнеров DI вам не нужно добавлять жесткие ссылки на все необходимые библиотеки. Вместо этого вы можете использовать позднее связывание в форме сканирования сборки на основе соглашений (предпочтительно) или конфигурации XML.

Однако при этом не забудьте скопировать сборки в папку bin приложения, потому что это больше не происходит автоматически. Лично я редко считаю, что это стоит дополнительных усилий.

Более подробную версию этого ответа можно найти в этом отрывке из моей книги « Внедрение зависимостей, принципы, практики, шаблоны» .

Марк Симанн
источник
3
Большое спасибо, теперь это имеет смысл .. Мне нужно было знать, было ли это задумано. Что касается обеспечения правильного использования зависимостей, я реализовал отдельный проект с моим загрузчиком DI, как Стивен, упомянутый ниже, где я ссылаюсь на остальные библиотеки. На этот проект ссылается приложение точки входа, и в конце полной сборки все необходимые библиотеки DLL оказываются в папке bin. Спасибо!
diegohb 01
2
@Mark Seemann Этот вопрос / ответ относится к Microsoft? Я хотел бы знать, имеет ли смысл идея перемещения всех зависимостей в «точку входа приложения» для проекта Java EE / Spring с использованием Maven… спасибо!
Grégoire C
5
Этот ответ применим не только к .NET. Вы можете сослаться на главу Роберта К. Мартина « Принципы дизайна пакетов» в, например, « Гибкая разработка программного обеспечения, принципы, шаблоны и практики»
Марк Симанн,
7
@AndyDangerGagne Корень композиции - это шаблон DI, противоположный локатору служб . С точки зрения Composition Root ни один из типов не является полиморфным; Композиционный корень рассматривает все типы как конкретные типы, и поэтому принцип замещения Лискова к нему не применяется.
Mark Seemann
4
Как правило, интерфейсы должны определяться клиентами, использующими их ( APP, гл. 11 ), поэтому, если библиотеке J требуется интерфейс, он должен быть определен в библиотеке J. Это следствие принципа инверсии зависимостей.
Марк Земанн
65

Если бы я не использовал контейнер DI, мне бы не пришлось ссылаться на библиотеку EntityFramework в моем приложении MVC3.

Даже при использовании контейнера DI вам не нужно позволять вашему проекту MVC3 ссылаться на EF, но вы (неявно) решите сделать это, реализовав Composition Root (путь запуска, по которому вы составляете свои графы объектов) внутри вашего проекта MVC3. Если вы очень строго относитесь к защите архитектурных границ с помощью сборок, вы можете перенести логику представления в другой проект.

Когда вы перемещаете всю логику, связанную с MVC (контроллеры и т. Д.) Из запускаемого проекта в библиотеку классов, это позволяет этой сборке уровня представления оставаться отключенной от остальной части приложения. Сам проект вашего веб-приложения станет очень тонкой оболочкой с необходимой логикой запуска. Проект веб-приложения будет корнем композиции, который ссылается на все другие сборки.

Извлечение логики представления в библиотеку классов может усложнить работу с MVC. Будет сложнее подключить все, поскольку контроллеры не входят в запускаемый проект (в то время как представления, изображения, файлы css, скорее всего, должны оставаться в запускаемом проекте). Вероятно, это выполнимо, но для настройки потребуется больше времени.

Из-за недостатков я обычно советую просто оставить корневой каталог композиции в веб-проекте. Многие разработчики не хотят, чтобы их сборка MVC зависела от сборки DAL, но это не проблема. Не забывайте, что сборки - это артефакт развертывания ; вы разделяете код на несколько сборок, чтобы можно было развертывать код отдельно. С другой стороны, архитектурный слой - это логический артефакт. Вполне возможно (и часто) иметь несколько слоев в одной сборке.

В этом случае у нас будет корень композиции (слой) и уровень презентации в одном проекте веб-приложения (то есть в одной сборке). И несмотря на то, что сборка ссылок сборки , содержащие DAL, Сретение слой еще не ссылается на Access Data Layer . Это большое различие.

Конечно, когда мы делаем это, мы теряем возможность компилятора проверять это архитектурное правило во время компиляции, но это не должно быть проблемой. Большинство архитектурных правил фактически не могут быть проверены компилятором, и всегда есть что-то вроде здравого смысла. И если в вашей команде нет здравого смысла, вы всегда можете использовать обзоры кода (которые, кстати, должна делать каждая команда, IMO). Вы также можете использовать такой инструмент, как NDepend (который является коммерческим), который поможет вам проверить ваши архитектурные правила. Когда вы интегрируете NDepend в процесс сборки, он может предупреждать вас, когда кто-то проверил код, нарушающий такое архитектурное правило.

Вы можете прочитать более подробное обсуждение того, как работает корень композиции, в главе 4 моей книги « Внедрение зависимостей, принципы, практики, шаблоны» .

Стивен
источник
Моим решением был отдельный проект для начальной загрузки, поскольку у нас нет ndepend, и я никогда не использовал его раньше. Я изучу это, поскольку это звучит как лучший способ выполнить то, что я пытаюсь сделать, когда будет только одно конечное приложение.
diegohb 01
1
Последний абзац отличный и помогает мне изменить свое мнение о том, насколько я строг к хранению слоев в отдельных сборках. Наличие двух или более логических слоев в одной сборке на самом деле нормально, если вы используете другие процессы, связанные с написанием кода (например, обзоры кода), чтобы гарантировать отсутствие ссылок на классы DAL в вашем коде пользовательского интерфейса и наоборот.
BenM
6

Если бы я не использовал контейнер DI, мне бы не пришлось ссылаться на библиотеку EntityFramework в моем приложении MVC3, только на мой бизнес-уровень, который будет ссылаться на мой уровень DAL / Repo.

Вы можете создать отдельный проект под названием «DependencyResolver». В этом проекте вы должны указать все свои библиотеки.

Теперь слой пользовательского интерфейса не требует ссылки на NHibernate / EF или любую другую библиотеку, не относящуюся к пользовательскому интерфейсу, за исключением Castle Windsor.

Если вы хотите скрыть Castle Windsor и DependencyResolver со своего уровня пользовательского интерфейса, вы можете написать HttpModule, который вызывает материал реестра IoC.

У меня есть только пример для 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 не использует контейнер IoC напрямую, но делегирует его методам контейнера 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;
    }
}

GetControllerДелегат устанавливается в StructureMap реестре (в Виндзоре он должен быть Installer).

Rookian
источник
1
мне это нравится даже больше, чем то, чем я закончил, модули великолепны. так где же мне позвонить в Container.Dispose ()? Событие ApplicationEnd или EndRequest в модуле ...?
diegohb 01
1
@Steven Потому что Global.asax находится на вашем уровне пользовательского интерфейса MVC. HttpModule будет в проекте DependencyResolver.
Rookian 01
1
Небольшое преимущество в том, что никто не может использовать контейнер IoC в пользовательском интерфейсе. Т.е. никто не может использовать контейнер IoC в качестве локатора службы в пользовательском интерфейсе.
Rookian 01
1
Кроме того, он запрещает разработчикам случайно использовать код DAL на уровне пользовательского интерфейса, поскольку в пользовательском интерфейсе нет жесткой ссылки на сборку.
diegohb 01
1
Я понял, как сделать то же самое, используя общий API регистрации Bootstrapper. Мой проект пользовательского интерфейса ссылается на Bootstrapper, проект разрешения зависимостей, в котором я подключаю свои регистрации, и проекты в моем Core (для интерфейсов), но ни на что другое, даже на мою DI Framework (SimpleInjector). Я использую OutputTo nuget для копирования DLL в папку bin.
diegohb
0
  • Существует зависимость: если объект создает экземпляр другого объекта.
  • Нет зависимости: если объект ожидает абстракцию (внедрение конструктора, внедрение метода ...)
  • Ссылки на сборки (ссылки на dll, веб-службы ..) не зависят от концепции зависимости, потому что для разрешения абстракции и возможности компилировать код уровень должен ссылаться на нее.
Риад Гомри
источник