Возможен ли тестируемый луковый код в трех- или многоуровневой архитектуре без использования контейнера IoC?

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

Однако, если объекты на каждом уровне (особенно бизнес) структурированы так, чтобы их можно было тестировать, их зависимости определяются как часть их публичных контрактов (при тестировании объекта 2-го уровня с зависимостью объекта 3-го уровня имитируйте/заглушайте 3-й уровень). -уровня и предоставить его объекту 2-го уровня). Это означает, что во время реализации первый уровень отвечает за захват зависимости третьего уровня для использования при построении объекта второго уровня. Я не против этого, если это очень мягкий объект, но если это компонент доступа к данным, который требует, например, строки подключения, первый уровень не должен нести за это ответственность. Кроме того, чем больше у вас зависимостей, тем больше верхний уровень отвечает за создание экземпляров и передачу всех зависимостей каждого объекта в ломтике луковицы, который он использует.

Единственный способ, которым я когда-либо видел эту проблему, — это использование IoC, но я работаю в ситуации, когда этот вариант исключен. Существуют ли какие-либо другие способы структурировать код, чтобы его можно было тестировать, но не сталкиваться с проблемой создания/предоставления зависимостей для каждого уровня на верхнем уровне? Я должен упомянуть, что я работаю в веб-приложении.

(Я просмотрел эту публикацию, чтобы освежить в памяти "правила".)

РЕДАКТИРОВАТЬ: я думаю, что могу суммировать проблему как таковую: без использования какого-либо контейнера IoC или загрузчика, есть ли способ структурировать код, чтобы его можно было тестировать, который не нарушает принцип глубины зависимости, который заключается в том, что каждый слой в лук может ссылаться только на слой под ним?


person moarboilerplate    schedule 25.07.2014    source источник


Ответы (2)


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

Пример в .NET:

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

Кстати. Вопрос должен быть о контейнере IoC. Не о самом IoC. IoC — это подход к управлению внутренней логикой извне, что достигается путем внедрения зависимостей. Это основной подход для легко тестируемых приложений. Контейнер IoC является частью фреймворка, который строит для вас иерархию зависимостей.

person Ladislav Mrnka    schedule 25.07.2014
comment
Спасибо за ответ. Поскольку я создаю только один набор страниц в устаревшем веб-приложении, я ищу что-то ненавязчивое, что не нужно было бы применять ко всему сайту, из-за чего команда не решается реализовать контейнер. Я мог бы применить загрузчик/контейнер к одному набору страниц, но это подразумевало бы архитектурное решение, которое возложило бы ответственность на остальную часть команды. Я просто не думаю, что мы готовы к таким глобальным изменениям. - person moarboilerplate; 25.07.2014
comment
Я также отредактировал вопрос, чтобы уточнить, что я говорю о контейнере IoC, а не об общей концепции IoC. Спасибо. - person moarboilerplate; 25.07.2014
comment
Разве не единственным архитектурным изменением является инициализация контейнера при запуске приложения? Большинство ваших объектов в веб-приложении в любом случае будут создаваться по запросу и только для тех страниц, где вы это запрашиваете. - person Ladislav Mrnka; 25.07.2014
comment
+1. Я бы добавил, что загрузчику (вероятно) потребуется составить полный граф зависимостей и, следовательно, ему потребуется знание всех сборок, которые необходимо подключить. На самом деле это верно и для использования контейнера IOC, но контейнер может сделать это с помощью позднего связывания, что делает необязательными жесткие ссылки на другие сборки. - person Phil Sandler; 25.07.2014
comment
На самом деле поздняя привязка возможна и без контейнера, но это больше похоже на PITA. - person Phil Sandler; 25.07.2014
comment
Если я не использую загрузчик, и в этом случае вместо его самостоятельного развертывания я бы использовал контейнер IoC, когда код структурирован в соответствии с принципами инверсии зависимостей, мне требуются ссылки на глубоко вложенные зависимости, чтобы получить экземпляр и предоставить его как зависимость от другого объекта. Кажется, это нарушает весь смысл n-уровневой архитектуры, потому что мне нужно сослаться на все это и составить его в коде веб-страницы. Есть ли способ обойти это без абстрагирования всего графа объектов в загрузчик/контейнер? - person moarboilerplate; 29.07.2014
comment
Я должен упомянуть, что одним из ограничений среды является то, что я вынужден использовать контейнер в качестве локатора службы (только на самом верхнем уровне разрешения зависимостей), поскольку внедрение WebForms выходит за рамки. - person moarboilerplate; 29.07.2014

  • Вы можете инвертировать управление без контейнера IoC, как объяснил @LadislavMrnka.

  • Вы можете выполнить инверсию зависимостей и извлечь выгоду из слабой связанности и тестируемости, не используя IoC на основе интерфейса. События и функции высшего порядка – это две способы сделать это.

  • Вы можете отделить часть своей кодовой базы, нет необходимости делать это все сразу. Объект может иметь свои зависимости, определяемые и/или вводимые объектом, который его потребляет, вам не нужно откладывать его до загрузчика.

Учитывая это, ответ на ваш вопрос «да» и по-разному :)

Я думаю, что это действительно хорошая идея начать с малого, сначала сделав несколько компонентов тестируемыми (и протестированными), вместо того, чтобы пытаться тестировать и IoC-ify все заранее в одном долгом, утомительном и рискованном рефакторинге. IoC может появиться позже, когда большая часть вашей кодовой базы будет протестирована и очищена.

person guillaume31    schedule 29.07.2014
comment
Основная трудность, с которой я сталкиваюсь, заключается в том, что при структурировании инверсии зависимостей в веб-приложении WebForms, где я не внедряю формы и не использую сумасшедший http-обработчик, отделенный код становится местом, где в зависимости от моей реализации я должен создавать экземпляры зависимостей для всего. несколько уровней вниз или, если вы следуете подходу функций более высокого порядка, вызовите методы. Это приводит к тому, что мой программный код требует ссылки на вещи, которые должны быть инкапсулированы в отдельной библиотеке (даже если я использую абстрактную фабрику, мне все равно нужно знать тип объекта, который будет выводиться). - person moarboilerplate; 29.07.2014