2016-04-03 2 views
0

Я прочитал вопрос Ioc/DI - Why do I have to reference all layers/assemblies in entry application?В каких случаях следует заказывать регистрацию зависимостей?

Таким образом, в растворе Asp.Net MVC5, корень композиции находится в проекте MVC5 (и имеющий узел, отвечающий за внедрение зависимости всех регистраций не имеет смысла).

Внутри этой картинки мне непонятно, какой лучший подход среди следующих.

Подход 1

Конкретные реализации являются public class ... и все регистрации положения централизованы в пределах корня композиции (например, в одном или более файлов в папке CompositionRoot). Проект MVC5 должен ссылаться на все сборки, обеспечивающие привязку хотя бы одной конкретной реализации. Никакая библиотека не ссылается на библиотеку DI. Проект MVC может содержать интерфейсы, которые должны быть связаны без каких-либо недостатков.

подход 2

Конкретные реализации являются internal class .... Каждая библиотека предоставляет локальный обработчик конфигурации DI. Например,

public class DependencyInjectionConfig { 
    public static void Configure(Container container) { 
     //here registration of assembly-provided implementations 
     //... 
    } 
} 

, который должен зарегистрировать свои собственные реализации. Корень композиции запускает регистрацию, вызывая все методы Configure(), всего по одному для каждого проекта. Проект MVC5 должен затем ссылаться на все сборки, обеспечивающие привязку хотя бы одной конкретной реализации. Библиотеки must ссылка библиотека DI. В этом случае проект MVC5 не может содержать интерфейсы (в противном случае была бы круговая ссылка): для привязки общедоступных интерфейсов потребуется сборка ServiceLayer.

подход 3

То же, что подход 2, но локальные модули конфигурации обнаруживаются динамически с помощью сборки отражения (по соглашению?). Поэтому проект MVC5 не ссылается на библиотеки. Проект MVC может содержать и может ссылаться на библиотеки. Библиотеки must ссылка библиотека DI.

Какова наилучшая практика здесь? Есть ли другая возможность?

* EDIT (2016-12-22) * Благодаря полученным ответам, я опубликовал this github project описывая лучшее решение, которое я нашел до сих пор.

+0

Это не очень ясно для меня, что именно вы спрашиваете здесь. Я чувствую, что вам не хватает точки контейнеров IoC/DI. Имея все в CompositionRoot, вы затем полагаетесь на абстракции (интерфейсы/абстрактные классы) и легко вводите другое поведение (через конкретные иммы). – Wjdavis5

+0

Мой вопрос: где вы размещаете свои (тонны) правил конфигурации контейнеров, где интерфейсы и реализации распространяется на решение, состоящее из двадцати проектов? – Marcello

+0

Я думаю, это действительно зависит от вашего проекта. Например, у меня около 50 проектов в sln для очень большого сервиса. Но есть только один exe. Мои правила инициализируются как часть запуска exe. Если вы находитесь в ситуации, когда есть много допустимых точек входа, вы хотите, чтобы ваши правила настраивали @ каждую точку входа.Что не должно быть так сложно, если вы наследуете класс EXE от общего базового класса, который затем будет содержать правила. – Wjdavis5

ответ

2

Обычно мне нравится инкапсулировать эти типы вещей в каждый проект. Так, например, у меня может быть следующее. (Это очень упрощенный пример, и я буду использовать AutoFac в этом примере, но я бы предположил, что все рамки DI имеют что-то вроде следующего).

Общая площадь только для POCOs и интерфейсов.

// MyProject.Data.csproj 
namespace MyProject.Data 
{ 
    public Interface IPersonRepository 
    { 
    Person Get(); 
    } 

    public class Person 
    { 
    } 
} 

Внедрение Хранилища и доступа к данным

// MyProject.Data.EF.csproj 
// This project uses EF to implement that data 
namespace MyProject.Data.EF 
{ 
    // internal, because I don't want anyone to actually create this class 
    internal class PersonRepository : IPersonRepository 
    { 
    Person Get() 
    { // implementation } 
    } 

    public class Registration : Autofac.Module 
    { 
    protected override void Load(ContainerBuilder builder) 
    { 
     builder.Register<PersonRepository>() 
     .As<IPersonRepository>() 
     .IntancePerLifetimeScope(); 
    } 
    } 
} 

Потребитель

// MyPrject.Web.UI.csproj 
// This project requires an IPersonRepository 
namespace MyProject.Web.UI 
{ 
    // Asp.Net MVC Example 
    internal class IoCConfig 
    { 
    public static void Start() 
    { 
     var builder = new ContainerBuilder(); 

     var assemblies = BuildManager.GetReferencedAssemblies() 
     .Cast<Assembly>(); 

     builder.RegisterAssemblyModules(assemblies); 
    } 
    } 
} 

Так зависимостями выглядеть следующим образом:

MyProject.Data.csproj 
- None 

MyProject.Data.EF.csproj 
- MyProject.Data 

MyProject.Web.UI.csproj 
- MyProject.Data 
- MyProject.Data.EF 

В этой установке, Web.UI не может знать что-нибудь abo что зарегистрировано и по какой причине. Он знает только, что проект EF имеет реализации, но не может получить к ним доступ.

Я могу отказаться от EF, чтобы сказать Dapper очень легко, поскольку каждый проект инкапсулирует его собственные реализации и регистрации.

+0

Кажется очень чистым. Любое беспокойство о жизни? Как модуль знает, какое время жизни (экземпляр для веб-запроса vs singleton vs ...) подходит для проекта верхнего уровня (т. Е. MyProject.Web.UI)? Является ли это нарушением ответственности, которую мы должны принять, чтобы заставить все работать? – Marcello

+0

Да, каждый тип должен знать, как долго он должен жить, независимо от Web, Console, Service, Win32. Например, EF ContextDB не должен жить вечно в любом приложении. Если вы заполняете класс конфигурации, он должен быть одиночным, независимо от приложения. –

+0

Если бы я добавлял модульные тесты и имел «InMemoryPersonRepository», как бы я заменил «PersonRepository» для моего «InMemoryPersonRepository»? –

1

Вы обнаружили настоящую проблему. (Можно было бы сказать, что это хорошая проблема есть.) Если приложения ввода A ссылки B, B ссылки C и B и/или C требуют некоторого DI регистрации, что делает A (приложение ввода) ответственность за знание достаточно о деталях B и C для регистрации всех зависимостей.

Решение состоит в том, чтобы иметь отдельную сборку, которая обрабатывает составление всех регистраций для B и C.A указывает, что он содержит всю конфигурацию контейнера, которую A должен использовать B и C.

Преимущества

  • A не знает больше, о B и C, чем она должна
  • Ни A, B, ни C должны быть привязаны к одной конкретной структуры DI как Unity или Виндзор.

Here's an example. Это класс шины событий, который лучше всего работает с контейнером DI. Но для его использования вам не нужно знать все о зависимостях, необходимых для регистрации. Итак, для Виндзора я создал DomainEventFacility. Вы только что позвонили

_container.AddFacility<DomainEventFacility>(); 

и все зависимости зарегистрированы. Единственное, что вы регистрируете, - это обработчики событий.

Тогда, если я хочу использовать одну и ту же библиотеку шины событий с другим контейнером DI, например Unity, я могу просто создать некоторую подобную сборку для обработки той же конфигурации для Unity.

+0

Я полагаю, что я должен «решить», а не «решение». Я уверен, что есть другие подходы. –

+0

Если A ссылается на проект контейнера, то A не может выставлять интерфейсы, иначе для регистрации этих интерфейсов произойдет циклическая зависимость. Или вы полагаетесь на размышления? – Marcello

+0

Проект конфигурации контейнера будет для зависимостей внутри B, о которых A даже не должен знать. «A» не должно подвергаться их воздействию. Это должно скрыть внутреннюю работу «B» и «C» с «A». (Напомните мне, чтобы никогда больше ничего не называть A, B и C.) –

Смежные вопросы