Comments 26
Модули Autofac для декомпозиции непригодны, так как не обеспечивают инкапсуляцию.

Модули прекрасно подходят для декомпозиции "каждая отдельная сборка содержит свой модуль, описывающий ее нужды и экспонируемые ей сервисы".

Правда?
Модуль B для собственных нужд зарегистрировал в качестве реализации интерфейса IA класс X.
Потом в ядро была добавлена регистрация класса Y как реализации того же интерфейса IA. Какая из реализаций будет доступна модулям и почему?

Правда.


Если модуль B хочет сам управлять теми реализациями, которые потребляют его компоненты, то он резолвит их на собственном скоупе, никак не завися от того, что определяют в других модулях. Но ситуация, в которой модуль потребляет реализации, определенные где-то еще (общедоступные сервисы), и выставляет свои реализации для чужого потребления (плагины), встречается намного чаще.

Эта ваша реплика


"каждая отдельная сборка содержит свой модуль, описывающий ее нужды и экспонируемые ей сервисы"

никак не сочетается со следующей:


Если модуль B хочет сам управлять теми реализациями, которые потребляют его компоненты, то он резолвит их на собственном скоупе, никак не завися от того, что определяют в других модулях.

Модуль Autofac всего лишь контейнер для регистраций. Собственный скоуп к модулям абсолютно ортогонален.


Но ситуация, в которой модуль потребляет реализации, определенные где-то еще (общедоступные сервисы), и выставляет свои реализации для чужого потребления (плагины), встречается намного чаще.

И модули Autofac здесь нисколько не помогают декомпозиции: реальные зависимости плагинов нельзя определить и разрешить только по его модулю — необходимо знать все регистрации ядра, все регистрации модулей плюс порядок регистрации модулей в ядре. Это полный аналог глобальной переменной.
Кстати, на мой вопрос вы не ответили.

Модуль Autofac всего лишь контейнер для регистраций. Собственный скоуп к модулям абсолютно ортогонален.

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


И модули Autofac здесь нисколько не помогают декомпозиции

Почему же? Я могу зарегистрировать те реализации, которые плагин содержит.


реальные зависимости плагинов нельзя определить и разрешить только по его модулю

Определить — можно. Просто собираем все, что плагин экспонировал, и смотрим, что нужно для разрешения.


Разрешить — нельзя, но и не нужно: разрешение зависимостей происходит в тот момент, когда у нас есть весь composition root, от всего приложения, включая все плагины.


Кстати, на мой вопрос вы не ответили.

На вот этот?


Какая из реализаций будет доступна модулям и почему?

Я счел его риторическим. Но вообще ответ на него есть в документации — та, которая была зарегистрирована последней (если все регистрации немаркированные, разрешение немаркированное, и все регистрации и разрешение делаются на одном скоупе).

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

И как же тогда ваш тезис про декомпозицию через модули Autofac, если вам для нее помимо модулей сразу потребовались внутренние скоупы?


Почему же? Я могу зарегистрировать те реализации, которые плагин содержит.

И где тут декомпозиция?
Для того, чтобы понять зависимости плагина, реализованного как модуль Autofac, вам придется прошерстить все зарегистрированные в нем реализации, вычеркивая по ходу то, что плагин реализовал сам. Разительный контраст с зависимостями класса.
А для того чтобы понять, как эти зависимости будут разрешены — надо просмотреть все ядро и все модули, которые зарегистрированы до вашего. Любое изменение регистрации в ядре и других модулях способно сломать ваш код.
Это не декомпозиция по построению — модулем Autofac нельзя оперировать как единым целым, его "абстракция" не то что дырява, а целиком состоит из одной большой дыры.


Я счел его риторическим. Но вообще ответ на него есть в документации

И этот ответ ставит крест на модулях Autofac как средстве декомпозиции. Ибо знание всех деталей реализации модуля необходимо для корректного конфигурирования Composition Root.
Что характерно, детали реализации бизнес-классов для этого не нужны: достаточно списка интерфейсов и параметров конструктора.

И как же тогда ваш тезис про декомпозицию через модули Autofac, если вам для нее помимо модулей сразу потребовались внутренние скоупы?

Это вам они сразу потребовались. Я без них обходился и продолжаю обходиться.


И где тут декомпозиция?

Там, где каждая сборка сама объявляет, какие сервисы она экспонирует.


Ибо знание всех деталей реализации модуля необходимо для корректного конфигурирования Composition Root.

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

Это вам они сразу потребовались. Я без них обходился и продолжаю обходиться.

То есть вы вручную следите за согласованностью всех регистраций во всех модулях и ядре.


Там, где каждая сборка сама объявляет, какие сервисы она экспонирует.

У модуля Autofac экспонированные сервисы — не интерфейс, а детали реализации.


ограничиваясь соглашением о том, какие сервисы кто предоставляет

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

То есть вы вручную следите за согласованностью всех регистраций во всех модулях и ядре.

Нет, я не делаю для этого никаких действий.


У модуля Autofac экспонированные сервисы — не интерфейс, а детали реализации.

Я не очень понимаю, как вы это разделяете. Могу ли я узнать, какие сервисы зарегистрированы модулем? Могу (согласен, что это не очень просто). Нужно ли мне это… не уверен.


И соблюдение этих соглашений накладывает жесткие требования на реализацию модулей.

Не такие уж и жесткие.


Т.е. сами модули по части декомпозиции вам снова не помогли никак.

Модули мне помогли тем фактом, что они есть, и мне не надо придумывать механизм их обнаружения и регистрации. Когда я работал с Unity, мне приходилось все то же самое делать самостоятельно. А тут я получаю некий — пусть неидеальный, но все же работающий — механизм, к которому мне теперь просто надо применить некие правила.


Если очень грубо, структурные единицы программы тоже никак не помогают вам в реализации SRP, однако если бы их не было вовсе — вы бы не смогли выдержать SRP.

Нет, я не делаю для этого никаких действий.

Это противоречит вашему предыдущему утверждению, что вы установили соглашения для модулей и следите за их соблюдением.


Если очень грубо, структурные единицы программы тоже никак не помогают вам в реализации SRP, однако если бы их не было вовсе — вы бы не смогли выдержать SRP.

Вообще-то помогают: методы скрывают сложность использования локальных переменных и собственный код, классы — всю свою приватную часть, интерфейсы — реализующие их классы, DI-контейнеры — реализацию внедрения зависимостей.
Модули Autofac не делают по этой части ничего от слова совсем.

Это противоречит вашему предыдущему утверждению, что вы установили соглашения для модулей и следите за их соблюдением.

Это не "следите за согласованностью всех регистраций". Я просто определил, какие сервисы в какой сборке могут задаваться. Грубо говоря, сборка с плагином не может задать дефолтный сервис аутентификации, но может добавлять новые. С модулями это не связано, это вообще соглашение о том, как можно расширять систему.


Модули Autofac не делают по этой части ничего от слова совсем.

Модули Autofac скрывают от меня сложность сбора зависимостей из сборки.

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

Т.е. вы заранее воспретили регистрации для одного интерфейса в двух и более разных местах (именованные для упрощения в расчет не берем). Это помогает, но применимо далеко не в каждом проекте.

Если быть точным, я разделил сервисы на единичные и множественные, и для первых действительно запретил множественную регистрацию. И да, это помогает. И да, применимо не всегда, но подавляющее большинство случаев из моей практики покрывало.

А мне повезло нарваться на множественные случаи, где подобных соглашений было или явно недостаточно (требовались разные реализации в разных контекстах), или вообще не удавалось добиться (противоречия между командами).

Это тот случай, когда базовой функциональности Autofac недостаточно, и надо реализовывать что-то свое. Это не означает, что для остальных случаев базовой функциональности недостаточно.

Это тот случай, когда базовой функциональности Autofac недостаточно, и надо реализовывать что-то свое

Так и появился исходный материал для этой статьи. К счастью, своего поверх Autofac потребовалось совсем немного.

Можете привести конкретный пример из практики?
Пока что попахивает нарушением то ли SOLID, то ли DRY

Для примера из практики надо и слишком много контекста, и нарушить NDA.
Вы можете ответить на конкретный вопрос из моего комментария?

Вот только каким образом это противоречит моему тезису о непригодности модулей Autofac для декомпозиции?
Вам для решения потребовалось все что угодно, но не модули. Я утверждаю, что модули эту проблему вообще не решают.

Декомпозиция — разделение целого на части.
В документации написано чётко — A module is a small class that can be used to bundle up a set of related components behind a ‘facade’ to simplify configuration and deployment. Задача модулей — Decrease Configuration Complexity

Модули пригодны для декомпозиции.

Проблема, которую вы описали, решается не декомпозицией.
Декомпозиция не пригодна для решения описанной проблемы.
Задача модулей — Decrease Configuration Complexity

Эту задачу модули не решают. Вы обязаны контролировать все регистрации во всех модулях и ядре, т.е. от использования модулей сложность только возрастает из-за фрагментирования конфигурации.
Проблема, которую я описал, обязана решаться декомпозицией. Ибо декомпозиция — разбиение целого на более простые части с целью сведения решения сложной задачи к решению нескольких простых.
Модули ничего не упрощают. Вы по-прежнему вынуждены учитывать все регистрации во всем дереве модулей.

Вы обязаны контролировать все регистрации во всех модулях

Да, должны. No magic here.

Модули свою задачу решают, жизнь упрощают, важно правильно делить на части.
В своих проектах я знаю что регистрации будут в том же подпроекте где и имплементация этих интерфейсов. Знаю какой файл открыть. И удобнее читать часть конфигурации этого логического контекста, а не простыню из несколько экранов из всех регистраций всего проекта.
И удобнее читать часть конфигурации этого логического контекста, а не простыню из несколько экранов из всех регистраций всего проекта.

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


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

Для такого простого случая хватит второго способа по ссылке. Все гораздо интереснее, если зависимость от ISender не прямая, а транзитивная.
PS: на следующие вопросы в таком тоне ищите ответ сами.

Only those users with full accounts are able to leave comments. Log in, please.